Skip to content

Commit bced622

Browse files
authored
fix(qwenpaw): align session propagation and quiet bootstrap (#215)
2 parents 13f9339 + c4ce725 commit bced622

18 files changed

Lines changed: 642 additions & 38 deletions

File tree

README-zh.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ loongsuite-instrument \
429429

430430
```bash
431431
export LOONGSUITE_PYTHON_SITE_BOOTSTRAP=True
432+
# 交互式 CLI / 应用可选:不要把通用成功提示写入 stdout。
433+
export LOONGSUITE_PYTHON_SITE_BOOTSTRAP_LOG_SUCCESS=False
434+
export LOONGSUITE_PYTHON_SITE_BOOTSTRAP_STATUS_FILE=/tmp/loongsuite-site-bootstrap-status.json
432435
```
433436

434437
**步骤 4 — 创建 `~/.loongsuite/bootstrap-config.json`**
@@ -445,7 +448,7 @@ loongsuite-instrument \
445448
}
446449
```
447450

448-
然后执行 `python demo.py`。如需使用 **console** exporter、其他后端、改用 **`loongsuite-instrument`**(而非直接 `python`),或查看完整优先级/边界场景,请阅读 [loongsuite-site-bootstrap/README.md](loongsuite-site-bootstrap/README.md)
451+
然后执行 `python demo.py`。如需使用 **console** exporter、其他后端、改用 **`loongsuite-instrument`**(而非直接 `python`、控制成功提示输出,或查看完整优先级/边界场景,请阅读 [loongsuite-site-bootstrap/README.md](loongsuite-site-bootstrap/README.md)
449452

450453
> **Beta:**Site-bootstrap 会影响其启用环境中的所有 Python 进程,生产环境使用前请先阅读包 README。
451454

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,9 @@ Run **without** changing codes or bootstrap commands: a **`.pth` hook** loads Lo
423423

424424
```bash
425425
export LOONGSUITE_PYTHON_SITE_BOOTSTRAP=True
426+
# Optional for interactive CLIs/apps: suppress the generic success line on stdout.
427+
export LOONGSUITE_PYTHON_SITE_BOOTSTRAP_LOG_SUCCESS=False
428+
export LOONGSUITE_PYTHON_SITE_BOOTSTRAP_STATUS_FILE=/tmp/loongsuite-site-bootstrap-status.json
426429
```
427430

428431
**Step 4 — Create `~/.loongsuite/bootstrap-config.json`** with the OpenTelemetry environments keys you need.
@@ -437,7 +440,7 @@ Run **without** changing codes or bootstrap commands: a **`.pth` hook** loads Lo
437440
}
438441
```
439442

440-
Then run `python demo.py`. For **console** exporters, other backends, using **`loongsuite-instrument`** instead of plain `python`, or full precedence / edge cases, see [loongsuite-site-bootstrap/README.md](loongsuite-site-bootstrap/README.md).
443+
Then run `python demo.py`. For **console** exporters, other backends, using **`loongsuite-instrument`** instead of plain `python`, success logging controls, or full precedence / edge cases, see [loongsuite-site-bootstrap/README.md](loongsuite-site-bootstrap/README.md).
441444

442445
> **Beta:** Site-bootstrap affects every Python process in the environment where it is enabled; read the package README before using it in production.
443446

instrumentation-genai/opentelemetry-instrumentation-langchain/src/opentelemetry/instrumentation/langchain/callback_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def on_chat_model_start(
115115
for sub_messages in messages:
116116
for message in sub_messages:
117117
# Cast to Any to avoid type checking issues with LangChain's complex content type
118-
raw_content: Any = message.content # type: ignore[misc]
118+
raw_content: Any = message.content
119119
role = message.type
120120
parts: list[Text] = []
121121

instrumentation-loongsuite/loongsuite-instrumentation-agentscope/src/opentelemetry/instrumentation/agentscope/_wrapper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from opentelemetry.util.genai.types import Error, LLMInvocation
7171

7272
from .utils import (
73+
apply_entry_baggage_identity,
7374
convert_agent_response_to_output_messages,
7475
convert_chatresponse_to_output_messages,
7576
create_agent_invocation,
@@ -182,6 +183,7 @@ def hook(agent_self: Any, kwargs: dict) -> None:
182183

183184
state.react_round += 1
184185
inv = ReactStepInvocation(round=state.react_round)
186+
apply_entry_baggage_identity(inv)
185187
handler.start_react_step(inv, context=state.original_context)
186188
state.active_step = inv
187189
state.pending_acting_count = 0

instrumentation-loongsuite/loongsuite-instrumentation-agentscope/src/opentelemetry/instrumentation/agentscope/patch.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
from opentelemetry.util.genai.span_utils import _apply_error_attributes
3535
from opentelemetry.util.genai.types import Error
3636

37+
from .utils import (
38+
apply_entry_baggage_identity,
39+
entry_baggage_identity_attributes,
40+
)
41+
3742
logger = logging.getLogger(__name__)
3843

3944

@@ -407,6 +412,7 @@ async def wrap_tool_call(wrapped, instance, args, kwargs, handler):
407412
tool_description=tool_description,
408413
tool_call_arguments=tool_args,
409414
)
415+
apply_entry_baggage_identity(invocation)
410416

411417
# --- Skill attributes ---
412418
#
@@ -479,6 +485,7 @@ async def wrap_formatter_format(wrapped, instance, args, kwargs, tracer=None):
479485
try:
480486
# Record only basic information
481487
span.set_attribute("gen_ai.operation.name", "format")
488+
span.set_attributes(entry_baggage_identity_attributes())
482489

483490
# Execute the wrapped async call
484491
result = await wrapped(*args, **kwargs)

instrumentation-loongsuite/loongsuite-instrumentation-agentscope/src/opentelemetry/instrumentation/agentscope/utils.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@
3535
from agentscope.model import ChatModelBase, ChatResponse
3636
from pydantic import BaseModel
3737

38+
from opentelemetry import baggage
3839
from opentelemetry.semconv._incubating.attributes import (
3940
gen_ai_attributes as GenAIAttributes,
4041
)
42+
from opentelemetry.util.genai.extended_semconv.gen_ai_extended_attributes import (
43+
GEN_AI_SESSION_ID,
44+
GEN_AI_USER_ID,
45+
)
4146
from opentelemetry.util.genai.extended_types import (
4247
EmbeddingInvocation,
4348
InvokeAgentInvocation,
@@ -87,6 +92,43 @@ class AgentScopeGenAiProviderName(str, Enum):
8792
]
8893

8994

95+
def _current_baggage_value(key: str) -> str | None:
96+
try:
97+
value = baggage.get_baggage(key)
98+
except Exception:
99+
return None
100+
if value is None:
101+
return None
102+
text = str(value).strip()
103+
return text or None
104+
105+
106+
def entry_baggage_identity_attributes() -> dict[str, str]:
107+
"""Return entry-level identity from current OpenTelemetry Baggage.
108+
109+
QwenPaw opens an Entry span before AgentScope runs and writes
110+
``gen_ai.session.id`` / ``gen_ai.user.id`` into Baggage. AgentScope has
111+
its own ``run_id``; when both instrumentations are active, entry baggage is
112+
the request-level identity and should color downstream spans.
113+
"""
114+
attributes: dict[str, str] = {}
115+
session_id = _current_baggage_value(GEN_AI_SESSION_ID)
116+
user_id = _current_baggage_value(GEN_AI_USER_ID)
117+
if session_id:
118+
attributes[GEN_AI_SESSION_ID] = session_id
119+
if user_id:
120+
attributes[GEN_AI_USER_ID] = user_id
121+
return attributes
122+
123+
124+
def apply_entry_baggage_identity(invocation: Any) -> str | None:
125+
"""Copy entry-level identity baggage onto a GenAI invocation."""
126+
attributes = entry_baggage_identity_attributes()
127+
for key, value in attributes.items():
128+
invocation.attributes.setdefault(key, value)
129+
return attributes.get(GEN_AI_SESSION_ID)
130+
131+
90132
def get_provider_name(chat_model: ChatModelBase) -> str:
91133
"""Parse chat model provider name"""
92134
classname = chat_model.__class__.__name__
@@ -318,6 +360,9 @@ def create_llm_invocation(
318360
provider=provider_name,
319361
input_messages=input_messages,
320362
)
363+
entry_session_id = apply_entry_baggage_identity(invocation)
364+
if entry_session_id and invocation.conversation_id is None:
365+
invocation.conversation_id = entry_session_id
321366

322367
# Set optional request parameters if present
323368
if call_kwargs.get("max_tokens"):
@@ -353,6 +398,7 @@ def create_embedding_invocation(
353398
request_model=request_model,
354399
provider=provider_name,
355400
)
401+
apply_entry_baggage_identity(invocation)
356402

357403
# Set encoding formats if present
358404
if call_kwargs.get("encoding_formats"):
@@ -392,16 +438,18 @@ def create_agent_invocation(
392438
except Exception as e:
393439
logger.debug(f"Error converting agent input messages: {e}")
394440

441+
entry_session_id = _current_baggage_value(GEN_AI_SESSION_ID)
395442
invocation = InvokeAgentInvocation(
396443
provider=provider_name,
397444
agent_name=getattr(reply_instance, "name", "unknown_agent"),
398445
agent_id=getattr(reply_instance, "id", "unknown"),
399446
agent_description=inspect.getdoc(reply_instance.__class__)
400447
or "No description available",
401-
conversation_id=_config.run_id,
448+
conversation_id=entry_session_id or _config.run_id,
402449
request_model=request_model,
403450
input_messages=input_messages,
404451
)
452+
apply_entry_baggage_identity(invocation)
405453

406454
# Set system instruction if available
407455
if hasattr(reply_instance, "sys_prompt") and reply_instance.sys_prompt:

instrumentation-loongsuite/loongsuite-instrumentation-agentscope/tests/test_utils.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,32 @@
1717
Tests for utility functions in opentelemetry.instrumentation.agentscope.utils
1818
"""
1919

20+
from types import SimpleNamespace
21+
2022
from agentscope.message import Msg, ToolResultBlock
2123
from agentscope.tracing._converter import (
2224
_convert_block_to_part as _convert_block_to_part_framework,
2325
)
2426

27+
from opentelemetry import baggage
28+
from opentelemetry import context as otel_context
29+
from opentelemetry.instrumentation.agentscope import utils as utils_module
2530
from opentelemetry.instrumentation.agentscope.utils import (
2631
_convert_block_to_part as _convert_block_to_part_local,
2732
)
2833
from opentelemetry.instrumentation.agentscope.utils import (
34+
apply_entry_baggage_identity,
2935
convert_agentscope_messages_to_genai_format,
36+
create_agent_invocation,
37+
create_embedding_invocation,
38+
create_llm_invocation,
39+
entry_baggage_identity_attributes,
40+
)
41+
from opentelemetry.util.genai.extended_semconv.gen_ai_extended_attributes import (
42+
GEN_AI_SESSION_ID,
43+
GEN_AI_USER_ID,
3044
)
45+
from opentelemetry.util.genai.extended_types import ReactStepInvocation
3146
from opentelemetry.util.genai.types import ToolCallResponse
3247

3348

@@ -103,3 +118,106 @@ def test_convert_with_framework_converter(self):
103118
part_obj = converted[0].parts[0]
104119
assert isinstance(part_obj, ToolCallResponse)
105120
assert part_obj.response == "framework output"
121+
122+
def test_create_agent_invocation_prefers_entry_baggage_identity(
123+
self, monkeypatch
124+
):
125+
monkeypatch.setattr(
126+
utils_module._config, "run_id", "agentscope-run-id"
127+
)
128+
ctx = baggage.set_baggage(GEN_AI_SESSION_ID, "entry-session")
129+
ctx = baggage.set_baggage(GEN_AI_USER_ID, "entry-user", ctx)
130+
token = otel_context.attach(ctx)
131+
try:
132+
invocation = create_agent_invocation(
133+
SimpleNamespace(
134+
model=None,
135+
name="TestAgent",
136+
id="agent-id",
137+
sys_prompt=None,
138+
),
139+
tuple(),
140+
{},
141+
)
142+
finally:
143+
otel_context.detach(token)
144+
145+
assert invocation.conversation_id == "entry-session"
146+
assert invocation.attributes[GEN_AI_SESSION_ID] == "entry-session"
147+
assert invocation.attributes[GEN_AI_USER_ID] == "entry-user"
148+
149+
def test_entry_baggage_identity_attributes(self):
150+
ctx = baggage.set_baggage(GEN_AI_SESSION_ID, "entry-session")
151+
ctx = baggage.set_baggage(GEN_AI_USER_ID, "entry-user", ctx)
152+
token = otel_context.attach(ctx)
153+
try:
154+
attributes = entry_baggage_identity_attributes()
155+
finally:
156+
otel_context.detach(token)
157+
158+
assert attributes == {
159+
GEN_AI_SESSION_ID: "entry-session",
160+
GEN_AI_USER_ID: "entry-user",
161+
}
162+
163+
def test_create_agent_invocation_falls_back_to_agentscope_run_id(
164+
self, monkeypatch
165+
):
166+
monkeypatch.setattr(
167+
utils_module._config, "run_id", "agentscope-run-id"
168+
)
169+
170+
invocation = create_agent_invocation(
171+
SimpleNamespace(
172+
model=None,
173+
name="TestAgent",
174+
id="agent-id",
175+
sys_prompt=None,
176+
),
177+
tuple(),
178+
{},
179+
)
180+
181+
assert invocation.conversation_id == "agentscope-run-id"
182+
assert GEN_AI_SESSION_ID not in invocation.attributes
183+
assert GEN_AI_USER_ID not in invocation.attributes
184+
185+
def test_model_invocations_copy_entry_baggage_identity(self):
186+
ctx = baggage.set_baggage(GEN_AI_SESSION_ID, "entry-session")
187+
ctx = baggage.set_baggage(GEN_AI_USER_ID, "entry-user", ctx)
188+
token = otel_context.attach(ctx)
189+
try:
190+
llm_invocation = create_llm_invocation(
191+
SimpleNamespace(model_name="qwen-max"),
192+
tuple(),
193+
{},
194+
)
195+
embedding_invocation = create_embedding_invocation(
196+
SimpleNamespace(model_name="text-embedding-v4"),
197+
tuple(),
198+
{},
199+
)
200+
finally:
201+
otel_context.detach(token)
202+
203+
assert llm_invocation.conversation_id == "entry-session"
204+
assert llm_invocation.attributes[GEN_AI_SESSION_ID] == "entry-session"
205+
assert llm_invocation.attributes[GEN_AI_USER_ID] == "entry-user"
206+
assert (
207+
embedding_invocation.attributes[GEN_AI_SESSION_ID]
208+
== "entry-session"
209+
)
210+
assert embedding_invocation.attributes[GEN_AI_USER_ID] == "entry-user"
211+
212+
def test_react_step_invocation_copies_entry_baggage_identity(self):
213+
ctx = baggage.set_baggage(GEN_AI_SESSION_ID, "entry-session")
214+
ctx = baggage.set_baggage(GEN_AI_USER_ID, "entry-user", ctx)
215+
token = otel_context.attach(ctx)
216+
try:
217+
invocation = ReactStepInvocation(round=1)
218+
apply_entry_baggage_identity(invocation)
219+
finally:
220+
otel_context.detach(token)
221+
222+
assert invocation.attributes[GEN_AI_SESSION_ID] == "entry-session"
223+
assert invocation.attributes[GEN_AI_USER_ID] == "entry-user"

instrumentation-loongsuite/loongsuite-instrumentation-langchain/src/opentelemetry/instrumentation/langchain/internal/_tracer.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def _handle_llm_start(self, run: Run) -> None:
270270
rd = _RunData(
271271
run_kind="llm",
272272
span=invocation.span,
273-
context=set_span_in_context(invocation.span)
273+
context=otel_context.get_current()
274274
if invocation.span
275275
else None,
276276
invocation=invocation,
@@ -413,9 +413,7 @@ def _start_agent(self, run: Run) -> None:
413413
rd = _RunData(
414414
run_kind="agent",
415415
span=invocation.span,
416-
context=set_span_in_context(invocation.span)
417-
if invocation.span
418-
else None,
416+
context=otel_context.get_current() if invocation.span else None,
419417
invocation=invocation,
420418
is_langgraph_react=_has_langgraph_react_metadata(run),
421419
)
@@ -437,7 +435,12 @@ def _start_chain(self, run: Run) -> None:
437435
span.set_attribute(INPUT_VALUE, _safe_json(inputs))
438436

439437
# Attach chain span context so non-LangChain children nest correctly.
440-
ctx = set_span_in_context(span)
438+
current_context = (
439+
parent_ctx
440+
if parent_ctx is not None
441+
else otel_context.get_current()
442+
)
443+
ctx = set_span_in_context(span, current_context)
441444
token = otel_context.attach(ctx)
442445

443446
# Propagate inside_langgraph_react from parent so that
@@ -576,7 +579,7 @@ def _on_tool_start(self, run: Run) -> None:
576579
rd = _RunData(
577580
run_kind="tool",
578581
span=invocation.span,
579-
context=set_span_in_context(invocation.span)
582+
context=otel_context.get_current()
580583
if invocation.span
581584
else None,
582585
invocation=invocation,
@@ -634,7 +637,7 @@ def _on_retriever_start(self, run: Run) -> None:
634637
rd = _RunData(
635638
run_kind="retriever",
636639
span=invocation.span,
637-
context=set_span_in_context(invocation.span)
640+
context=otel_context.get_current()
638641
if invocation.span
639642
else None,
640643
invocation=invocation,
@@ -724,7 +727,7 @@ def _enter_react_step(self, agent_run_id: UUID) -> None:
724727
self._handler.start_react_step(inv, context=agent_rd.original_context)
725728

726729
step_ctx = (
727-
set_span_in_context(inv.span)
730+
otel_context.get_current()
728731
if inv.span
729732
else agent_rd.original_context
730733
)

0 commit comments

Comments
 (0)