trace_id.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. """
  2. Trace ID 生成与逐层解析工具。
  3. Sub-Agent 创建 Trace 时生成嵌套 ID;查询和辅助显示只解析最后一层,
  4. 权威父子关系始终以 Trace.parent_trace_id 为准。
  5. Trace ID 格式:
  6. - 主 Trace: {uuid} (标准 UUID)
  7. - Sub-Trace: {parent_id}@{mode}-{timestamp}-{seq}
  8. 例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
  9. """
  10. import uuid
  11. from datetime import datetime
  12. from threading import Lock
  13. from typing import Dict, Optional
  14. # 全局计数器(线程安全)
  15. _seq_lock = Lock()
  16. _seq_counter: Dict[str, int] = {} # key: "{parent_id}@{mode}-{timestamp}"
  17. def generate_trace_id() -> str:
  18. """
  19. 生成主 Trace ID
  20. Returns:
  21. 标准 UUID 字符串
  22. """
  23. return str(uuid.uuid4())
  24. def generate_sub_trace_id(parent_id: str, mode: str) -> str:
  25. """
  26. 生成 Sub-Trace ID
  27. 格式: {parent_id}@{mode}-{timestamp}-{seq}
  28. 使用完整的 parent_id(不截断),避免 ID 冲突风险。
  29. 同一秒内多次调用会递增序号。
  30. Args:
  31. parent_id: 父 Trace ID(完整 UUID)
  32. mode: 运行模式(explore, delegate, compaction 等)
  33. Returns:
  34. Sub-Trace ID
  35. Examples:
  36. >>> generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "explore")
  37. '2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001'
  38. >>> generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "delegate")
  39. '2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001'
  40. """
  41. # 直接使用完整 UUID,不截断
  42. timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
  43. # 生成序号(同一秒内递增)
  44. prefix = f"{parent_id}@{mode}-{timestamp}"
  45. with _seq_lock:
  46. seq = _seq_counter.get(prefix, 0) + 1
  47. _seq_counter[prefix] = seq
  48. return f"{prefix}-{seq:03d}"
  49. def parse_parent_trace_id(trace_id: str) -> Optional[str]:
  50. """
  51. 从嵌套 ID 最后一个 @ 之前解析直接父 Trace ID。
  52. 该函数仅供追溯与辅助显示,不取代持久化的 parent_trace_id 安全校验。
  53. Args:
  54. trace_id: Trace ID
  55. Returns:
  56. 父 Trace ID,如果是主 Trace 则返回 None
  57. Examples:
  58. >>> parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
  59. '2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d'
  60. >>> parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
  61. None
  62. """
  63. if '@' in trace_id:
  64. return trace_id.rsplit('@', 1)[0]
  65. return None
  66. def is_sub_trace(trace_id: str) -> bool:
  67. """
  68. 判断是否为 Sub-Trace
  69. Args:
  70. trace_id: Trace ID
  71. Returns:
  72. True 表示是 Sub-Trace,False 表示是主 Trace
  73. Examples:
  74. >>> is_sub_trace("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
  75. True
  76. >>> is_sub_trace("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
  77. False
  78. """
  79. return '@' in trace_id
  80. def extract_mode(trace_id: str) -> Optional[str]:
  81. """
  82. 从最后一层 Sub-Trace ID 中提取当前子 Trace 的运行模式。
  83. 递归 ID 可含多个 @,因此这里与 parse_parent_trace_id() 一样从末层解析。
  84. Args:
  85. trace_id: Trace ID
  86. Returns:
  87. 运行模式(explore, delegate 等),如果是主 Trace 则返回 None
  88. Examples:
  89. >>> extract_mode("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
  90. 'explore'
  91. >>> extract_mode("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001")
  92. 'delegate'
  93. >>> extract_mode("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
  94. None
  95. """
  96. if '@' not in trace_id:
  97. return None
  98. # 格式: parent@mode-timestamp-seq
  99. parts = trace_id.rsplit('@', 1)[1] # "mode-timestamp-seq"
  100. mode = parts.split('-')[0]
  101. return mode
  102. def reset_seq_counter():
  103. """
  104. 重置序号计数器
  105. 主要用于测试,生产环境不应调用此函数。
  106. """
  107. global _seq_counter
  108. with _seq_lock:
  109. _seq_counter.clear()