接入 ARMS 應用監控以後,探針對常見的 AI 架構進行了自動埋點,因此不需要修改任何代碼,就可以實現調用鏈資訊的採集。如果您需要在調用鏈資訊中,體現業務方法的執行情況,可以引入 loongsuite-otel-util-genai 以及 OpenTelemetry SDK,在業務代碼中增加自訂埋點。本文介紹如何通過 loongsuite-otel-util-genai 以及 OpenTelemetry Python SDK 實現自訂埋點以及自訂 Attribute。
ARMS 探針支援的 AI 組件和架構,請參見:
前提條件
已經成功接入 ARMS 應用監控。
引入依賴
pip install -U "loongsuite-otel-util-genai>=0.6.1"安裝後提供 opentelemetry.util.genai 包及 ExtendedTelemetryHandler 等擴充介面。0.6.1 及以上版本支援在 Agent Span 生命週期內自動將 Agent 名稱透傳到下遊 GenAI 子 Span。更多資訊,請參見 loongsuite-otel-util-genai 詳細文檔。
使用 loongsuite-otel-util-genai 和 OpenTelemetry SDK
通過 loongsuite-otel-util-genai 和 OpenTelemetry SDK 主要可以實現以下操作:
建立 GenAI 語義的 Span(Entry、Agent、Tool、ReAct Step等)。
通過 OpenTelemetry SDK 埋點產生自訂 Span。
為 Span 增加自訂 Attributes。
擷取當前 Trace 上下文並列印 traceId。
名詞介紹
Span:一次請求的一個具體操作,比如一次 LLM 調用或一次工具執行。
SpanContext:一次請求追蹤的上下文,包含 traceId、spanId 等資訊。
Attribute:Span 的附加屬性欄位,用於記錄關鍵資訊,如模型名稱、Token 用量等。
Handler:loongsuite-otel-util-genai 提供的
ExtendedTelemetryHandler,用於建立符合 GenAI語義約定的 Span。
loongsuite-otel-util-genai 支援的全部 Span 類型如下表所示,本文重點介紹 Entry、Agent、Tool 和 ReAct Step 的用法,其他類型(Embedding、Retrieval、Rerank、Memory等)的詳細用法請參見loongsuite-otel-util-genai 完整文檔。
Span 類型 | 操作名 | 說明 |
Entry |
| 應用入口,攜帶 session_id / user_id / 應用完整互動資訊 |
Agent |
| Agent 調用,匯總 Token 用量 |
Tool |
| 工具/函數執行 |
Step |
| ReAct 單輪迭代標識 |
LLM |
| 大模型對話(通常由探針自動採集) |
Embedding |
| 向量嵌入 |
Retriever |
| 檢索(RAG) |
Reranker |
| 重排序 |
Memory |
| 記憶讀寫 |
下面分步介紹各類 Span 的埋點寫法,每一步給出獨立的程式碼片段。完整可啟動並執行範例程式碼請參見本文末尾附錄部分。
請務必通過 get_extended_telemetry_handler() 擷取 Handler 執行個體,而非直接執行個體化 TelemetryHandler。ARMS 探針僅對 get_extended_telemetry_handler() 進行了相容適配,直接執行個體化 TelemetryHandler 可能導致環境變數相容性問題。
自訂埋點時請務必遵循LLM Trace欄位定義說明中的語義規範。AI應用可觀測能力(Token統計、會話分析等)均基於該規範中定義進行適配和渲染,若 Span 屬性不符合規範,相關資料可能無法在控制台中正確展示。
1. 擷取 Handler 和 Tracer
通過 get_extended_telemetry_handler() 擷取 loongsuite-otel-util-genai 的單例 Handler,通過 get_tracer(__name__) 擷取 OpenTelemetry SDK 的 Tracer。兩者分別用於建立 GenAI 語義 Span 和自訂業務 Span。
from opentelemetry.util.genai.extended_handler import get_extended_telemetry_handler
from opentelemetry.util.genai.extended_types import (
ExecuteToolInvocation,
InvokeAgentInvocation,
)
from opentelemetry.util.genai._extended_common import EntryInvocation, ReactStepInvocation
from opentelemetry.util.genai.types import Error, InputMessage, OutputMessage, Text
from opentelemetry.trace import get_tracer
handler = get_extended_telemetry_handler()
tracer = get_tracer(__name__)
Handler 提供兩種使用方式:
上下文管理器(
with handler.entry(inv)等):推薦方式,自動管理 Span 生命週期。start/stop/fail API(
handler.start_entry(inv)/handler.stop_entry(inv)/handler.fail_entry(inv, error)):適用於非同步、回調或流式等無法使用with語句的情境。
2. 建立 Entry Span
在請求入口處建立 Entry Span,攜帶 session_id、user_id,並通過 input_messages 記錄使用者輸入。流式響應完成後,將輸出內容拼接設定到 output_messages,再調用 stop_entry 結束 Span。這樣在控制台中能直接看到該次請求的完整輸入和最終輸出。
entry_inv = EntryInvocation(
session_id=req.session_id or str(uuid.uuid4()),
user_id=req.user_id or "anonymous",
input_messages=[
InputMessage(role="user", parts=[Text(content=req.topic)]),
],
)
def event_generator():
handler.start_entry(entry_inv)
output_chunks: list[str] = [ ]
try:
for chunk in run_agent_stream(topic=req.topic):
output_chunks.append(chunk)
yield f"data: {json.dumps({'content': chunk}, ensure_ascii=False)}\n\n"
yield "data: [DONE]\n\n"
except Exception as exc:
handler.fail_entry(entry_inv, Error(message=str(exc), type=type(exc)))
yield f"data: {json.dumps({'error': str(exc)}, ensure_ascii=False)}\n\n"
return
entry_inv.output_messages = [
OutputMessage(
role="assistant",
parts=[Text(content="".join(output_chunks))],
finish_reason="stop",
),
]
handler.stop_entry(entry_inv)
3. 建立 Agent Span
通過 start_invoke_agent 建立 Agent Span,記錄 Agent 名稱、模型和描述資訊。Agent Span 是整個調用鏈的根 GenAI Span,所有後續的 ReAct Step、LLM 調用和 Tool 調用都作為它的子 Span。
從 loongsuite-otel-util-genai 0.6.1 開始,調用 start_invoke_agent 時,如果 InvokeAgentInvocation.agent_name 已設定,工具會自動通過 OpenTelemetry Baggage 寫入 gen_ai.agent.name。在該 Agent Span 生命週期內,由 LoongSuite GenAI 探針或本工具建立的 LLM、Tool、Embedding、Retrieval、Rerank、Memory、ReAct Step 等 GenAI 子 Span 會自動繼承該屬性,無需在每個子 Span 中手動設定 Agent 名稱。通過 OpenTelemetry SDK 建立的普通自訂業務 Span 不會自動補充該 GenAI 屬性,如需記錄業務屬性,請繼續使用 span.set_attribute()。
invocation = InvokeAgentInvocation(
provider="dashscope",
agent_name="TechContentAgent",
agent_description="技術內容產生助手",
request_model="qwen-plus",
)
total_input_tokens = 0
total_output_tokens = 0
handler.start_invoke_agent(invocation)
try:
# ... Agent 核心邏輯(ReAct 迴圈) ...
invocation.input_tokens = total_input_tokens
invocation.output_tokens = total_output_tokens
handler.stop_invoke_agent(invocation)
except Exception:
handler.fail_invoke_agent(invocation, Error(message="agent failed", type=RuntimeError))
raise
Agent 執行完成後,將累積的 total_input_tokens 和 total_output_tokens 寫入 Agent Span,實現 Token 指標匯總統計。
4. 建立 ReAct Step Span
在每一輪 ReAct 推理迭代時建立 Step Span,傳入當前輪次 round。迭代結束時設定 finish_reason:需要繼續迭代為 continue,最終回答為 stop。樣本中每輪迭代的 LLM 調用由 ARMS 探針自動埋點,無需手動建立。
step_inv = ReactStepInvocation(round=iteration + 1)
handler.start_react_step(step_inv)
try:
response = client.chat.completions.create(
model="qwen-plus",
messages=messages,
tools=TOOL_DEFINITIONS,
)
# ... 處理響應 ...
step_inv.finish_reason = "stop" # 或 "continue"
handler.stop_react_step(step_inv)
except Exception:
handler.fail_react_step(step_inv, Error(message="step failed", type=RuntimeError))
raise
5. 建立 Tool Span
當模型返回工具調用時,為每個 tool_call 建立 Tool Span,記錄工具名稱、調用 ID、入參和返回結果。
tool_inv = ExecuteToolInvocation(
tool_name=tool_call.function.name,
tool_call_id=tool_call.id,
tool_call_arguments=tool_call.function.arguments,
tool_type="function",
)
handler.start_execute_tool(tool_inv)
try:
result = dispatch_tool(tool_name, tool_call.function.arguments)
tool_inv.tool_call_result = result
except Exception as exc:
handler.fail_execute_tool(tool_inv, error=Error(message=str(exc), type=type(exc)))
raise
else:
handler.stop_execute_tool(tool_inv)
6. 使用 OpenTelemetry SDK 建立自訂 Span
除了 loongsuite-otel-util-genai 提供的 GenAI 語義 Span,還可以通過 OpenTelemetry SDK 的 tracer.start_as_current_span() 建立自訂業務 Span,與 GenAI Span 混合使用。
以下樣本展示了兩種典型的自訂 Span 用法:
duplicate_tool_detection — 工具重複調用檢測
在每輪 ReAct 迭代前執行,通過 Counter 統計每個工具的調用次數,將檢測結果寫入 gen_ai.loop_detection.* 屬性。若發現重複,向訊息列表追加系統提示引導模型避免重複。
def _check_duplicate_tools(
tool_usage_counter: Counter,
messages: list[dict[str, Any]],
) -> None:
duplicates = [name for name, count in tool_usage_counter.items() if count > 1]
has_duplicates = len(duplicates) > 0
with tracer.start_as_current_span("duplicate_tool_detection") as span:
span.set_attributes({
"gen_ai.loop_detection.detected": has_duplicates,
"gen_ai.loop_detection.duplicate_tools": str(duplicates) if has_duplicates else "[ ]",
"gen_ai.loop_detection.total_calls": sum(tool_usage_counter.values()),
"gen_ai.loop_detection.unique_tools": len(tool_usage_counter),
})
if has_duplicates:
details = ", ".join(f"{n}({tool_usage_counter[n]}次)" for n in duplicates)
messages.append({
"role": "system",
"content": f"[系統提示] 檢測到工具被重複調用:{details}。請避免重複調用。",
})
response_loop_detection — LLM 回複迴圈檢測
在每輪 LLM 回複後執行,通過比較當前回複與上一輪迴複的文本相似性,將 is_loop、overlap_ratio 等指標寫入 Span 屬性。若檢測到迴圈(文本完全相同或重疊率超過 80%),設定 finish_reason 為 loop_detected 並提前終止 Agent。
def _check_response_loop(
current_content: str | None,
previous_content: str | None,
) -> bool:
cur = (current_content or "").strip()
prev = (previous_content or "").strip()
with tracer.start_as_current_span("response_loop_detection") as span:
if not prev or not cur:
span.set_attributes({
"gen_ai.loop_detection.is_loop": False,
"gen_ai.loop_detection.reason": "no_text_content",
})
return False
is_identical = cur == prev
longer = max(len(cur), len(prev))
common_prefix_len = sum(1 for a, b in zip(cur, prev) if a == b)
overlap_ratio = common_prefix_len / longer if longer > 0 else 0.0
is_loop = is_identical or overlap_ratio > 0.8
span.set_attributes({
"gen_ai.loop_detection.is_loop": is_loop,
"gen_ai.loop_detection.is_identical": is_identical,
"gen_ai.loop_detection.overlap_ratio": round(overlap_ratio, 2),
"gen_ai.loop_detection.current_length": len(cur),
"gen_ai.loop_detection.previous_length": len(prev),
})
return is_loop
由於自訂 Span 不屬於大模型語義規範,在控制台的調用鏈視圖中需要切換到全部視圖才能查看。
查看監控詳情
登入CloudMonitor2.0控制台,選擇目標工作空間,在左側導覽列選擇所有功能 > AI應用可觀測。
在AI應用列表頁面可以看到已接入的應用,單擊應用程式名稱可以查看詳細的應用監控資料。
埋點效果展示
1. Entry Span 詳情
Enter Span 能看到 gen_ai.session.id、gen_ai.user.id 等關鍵屬性,通過在函數入口處設定能自動透傳到 LLM、TOOL等Span中,能用於關聯會話和使用者資訊進行分析。同時 Entry Span 還攜帶 gen_ai.input.messages(使用者輸入內容)和 gen_ai.output.messages(最終輸出內容),便於在控制台中直接查看該次請求的整體互動內容。
Entry Span 詳情面板中,關鍵屬性包括 gen_ai.session.id(會話唯一標識)和 gen_ai.user.id(使用者標識),這些屬性在函數入口處設定後會自動透傳到下遊子 Span。同時可在詳情面板查看 gen_ai.input.messages(使用者輸入的完整訊息內容)和 gen_ai.output.messages(模型最終輸出的完整訊息內容)。
2. Agent Span 詳情
Agent Span能看到該 Agent 的定義名稱以及相應的描述,同時體現上述範例程式碼中統計的屬於該 Agent 層級的 Token 用量匯總統計效果。
Agent Span 詳情面板中,關鍵屬性包括 gen_ai.agent.name(值為 TechContentAgent)、gen_ai.provider.name(dashscope)、gen_ai.request.model(qwen-plus),以及 Agent 層級的 Token 用量匯總:gen_ai.usage.input_tokens(3982)、gen_ai.usage.output_tokens(884)、gen_ai.usage.total_tokens(4866)。
3. Tool Span 詳情
Tool Span 能看到該 Tool 的名稱以及入參配置,並且展示工具調用結果。Tool Span 展示工具調用的詳細資料。選中 execute_tool generate_seo_keywords 後,右側詳情面板顯示工具名稱(Tool: $generate_seo_keywords)、工具類型(Tool Type: function)、工具調用入參(Tool Call Arguments,如 {"topic": "CMS 2.0 AI 警示降噪"})以及工具返回結果(Tool Result),便於排查工具調用的輸入輸出是否符合預期。
4. LLM Span 詳情
LLM Span在上述範例程式碼中並沒有進行手動埋點,由於是 openai 調用,此處全部由探針自動採集,能清晰觀察到該次 LLM 調用的完整上下文資訊以及 token 消耗。
LLM Span 詳情面板展示應用程式名稱、介面名、IP、開始與結束時間、spanId、parentSpanId、狀態代碼等基本資料。附加資訊選項卡可查看該次調用的 response 輸出內容,Output Messages選項卡展示 assistant 的 tool_call 調用參數(如工具名稱和請求體)。
5.自訂 Span 詳情
範例程式碼中通過 OpenTelemetry SDK 建立了兩個自訂業務 Span,展示如何將自訂埋點與 GenAI 語義 Span 混合使用,由於該自訂Span並不在大模型語義中,需要開啟全部視圖進行查看。
duplicate_tool_detection:在每輪 ReAct 迭代前執行,用於檢測 Agent 是否陷入工具重複調用。Span 屬性中記錄了是否檢測到重複、重複的工具列表、總調用次數和去重工具數,便於在 ARMS 中快速定位 Agent 的工具調用迴圈問題。
在 ARMS Trace 詳情頁中,單擊 duplicate_tool_detection Span 可在右側詳情面板查看迴圈檢測屬性,各屬性鍵名分別為
gen_ai.loop_detection.detected、gen_ai.loop_detection.duplicate_tools、gen_ai.loop_detection.total_calls、gen_ai.loop_detection.unique_tools和gen_ai.loop_detection.details。當 4 次工具調用均不重複時,detected為false,duplicate_tools為空白數組[]。response_loop_detection:在每輪 LLM 回複後執行,用於檢測模型是否連續返回高度相似的內容。Span 屬性中記錄了是否判定為迴圈、文本是否完全相同、重疊率以及當前和上一輪迴複的文本長度,協助排查模型陷入重複輸出的異常情境。
在 Trace 詳情頁的瀑布視圖中選中 response_loop_detection Span,右側 Attributes 面板可查看具體屬性,例如
gen_ai.loop_detection.is_loop=false表示未檢測到迴圈,gen_ai.loop_detection.reason=no_text_content表示原因為無常值內容。
相關文檔
loongsuite-otel-util-genai 支援的全部 Span 類型(Embedding、Retrieval、Rerank、Memory等)的詳細用法,請參見loongsuite-otel-util-genai 完整文檔。