Skip to content

Commit 366811c

Browse files
fix(trace): missing output of agent_step logging (#145)
Co-authored-by: Zhicheng Zhang <[email protected]>
1 parent 7d2dcfe commit 366811c

File tree

10 files changed

+1794
-246
lines changed

10 files changed

+1794
-246
lines changed

src/agentscope_runtime/engine/runner.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
from .services.environment_manager import EnvironmentManager
2525
from .tracing import TraceType
2626
from .tracing.wrapper import trace
27+
from .tracing.message_util import (
28+
merge_agent_response,
29+
get_agent_response_finish_reason,
30+
)
2731

2832

2933
class Runner:
@@ -128,7 +132,12 @@ async def deploy(
128132
self._deploy_managers[deploy_manager.deploy_id] = deploy_result
129133
return deploy_result
130134

131-
@trace(TraceType.AGENT_STEP)
135+
@trace(
136+
TraceType.AGENT_STEP,
137+
trace_name="agent_step",
138+
merge_output_func=merge_agent_response,
139+
get_finish_reason_func=get_agent_response_finish_reason,
140+
)
132141
async def stream_query( # pylint:disable=unused-argument
133142
self,
134143
request: Union[AgentRequest, dict],
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Feature Overview
2+
The Tracing module is used for tracing components and arbitrary functions, including two parts: Log and Report. The Log part outputs in Dashscope Log format, while the Report part uses the OpenTelemetry SDK to report tracing information.
3+
4+
# Usage
5+
## Logging
6+
1. Configure environment variables (enabled by default)
7+
```shell
8+
export TRACE_ENABLE_LOG=true
9+
```
10+
2. Add decorator to any function, example:
11+
```python
12+
from agentscope_runtime.engine.tracing import trace, TraceType
13+
14+
@trace(trace_type=TraceType.LLM, trace_name="llm_func")
15+
def llm_func():
16+
pass
17+
```
18+
Output:
19+
```text
20+
{"time": "2025-08-13 11:23:41.808", "step": "llm_func_start", "model": "", "user_id": "", "code": "", "message": "", "task_id": "", "request_id": "", "context": {}, "interval": {"type": "llm_func_start", "cost": 0}, "ds_service_id": "test_id", "ds_service_name": "test_name"}
21+
{"time": "2025-08-13 11:23:41.808", "step": "llm_func_end", "model": "", "user_id": "", "code": "", "message": "", "task_id": "", "request_id": "", "context": {}, "interval": {"type": "llm_func_end", "cost": "0.000"}, "ds_service_id": "test_id", "ds_service_name": "test_name"}
22+
```
23+
24+
3. Custom logging (prerequisite: **function contains kwargs parameter**)
25+
```python
26+
from agentscope_runtime.engine.tracing import trace, TraceType
27+
28+
@trace(trace_type=TraceType.LLM, trace_name="llm_func")
29+
def llm_func(**kwargs):
30+
trace_event = kwargs.pop("trace_event", None)
31+
if trace_event:
32+
# Custom string message
33+
trace_event.on_log("hello")
34+
35+
# Formatted step message
36+
trace_event.on_log(
37+
"",
38+
**{
39+
"step_suffix": "mid_result",
40+
"payload": {
41+
"output": "hello",
42+
},
43+
},
44+
)
45+
```
46+
Output:
47+
```text
48+
{"time": "2025-08-13 11:27:14.727", "step": "llm_func_start", "model": "", "user_id": "", "code": "", "message": "", "task_id": "", "request_id": "", "context": {}, "interval": {"type": "llm_func_start", "cost": 0}, "ds_service_id": "test_id", "ds_service_name": "test_name"}
49+
{"time": "2025-08-13 11:27:14.728", "step": "", "model": "", "user_id": "", "code": "", "message": "hello", "task_id": "", "request_id": "", "context": {}, "interval": {"type": "", "cost": "0"}, "ds_service_id": "test_id", "ds_service_name": "test_name"}
50+
{"time": "2025-08-13 11:27:14.728", "step": "llm_func_mid_result", "model": "", "user_id": "", "code": "", "message": "", "task_id": "", "request_id": "", "context": {"output": "hello"}, "interval": {"type": "llm_func_mid_result", "cost": "0.000"}, "ds_service_id": "test_id", "ds_service_name": "test_name"}
51+
{"time": "2025-08-13 11:27:14.728", "step": "llm_func_end", "model": "", "user_id": "", "code": "", "message": "", "task_id": "", "request_id": "", "context": {}, "interval": {"type": "llm_func_end", "cost": "0.000"}, "ds_service_id": "test_id", "ds_service_name": "test_name"}
52+
```
53+
## Reporting
54+
1. Configure environment variables (disabled by default)
55+
```shell
56+
export TRACE_ENABLE_LOG=false
57+
export TRACE_ENABLE_REPORT=true
58+
export TRACE_AUTHENTICATION={YOUR_AUTHENTICATION}
59+
export TRACE_ENDPOINT={YOUR_ENDPOINT}
60+
```
61+
2. Add decorator to non-streaming functions, example:
62+
63+
```python
64+
from agentscope_runtime.engine.tracing import trace, TraceType
65+
66+
@trace(trace_type=TraceType.LLM,
67+
trace_name="llm_func")
68+
def llm_func(args: str):
69+
return args + "hello"
70+
```
71+
72+
73+
3. Add decorator to streaming functions, example:
74+
```python
75+
from agentscope_runtime.engine.tracing import trace, TraceType
76+
from agentscope_runtime.engine.tracing.message_util import (
77+
get_finish_reason,
78+
merge_incremental_chunk,
79+
)
80+
81+
@trace(trace_type=TraceType.LLM,
82+
trace_name="llm_func",
83+
get_finish_reason_func=get_finish_reason,
84+
merge_output_func=merge_incremental_chunk)
85+
def llm_func(args: str):
86+
for i in range(10):
87+
yield i
88+
```
89+
Where get_finish_reason and merge_incremental_chunk are custom processing functions, optional, defaults to get_finish_reason and merge_incremental_chunk in message_util.py.
90+
91+
get_finish_reason is a custom function to get finish_reason, used to determine if streaming output has ended. Example:
92+
```python
93+
from openai.types.chat import ChatCompletionChunk
94+
from typing import List, Optional
95+
96+
def get_finish_reason(response: ChatCompletionChunk) -> Optional[str]:
97+
finish_reason = None
98+
if hasattr(response, 'choices') and len(response.choices) > 0:
99+
if response.choices[0].finish_reason:
100+
finish_reason = response.choices[0].finish_reason
101+
102+
return finish_reason
103+
```
104+
105+
merge_output is a custom function to merge output, used to construct the final output information. Example:
106+
```python
107+
from openai.types.chat import ChatCompletionChunk
108+
from typing import List, Optional
109+
110+
def merge_incremental_chunk(
111+
responses: List[ChatCompletionChunk],
112+
) -> Optional[ChatCompletionChunk]:
113+
# get usage or finish reason
114+
merged = ChatCompletionChunk(**responses[-1].__dict__)
115+
116+
# if the responses has usage info, then merge the finish reason chunk to usage chunk
117+
if not merged.choices and len(responses) > 1:
118+
merged.choices = responses[-2].choices
119+
120+
for resp in reversed(responses[:-1]):
121+
for i, j in zip(merged.choices, resp.choices):
122+
if isinstance(i.delta.content, str) and isinstance(
123+
j.delta.content,
124+
str,
125+
):
126+
i.delta.content = j.delta.content + i.delta.content
127+
if merged.usage and resp.usage:
128+
merged.usage.total_tokens += resp.usage.total_tokens
129+
130+
return merged
131+
```
132+
133+
134+
135+
4. Setting request_id and common attributes
136+
137+
request_id is used to bind the context of different requests. common attributes are public span attributes, all spans under this request will have these attributes.
138+
139+
**Automatic request_id setting**: When the user does not manually call `TracingUtil.set_request_id` at the beginning of request processing, the system will automatically generate and set a unique request_id in the root span.
140+
141+
**Manual setting**: Set request_id and common attributes in functions **not decorated with @trace**, for example, set immediately after request information is parsed. Example:
142+
143+
```python
144+
from agentscope_runtime.engine.tracing import TracingUtil
145+
146+
common_attributes = {
147+
"gen_ai.user.id": "user_id",
148+
"bailian.app.id": "app_id",
149+
"bailian.app.owner_id": "app_id",
150+
"bailian.app.env": "pre",
151+
"bailian.app.workspace": "workspace"
152+
}
153+
TracingUtil.set_request_id("request_id")
154+
TracingUtil.set_common_attributes(common_attributes)
155+
```
156+
5. Custom reporting (prerequisite: **function contains kwargs parameter**)
157+
```python
158+
import json
159+
from agentscope_runtime.engine.tracing import trace, TraceType
160+
161+
@trace(trace_type=TraceType.LLM, trace_name="llm_func")
162+
def llm_func(**kwargs):
163+
trace_event = kwargs.pop("trace_event", None)
164+
if trace_event:
165+
# Set string attribute
166+
trace_event.set_attribute("key", "value")
167+
# Set dict attribute
168+
trace_event.set_attribute("func_7.key", json.dumps({'key0': 'value0', 'key1': 'value1'}))
169+
```

src/agentscope_runtime/engine/tracing/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33

44
from .base import BaseLogHandler, Tracer, TracerHandler
55
from .tracing_metric import TraceType
6+
from .tracing_util import TracingUtil
67
from .wrapper import trace
78

9+
__all__ = [
10+
"trace",
11+
"TraceType",
12+
"TracingUtil",
13+
]
14+
815

916
def create_handler(
1017
eval_mode: Union[str, List] = "default",
@@ -16,9 +23,7 @@ def create_handler(
1623
if "default" in eval_mode:
1724
handlers.append(BaseLogHandler())
1825
elif "local_logging" in eval_mode:
19-
from .local_logging_handler import (
20-
LocalLogHandler,
21-
)
26+
from .local_logging_handler import LocalLogHandler
2227

2328
handlers.append(LocalLogHandler(**eval_params))
2429
return handlers
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
from typing import AsyncIterable, AsyncIterator, Tuple, TypeVar
3+
4+
T_co = TypeVar("T_co", covariant=True)
5+
6+
7+
async def aenumerate(
8+
asequence: AsyncIterable[T_co],
9+
start: int = 0,
10+
) -> AsyncIterator[Tuple[int, T_co]]:
11+
"""Asynchronously enumerate an async iterator from a given start value.
12+
13+
Args:
14+
asequence (AsyncIterable[T_co]): The async iterable to enumerate.
15+
start (int): The starting value for enumeration. Defaults to 0.
16+
17+
Yields:
18+
Tuple[int, T_co]: A tuple containing the index and the item from the
19+
async iterable.
20+
"""
21+
n = start
22+
async for elem in asequence:
23+
yield n, elem
24+
n += 1

0 commit comments

Comments
 (0)