trodo-python 2.18.0__tar.gz → 2.18.2__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.2}/PKG-INFO +1 -1
- {trodo_python-2.18.0 → trodo_python-2.18.2}/pyproject.toml +1 -1
- trodo_python-2.18.2/tests/test_get_prompt_version_types.py +54 -0
- trodo_python-2.18.2/tests/test_multi_agent_shape.py +106 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/__init__.py +18 -3
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/client.py +4 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/PKG-INFO +1 -1
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/SOURCES.txt +2 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/README.md +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/setup.cfg +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_anon_distinct_id.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_auto_instrument_fixes.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_cross_process_session.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_datasets.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_end_run.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_error_enrichment.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_experiments.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_llm_usage_cost.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_processor_methods.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_prompt_trace.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_prompts.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_register_otel.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_start_run.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_user_traits.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/tests/test_wrap_agent_unchanged.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/async_client.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/endpoints.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/api/http_client.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/auto/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/auto/auto_event_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/dataset_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/experiment_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/group_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/people_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/prompt_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/managers/user_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/auto_instrument.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/context.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/helpers.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/processor.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/prompt_trace.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/register.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/transport.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/otel/wrap_agent.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/compile.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/template.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/prompts/types.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/queue/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/queue/batch_flusher.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/queue/event_queue.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/session/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/session/server_session.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/session/session_manager.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/types.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/user_context.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/util/__init__.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo/util/lru.py +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/dependency_links.txt +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/requires.txt +0 -0
- {trodo_python-2.18.0 → trodo_python-2.18.2}/trodo_python.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""The public get_prompt must accept a version_hash, not just an int.
|
|
2
|
+
|
|
3
|
+
The manager has always taken ``Union[int, str]`` and documents hash support —
|
|
4
|
+
the hash is the stable identity the dashboard shows and the SDK returns. But
|
|
5
|
+
the top-level ``trodo.get_prompt`` wrapper narrowed the hint to ``int``, so a
|
|
6
|
+
type checker flagged the documented usage, and the dashboard's own "Use this
|
|
7
|
+
prompt" snippet emits ``version="<hash>"`` for Python.
|
|
8
|
+
|
|
9
|
+
Checked by signature rather than by a network call: the wrapper only forwards,
|
|
10
|
+
so the contract IS the signature.
|
|
11
|
+
"""
|
|
12
|
+
import inspect
|
|
13
|
+
import typing
|
|
14
|
+
|
|
15
|
+
import trodo
|
|
16
|
+
from trodo.managers.prompt_manager import PromptManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _version_annotation(fn):
|
|
20
|
+
return typing.get_type_hints(fn)["version"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_public_get_prompt_accepts_int_or_str():
|
|
24
|
+
ann = _version_annotation(trodo.get_prompt)
|
|
25
|
+
args = typing.get_args(ann)
|
|
26
|
+
assert int in args, f"version should accept int, got {ann}"
|
|
27
|
+
assert str in args, f"version should accept a version_hash (str), got {ann}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_matches_the_manager_it_forwards_to():
|
|
31
|
+
assert _version_annotation(trodo.get_prompt) == _version_annotation(PromptManager.get)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_forwards_version_through_untouched():
|
|
35
|
+
"""A hash must reach the manager as-is — not coerced, not dropped."""
|
|
36
|
+
seen = {}
|
|
37
|
+
|
|
38
|
+
class _Stub:
|
|
39
|
+
def get(self, name, **kwargs):
|
|
40
|
+
seen.update(kwargs, name=name)
|
|
41
|
+
return "sentinel"
|
|
42
|
+
|
|
43
|
+
class _Client:
|
|
44
|
+
prompts = _Stub()
|
|
45
|
+
|
|
46
|
+
original = trodo._get_client
|
|
47
|
+
trodo._get_client = lambda: _Client()
|
|
48
|
+
try:
|
|
49
|
+
assert trodo.get_prompt("p", version="a3f9c2") == "sentinel"
|
|
50
|
+
finally:
|
|
51
|
+
trodo._get_client = original
|
|
52
|
+
|
|
53
|
+
assert seen["version"] == "a3f9c2"
|
|
54
|
+
assert seen["label"] is None
|
|
@@ -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}"
|
|
@@ -322,7 +322,7 @@ def reset(distinct_id: str) -> ResetResult:
|
|
|
322
322
|
def get_prompt(
|
|
323
323
|
name: str,
|
|
324
324
|
label: Optional[str] = None,
|
|
325
|
-
version: Optional[int] = None,
|
|
325
|
+
version: Optional[Union[int, str]] = None,
|
|
326
326
|
cache_ttl_seconds: Optional[float] = None,
|
|
327
327
|
fallback: Optional[Dict[str, Any]] = None,
|
|
328
328
|
max_retries: int = 2,
|
|
@@ -330,7 +330,9 @@ def get_prompt(
|
|
|
330
330
|
"""Fetch a managed prompt by name.
|
|
331
331
|
|
|
332
332
|
Follows the ``production`` label by default; pass ``label`` for a different
|
|
333
|
-
one or ``version`` to pin exactly.
|
|
333
|
+
one or ``version`` to pin exactly. ``version`` may be the integer
|
|
334
|
+
``version_no`` or a ``version_hash`` (full, or an unambiguous short prefix
|
|
335
|
+
like ``a3f9c2``) — the hash is the stable identity, so prefer it.
|
|
334
336
|
|
|
335
337
|
Cached for 60s with stale-while-revalidate, so a Trodo outage degrades
|
|
336
338
|
instead of taking your app down. Pass ``fallback`` to cover cold start::
|
|
@@ -524,14 +526,21 @@ def wrap_agent(
|
|
|
524
526
|
conversation_id: Optional[str] = None,
|
|
525
527
|
parent_run_id: Optional[str] = None,
|
|
526
528
|
metadata: Optional[Dict[str, Any]] = None,
|
|
529
|
+
user: Optional[Dict[str, Any]] = None,
|
|
527
530
|
) -> _wrap_agent_ctx:
|
|
528
|
-
"""Wrap a block as an agent run — returns a context manager yielding RunHandle.
|
|
531
|
+
"""Wrap a block as an agent run — returns a context manager yielding RunHandle.
|
|
532
|
+
|
|
533
|
+
``user`` carries profile traits alongside the run — ``{"properties": ...,
|
|
534
|
+
"set_once": ..., "fixed_properties": ...}`` — so a request handler can
|
|
535
|
+
attribute the run and enrich the profile in one round trip.
|
|
536
|
+
"""
|
|
529
537
|
return _get_client().wrap_agent(
|
|
530
538
|
agent_name,
|
|
531
539
|
distinct_id=distinct_id,
|
|
532
540
|
conversation_id=conversation_id,
|
|
533
541
|
parent_run_id=parent_run_id,
|
|
534
542
|
metadata=metadata,
|
|
543
|
+
user=user,
|
|
535
544
|
)
|
|
536
545
|
|
|
537
546
|
|
|
@@ -540,14 +549,18 @@ def agent(
|
|
|
540
549
|
*,
|
|
541
550
|
distinct_id: Optional[str] = None,
|
|
542
551
|
conversation_id: Optional[str] = None,
|
|
552
|
+
parent_run_id: Optional[str] = None,
|
|
543
553
|
metadata: Optional[Dict[str, Any]] = None,
|
|
554
|
+
user: Optional[Dict[str, Any]] = None,
|
|
544
555
|
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
545
556
|
"""Decorator form of wrap_agent."""
|
|
546
557
|
return _get_client().agent(
|
|
547
558
|
agent_name,
|
|
548
559
|
distinct_id=distinct_id,
|
|
549
560
|
conversation_id=conversation_id,
|
|
561
|
+
parent_run_id=parent_run_id,
|
|
550
562
|
metadata=metadata,
|
|
563
|
+
user=user,
|
|
551
564
|
)
|
|
552
565
|
|
|
553
566
|
|
|
@@ -591,6 +604,7 @@ def start_run(
|
|
|
591
604
|
parent_run_id: Optional[str] = None,
|
|
592
605
|
metadata: Optional[Dict[str, Any]] = None,
|
|
593
606
|
input: Any = None,
|
|
607
|
+
user: Optional[Dict[str, Any]] = None,
|
|
594
608
|
) -> str:
|
|
595
609
|
"""Open a Run record without a context manager.
|
|
596
610
|
|
|
@@ -607,6 +621,7 @@ def start_run(
|
|
|
607
621
|
parent_run_id=parent_run_id,
|
|
608
622
|
metadata=metadata,
|
|
609
623
|
input=input,
|
|
624
|
+
user=user,
|
|
610
625
|
)
|
|
611
626
|
|
|
612
627
|
|
|
@@ -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)
|
|
@@ -7,7 +7,9 @@ tests/test_datasets.py
|
|
|
7
7
|
tests/test_end_run.py
|
|
8
8
|
tests/test_error_enrichment.py
|
|
9
9
|
tests/test_experiments.py
|
|
10
|
+
tests/test_get_prompt_version_types.py
|
|
10
11
|
tests/test_llm_usage_cost.py
|
|
12
|
+
tests/test_multi_agent_shape.py
|
|
11
13
|
tests/test_processor_methods.py
|
|
12
14
|
tests/test_prompt_trace.py
|
|
13
15
|
tests/test_prompts.py
|
|
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
|