trodo-python 2.18.0__tar.gz → 2.18.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {trodo_python-2.18.0 → trodo_python-2.18.1}/PKG-INFO +1 -1
- {trodo_python-2.18.0 → trodo_python-2.18.1}/pyproject.toml +1 -1
- trodo_python-2.18.1/tests/test_multi_agent_shape.py +106 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/__init__.py +14 -1
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/client.py +4 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo_python.egg-info/PKG-INFO +1 -1
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo_python.egg-info/SOURCES.txt +1 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/README.md +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/setup.cfg +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_anon_distinct_id.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_auto_instrument_fixes.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_cross_process_session.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_datasets.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_end_run.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_error_enrichment.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_experiments.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_llm_usage_cost.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_processor_methods.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_prompt_trace.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_prompts.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_register_otel.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_start_run.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_user_traits.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/tests/test_wrap_agent_unchanged.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/api/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/api/async_client.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/api/endpoints.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/api/http_client.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/auto/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/auto/auto_event_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/dataset_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/experiment_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/group_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/people_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/prompt_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/managers/user_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/auto_instrument.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/context.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/helpers.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/processor.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/prompt_trace.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/register.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/transport.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/otel/wrap_agent.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/prompts/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/prompts/compile.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/prompts/template.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/prompts/types.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/queue/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/queue/batch_flusher.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/queue/event_queue.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/session/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/session/server_session.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/session/session_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/types.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/user_context.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/util/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo/util/lru.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.1}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Multi-agent run shape — one run, sub-agents as ``agent``-kind child spans.
|
|
2
|
+
|
|
3
|
+
The shape the ``trodo-tracing`` skill teaches: a request that fans out to
|
|
4
|
+
sub-agents produces ONE run whose tree carries an ``agent`` span per sub-agent,
|
|
5
|
+
with each sub-agent's own work nested underneath it. The failure mode this
|
|
6
|
+
guards against is a ``wrap_agent`` per sub-agent, which shatters one request
|
|
7
|
+
into N disconnected runs and loses whole-request cost, latency and the
|
|
8
|
+
delegation tree.
|
|
9
|
+
|
|
10
|
+
Also covers the module-level ``user`` / ``parent_run_id`` kwargs, which the
|
|
11
|
+
skill's guidance writes and which the top-level wrappers used to silently drop.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import inspect
|
|
17
|
+
|
|
18
|
+
import trodo
|
|
19
|
+
from trodo.otel.wrap_agent import span as span_ctx
|
|
20
|
+
from trodo.otel.wrap_agent import wrap_agent as wrap_agent_ctx
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _tree(payload):
|
|
24
|
+
"""{span_id: (name, kind, parent_span_id)} for one ingested run payload."""
|
|
25
|
+
return {s["span_id"]: (s["name"], s["kind"], s["parent_span_id"]) for s in payload["spans"]}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_sub_agents_nest_as_agent_spans_in_one_run(processor, http):
|
|
29
|
+
"""A supervisor with two workers and a writer = 1 run, 3 agent spans."""
|
|
30
|
+
with wrap_agent_ctx(
|
|
31
|
+
processor=processor, team_site_id="site", agent_name="research_assistant",
|
|
32
|
+
distinct_id="u_1", conversation_id="thread_1",
|
|
33
|
+
) as run:
|
|
34
|
+
run.set_input([{"role": "user", "content": "why did revenue drop"}])
|
|
35
|
+
|
|
36
|
+
with span_ctx("planner", kind="agent") as s:
|
|
37
|
+
s.set_output({"subtasks": ["pricing", "churn"]})
|
|
38
|
+
|
|
39
|
+
for topic in ("pricing", "churn"):
|
|
40
|
+
with span_ctx(f"researcher:{topic}", kind="agent") as s:
|
|
41
|
+
s.set_attribute("topic", topic)
|
|
42
|
+
# The sub-agent's own tool call nests under the sub-agent, not the run.
|
|
43
|
+
with span_ctx("web_search", kind="tool") as t:
|
|
44
|
+
t.set_tool("web_search")
|
|
45
|
+
t.set_output({"hits": 3})
|
|
46
|
+
s.set_output({"topic": topic})
|
|
47
|
+
|
|
48
|
+
run.set_output({"answer": "pricing change in March"})
|
|
49
|
+
|
|
50
|
+
# Exactly ONE run — not one per sub-agent.
|
|
51
|
+
assert len(http.run_ingest) == 1
|
|
52
|
+
payload = http.run_ingest[0]
|
|
53
|
+
assert payload["run"]["agent_name"] == "research_assistant"
|
|
54
|
+
assert payload["run"]["conversation_id"] == "thread_1"
|
|
55
|
+
|
|
56
|
+
tree = _tree(payload)
|
|
57
|
+
by_name = {v[0]: (sid, v[1], v[2]) for sid, v in tree.items()}
|
|
58
|
+
|
|
59
|
+
# Three agent-kind sub-agent spans, all siblings directly under the run root.
|
|
60
|
+
parents = set()
|
|
61
|
+
for name in ("planner", "researcher:pricing", "researcher:churn"):
|
|
62
|
+
sid, kind, parent = by_name[name]
|
|
63
|
+
assert kind == "agent", f"{name} should be an agent span, got {kind}"
|
|
64
|
+
parents.add(parent)
|
|
65
|
+
assert len(parents) == 1, "sub-agents should be siblings under one run root"
|
|
66
|
+
|
|
67
|
+
# Each tool span nests under its own researcher, preserving the delegation tree.
|
|
68
|
+
tool_parents = {v[2] for sid, v in tree.items() if v[1] == "tool"}
|
|
69
|
+
researcher_ids = {by_name[n][0] for n in ("researcher:pricing", "researcher:churn")}
|
|
70
|
+
assert tool_parents == researcher_ids
|
|
71
|
+
|
|
72
|
+
# The run rolls up the whole request, which is the point of keeping it one run.
|
|
73
|
+
assert payload["run"]["span_count"] == 5
|
|
74
|
+
assert payload["run"]["tool_count"] == 2
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_independently_triggered_sub_agent_links_via_parent_run_id(processor, http):
|
|
78
|
+
"""The other shape: separate runs, linked — for independently-triggered work."""
|
|
79
|
+
with wrap_agent_ctx(
|
|
80
|
+
processor=processor, team_site_id="site", agent_name="intake", distinct_id="u_1",
|
|
81
|
+
) as parent:
|
|
82
|
+
parent.set_output({"job": "enrich"})
|
|
83
|
+
|
|
84
|
+
with wrap_agent_ctx(
|
|
85
|
+
processor=processor, team_site_id="site", agent_name="enrichment",
|
|
86
|
+
distinct_id="u_1", parent_run_id=parent.run_id,
|
|
87
|
+
) as child:
|
|
88
|
+
child.set_output({"done": True})
|
|
89
|
+
|
|
90
|
+
assert len(http.run_ingest) == 2
|
|
91
|
+
assert http.run_ingest[1]["run"]["parent_run_id"] == parent.run_id
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_module_level_wrappers_forward_user_and_parent_run_id():
|
|
95
|
+
"""``trodo.wrap_agent(..., user=...)`` is what the skill tells people to write.
|
|
96
|
+
|
|
97
|
+
The module-level wrappers used to drop ``user`` on the floor (and ``agent``
|
|
98
|
+
also dropped ``parent_run_id``), so the documented call raised TypeError.
|
|
99
|
+
"""
|
|
100
|
+
for fn, expected in (
|
|
101
|
+
(trodo.wrap_agent, {"user", "parent_run_id", "conversation_id", "metadata"}),
|
|
102
|
+
(trodo.agent, {"user", "parent_run_id", "conversation_id", "metadata"}),
|
|
103
|
+
(trodo.start_run, {"user", "parent_run_id", "conversation_id", "metadata", "input"}),
|
|
104
|
+
):
|
|
105
|
+
params = set(inspect.signature(fn).parameters)
|
|
106
|
+
assert expected <= params, f"{fn.__name__} is missing {expected - params}"
|
|
@@ -524,14 +524,21 @@ def wrap_agent(
|
|
|
524
524
|
conversation_id: Optional[str] = None,
|
|
525
525
|
parent_run_id: Optional[str] = None,
|
|
526
526
|
metadata: Optional[Dict[str, Any]] = None,
|
|
527
|
+
user: Optional[Dict[str, Any]] = None,
|
|
527
528
|
) -> _wrap_agent_ctx:
|
|
528
|
-
"""Wrap a block as an agent run — returns a context manager yielding RunHandle.
|
|
529
|
+
"""Wrap a block as an agent run — returns a context manager yielding RunHandle.
|
|
530
|
+
|
|
531
|
+
``user`` carries profile traits alongside the run — ``{"properties": ...,
|
|
532
|
+
"set_once": ..., "fixed_properties": ...}`` — so a request handler can
|
|
533
|
+
attribute the run and enrich the profile in one round trip.
|
|
534
|
+
"""
|
|
529
535
|
return _get_client().wrap_agent(
|
|
530
536
|
agent_name,
|
|
531
537
|
distinct_id=distinct_id,
|
|
532
538
|
conversation_id=conversation_id,
|
|
533
539
|
parent_run_id=parent_run_id,
|
|
534
540
|
metadata=metadata,
|
|
541
|
+
user=user,
|
|
535
542
|
)
|
|
536
543
|
|
|
537
544
|
|
|
@@ -540,14 +547,18 @@ def agent(
|
|
|
540
547
|
*,
|
|
541
548
|
distinct_id: Optional[str] = None,
|
|
542
549
|
conversation_id: Optional[str] = None,
|
|
550
|
+
parent_run_id: Optional[str] = None,
|
|
543
551
|
metadata: Optional[Dict[str, Any]] = None,
|
|
552
|
+
user: Optional[Dict[str, Any]] = None,
|
|
544
553
|
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
545
554
|
"""Decorator form of wrap_agent."""
|
|
546
555
|
return _get_client().agent(
|
|
547
556
|
agent_name,
|
|
548
557
|
distinct_id=distinct_id,
|
|
549
558
|
conversation_id=conversation_id,
|
|
559
|
+
parent_run_id=parent_run_id,
|
|
550
560
|
metadata=metadata,
|
|
561
|
+
user=user,
|
|
551
562
|
)
|
|
552
563
|
|
|
553
564
|
|
|
@@ -591,6 +602,7 @@ def start_run(
|
|
|
591
602
|
parent_run_id: Optional[str] = None,
|
|
592
603
|
metadata: Optional[Dict[str, Any]] = None,
|
|
593
604
|
input: Any = None,
|
|
605
|
+
user: Optional[Dict[str, Any]] = None,
|
|
594
606
|
) -> str:
|
|
595
607
|
"""Open a Run record without a context manager.
|
|
596
608
|
|
|
@@ -607,6 +619,7 @@ def start_run(
|
|
|
607
619
|
parent_run_id=parent_run_id,
|
|
608
620
|
metadata=metadata,
|
|
609
621
|
input=input,
|
|
622
|
+
user=user,
|
|
610
623
|
)
|
|
611
624
|
|
|
612
625
|
|
|
@@ -357,7 +357,9 @@ class TrodoClient:
|
|
|
357
357
|
*,
|
|
358
358
|
distinct_id: Optional[str] = None,
|
|
359
359
|
conversation_id: Optional[str] = None,
|
|
360
|
+
parent_run_id: Optional[str] = None,
|
|
360
361
|
metadata: Optional[Dict[str, Any]] = None,
|
|
362
|
+
user: Optional[Dict[str, Any]] = None,
|
|
361
363
|
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
362
364
|
"""Decorator form of wrap_agent."""
|
|
363
365
|
|
|
@@ -368,7 +370,9 @@ class TrodoClient:
|
|
|
368
370
|
agent_name,
|
|
369
371
|
distinct_id=distinct_id,
|
|
370
372
|
conversation_id=conversation_id,
|
|
373
|
+
parent_run_id=parent_run_id,
|
|
371
374
|
metadata=metadata,
|
|
375
|
+
user=user,
|
|
372
376
|
) as run:
|
|
373
377
|
result = fn(*args, **kwargs)
|
|
374
378
|
run.set_output(result)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|