Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions trpc_agent_sdk/telemetry/_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,30 @@ def get_trpc_agent_span_name() -> str:
return _trpc_agent_span_name


def _join_parts_with_thought_tag(parts) -> str:
"""Join part texts, wrapping thought parts in <trace_think> tags.

Parts with ``thought=True`` (model reasoning/thinking) are wrapped in
``<trace_think>...</trace_think>`` tags so downstream consumers (e.g.
zhiyan-llm exporter) can distinguish and optionally strip them.

Args:
parts: A sequence of Part objects with ``text`` and optional
``thought`` attributes.

Returns:
The joined text string with thought parts tagged.
"""
result = []
for part in parts:
text = part.text or ""
if getattr(part, "thought", False):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MagicMock 未设置 thought 导致思考段判定恒真

_join_parts_with_thought_taggetattr(part, "thought", False) 判断思考段,但测试 _make_part 用无 spec 的 MagicMock,任意未定义属性返回真值子 mock,导致所有文本段都被包成 <trace_think>...</trace_think>,现有断言大面积失败、CI 阻塞。修复建议:在 _make_part 显式 part.thought = False 或改用 spec=Part 的 mock,并补充 thought=True 用例。

result.append(f"<trace_think>{text}</trace_think>")
else:
result.append(text)
return "\n".join(result)


def _safe_json_serialize(obj) -> str:
"""Convert any Python object to a JSON-serializable type or string.

Expand Down Expand Up @@ -116,11 +140,11 @@ def trace_runner(
span.set_attribute(f"{_trpc_agent_span_name}.runner.session_id", session_id)
input_str = ""
if new_message and new_message.parts:
input_str = "\n".join([part.text or "" for part in new_message.parts])
input_str = _join_parts_with_thought_tag(new_message.parts)
span.set_attribute(f"{_trpc_agent_span_name}.runner.input", input_str)
output_str = ""
if last_event and last_event.content and last_event.content.parts:
output_str = "\n".join([part.text or "" for part in last_event.content.parts])
output_str = _join_parts_with_thought_tag(last_event.content.parts)
span.set_attribute(f"{_trpc_agent_span_name}.runner.output", output_str)

# Set state attributes for begin and end
Expand Down Expand Up @@ -181,15 +205,15 @@ def trace_cancellation(
# Input (prefixed with [CANCELLED] marker)
input_str = ""
if new_message and new_message.parts:
input_str += "\n".join([part.text or "" for part in new_message.parts])
input_str += _join_parts_with_thought_tag(new_message.parts)
span.set_attribute(f"{_trpc_agent_span_name}.runner.input", input_str)

# Output (prefixed with [CANCELLED] marker, includes partial text or last event)
output_str = "[CANCELLED]\n"
if partial_text:
output_str += partial_text
elif last_event and last_event.content and last_event.content.parts:
output_str += "\n".join([part.text or "" for part in last_event.content.parts])
output_str += _join_parts_with_thought_tag(last_event.content.parts)
span.set_attribute(f"{_trpc_agent_span_name}.runner.output", output_str)

# Cancellation-specific attributes
Expand Down Expand Up @@ -245,11 +269,14 @@ def trace_agent(
for content in override_messages:
if content and content.parts:
for part in content.parts:
if part.text and not part.thought:
text_parts.append(part.text)
if part.text:
if getattr(part, "thought", False):
text_parts.append(f"<trace_think>{part.text}</trace_think>")
else:
text_parts.append(part.text)
input_str = "\n".join(text_parts)
elif invocation_context.user_content and invocation_context.user_content.parts:
input_str = "\n".join([part.text or "" for part in invocation_context.user_content.parts])
input_str = _join_parts_with_thought_tag(invocation_context.user_content.parts)
span.set_attribute(f"{_trpc_agent_span_name}.agent.input", input_str)

span.set_attribute(f"{_trpc_agent_span_name}.agent.output", agent_action)
Expand Down
Loading