agentx-python 0.6.8__tar.gz → 0.6.9__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.
- {agentx_python-0.6.8/agentx_python.egg-info → agentx_python-0.6.9}/PKG-INFO +1 -1
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/langchain.py +84 -62
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/tracer.py +7 -1
- agentx_python-0.6.9/agentx/version.py +1 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9/agentx_python.egg-info}/PKG-INFO +1 -1
- agentx_python-0.6.8/agentx/version.py +0 -1
- {agentx_python-0.6.8 → agentx_python-0.6.9}/LICENSE +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/README.md +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/agentx.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/client.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/evaluation_settings.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/models.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/redaction.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/reporting.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/runner.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/exceptions.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/_perf.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/anthropic.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/crewai.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/google_adk.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/google_genai.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/client.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/models.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/patterns.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/signals.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/py.typed +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/agent.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/ingest_client.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/util.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/SOURCES.txt +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/not-zip-safe +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/setup.cfg +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/setup.py +0 -0
- {agentx_python-0.6.8 → agentx_python-0.6.9}/tests/test_integration.py +0 -0
|
@@ -20,6 +20,7 @@ Requires: ``pip install "agentx-python[langchain]"``
|
|
|
20
20
|
"""
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
|
+
import threading
|
|
23
24
|
import time
|
|
24
25
|
from typing import Any, Dict, List, Optional, Union
|
|
25
26
|
from uuid import UUID
|
|
@@ -262,6 +263,13 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
262
263
|
self._pending_retrieval_steps: List[Dict[str, Any]] = []
|
|
263
264
|
# run_id → {"start": float, "query": str}
|
|
264
265
|
self._retrieval_starts: Dict[UUID, Dict[str, Any]] = {}
|
|
266
|
+
# Guards appends to a top-level run's shared aggregate lists (tool_calls,
|
|
267
|
+
# perf_tool_calls, execution_steps, retrieval_steps). LangGraph's ToolNode
|
|
268
|
+
# runs multiple tool calls from one AIMessage concurrently via a thread
|
|
269
|
+
# pool (see langgraph.prebuilt.tool_node.ToolNode._func), so on_tool_end /
|
|
270
|
+
# on_tool_error can fire from several threads at once for the same
|
|
271
|
+
# top-level run.
|
|
272
|
+
self._state_lock = threading.Lock()
|
|
265
273
|
|
|
266
274
|
# ------------------------------------------------------------------
|
|
267
275
|
# Chain lifecycle
|
|
@@ -311,14 +319,14 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
311
319
|
return
|
|
312
320
|
latency_ms = int((time.time() - state["start"]) * 1000)
|
|
313
321
|
output = _extract_output(outputs)
|
|
322
|
+
# Each tool_call dict already carries its own start_time/end_time (set in
|
|
323
|
+
# on_tool_end/on_tool_error), so no re-pairing against perf_tool_calls by
|
|
324
|
+
# index is needed here. That used to be done via zip(), which silently
|
|
325
|
+
# mispaired timestamps when LangGraph's ToolNode ran several tool calls
|
|
326
|
+
# from one AIMessage concurrently (see _state_lock's docstring): two
|
|
327
|
+
# lists appended to from different threads don't necessarily end up in
|
|
328
|
+
# the same relative order.
|
|
314
329
|
tool_calls = state["tool_calls"] or _extract_tool_calls_from_messages(outputs)
|
|
315
|
-
if state["tool_calls"] and len(state["tool_calls"]) == len(state["perf_tool_calls"]):
|
|
316
|
-
# Enrich with the timestamps perf_tool_calls tracked in lockstep,
|
|
317
|
-
# so tool calls interleave correctly in a merged span's timeline.
|
|
318
|
-
tool_calls = [
|
|
319
|
-
{**tc, "start_time": perf.get("start_time"), "end_time": perf.get("end_time")}
|
|
320
|
-
for tc, perf in zip(state["tool_calls"], state["perf_tool_calls"])
|
|
321
|
-
]
|
|
322
330
|
|
|
323
331
|
active_span = self._tracer.current_span
|
|
324
332
|
if active_span is not None:
|
|
@@ -332,6 +340,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
332
340
|
input=state["input"],
|
|
333
341
|
output=output,
|
|
334
342
|
model=state.get("model"),
|
|
343
|
+
framework="langchain",
|
|
335
344
|
input_tokens=state["input_tokens"] or None,
|
|
336
345
|
output_tokens=state["output_tokens"] or None,
|
|
337
346
|
)
|
|
@@ -383,6 +392,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
383
392
|
retrieval_steps=state["retrieval_steps"],
|
|
384
393
|
input=state["input"],
|
|
385
394
|
model=state.get("model"),
|
|
395
|
+
framework="langchain",
|
|
386
396
|
input_tokens=state["input_tokens"] or None,
|
|
387
397
|
output_tokens=state["output_tokens"] or None,
|
|
388
398
|
)
|
|
@@ -485,41 +495,42 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
485
495
|
# Extract token usage from LLMResult for this call
|
|
486
496
|
call_input_tokens: Optional[int] = None
|
|
487
497
|
call_output_tokens: Optional[int] = None
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
for
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
498
|
+
with self._state_lock:
|
|
499
|
+
if top and top in self._runs:
|
|
500
|
+
usage = {}
|
|
501
|
+
if hasattr(response, "llm_output") and isinstance(response.llm_output, dict):
|
|
502
|
+
usage = response.llm_output.get("token_usage") or response.llm_output.get("usage") or {}
|
|
503
|
+
# Also check generations for token counts (some providers put it there)
|
|
504
|
+
if not usage and hasattr(response, "generations"):
|
|
505
|
+
for gen_list in (response.generations or []):
|
|
506
|
+
for gen in (gen_list or []):
|
|
507
|
+
gen_info = getattr(gen, "generation_info", None) or {}
|
|
508
|
+
if gen_info.get("prompt_tokens") or gen_info.get("completion_tokens"):
|
|
509
|
+
usage = gen_info
|
|
510
|
+
break
|
|
511
|
+
if usage:
|
|
512
|
+
call_input_tokens = int(
|
|
513
|
+
usage.get("prompt_tokens") or usage.get("input_tokens") or usage.get("prompt_token_count") or 0
|
|
514
|
+
)
|
|
515
|
+
call_output_tokens = int(
|
|
516
|
+
usage.get("completion_tokens") or usage.get("output_tokens") or usage.get("candidates_token_count") or 0
|
|
517
|
+
)
|
|
518
|
+
self._runs[top]["input_tokens"] += call_input_tokens
|
|
519
|
+
self._runs[top]["output_tokens"] += call_output_tokens
|
|
520
|
+
|
|
521
|
+
if start_t is not None and top and top in self._runs:
|
|
522
|
+
steps = self._runs[top]["execution_steps"]
|
|
523
|
+
steps.append({
|
|
524
|
+
"name": f"LLM Call {len(steps) + 1}",
|
|
525
|
+
"duration_ms": (end_t - start_t) * 1000,
|
|
526
|
+
"start_time": start_t,
|
|
527
|
+
"end_time": end_t,
|
|
528
|
+
"model": llm_state.get("model"),
|
|
529
|
+
"input": llm_state.get("input"),
|
|
530
|
+
"output": _extract_llm_output(response),
|
|
531
|
+
"inputTokenSize": call_input_tokens,
|
|
532
|
+
"outputTokenSize": call_output_tokens,
|
|
533
|
+
})
|
|
523
534
|
|
|
524
535
|
# ------------------------------------------------------------------
|
|
525
536
|
# Tool lifecycle
|
|
@@ -561,18 +572,25 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
561
572
|
"input": state["tool_input"],
|
|
562
573
|
"output": str(output),
|
|
563
574
|
"latency_ms": latency_ms,
|
|
575
|
+
"success": True,
|
|
576
|
+
# Set directly on the tool_call dict (not just perf_tool_calls below) so
|
|
577
|
+
# on_chain_end's merged-span path doesn't need to re-pair the two lists by
|
|
578
|
+
# index later. See _state_lock's docstring for why that used to be unsafe.
|
|
579
|
+
"start_time": start_t,
|
|
580
|
+
"end_time": end_t,
|
|
564
581
|
}
|
|
565
582
|
top = self._find_top_ancestor(parent_run_id)
|
|
566
|
-
|
|
567
|
-
self._runs
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
583
|
+
with self._state_lock:
|
|
584
|
+
if top and top in self._runs:
|
|
585
|
+
self._runs[top]["tool_calls"].append(tool_call)
|
|
586
|
+
self._runs[top]["perf_tool_calls"].append({
|
|
587
|
+
"name": state["tool_name"],
|
|
588
|
+
"duration_ms": (end_t - start_t) * 1000,
|
|
589
|
+
"start_time": start_t,
|
|
590
|
+
"end_time": end_t,
|
|
591
|
+
"input": state["tool_input"],
|
|
592
|
+
"output": tool_call["output"],
|
|
593
|
+
})
|
|
576
594
|
|
|
577
595
|
def on_tool_error(
|
|
578
596
|
self,
|
|
@@ -593,18 +611,22 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
593
611
|
"input": state.get("tool_input"),
|
|
594
612
|
"output": f"ERROR: {error}",
|
|
595
613
|
"latency_ms": int((end_t - start_t) * 1000),
|
|
614
|
+
"success": False,
|
|
615
|
+
"start_time": start_t,
|
|
616
|
+
"end_time": end_t,
|
|
596
617
|
}
|
|
597
618
|
top = self._find_top_ancestor(parent_run_id)
|
|
598
|
-
|
|
599
|
-
self._runs
|
|
600
|
-
|
|
601
|
-
"
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
619
|
+
with self._state_lock:
|
|
620
|
+
if top and top in self._runs:
|
|
621
|
+
self._runs[top]["tool_calls"].append(tool_call)
|
|
622
|
+
self._runs[top]["perf_tool_calls"].append({
|
|
623
|
+
"name": state.get("tool_name", "unknown"),
|
|
624
|
+
"duration_ms": (end_t - start_t) * 1000,
|
|
625
|
+
"start_time": start_t,
|
|
626
|
+
"end_time": end_t,
|
|
627
|
+
"input": tool_call["input"],
|
|
628
|
+
"output": tool_call["output"],
|
|
629
|
+
})
|
|
608
630
|
|
|
609
631
|
# ------------------------------------------------------------------
|
|
610
632
|
# Retriever lifecycle
|
|
@@ -114,6 +114,9 @@ class _TraceSpan:
|
|
|
114
114
|
self._execution_steps: list = []
|
|
115
115
|
self._retrieval_steps: list = []
|
|
116
116
|
self._captured_model: Optional[str] = None
|
|
117
|
+
# Adopted from a merged child run (e.g. AgentXCallbackHandler) when this span itself
|
|
118
|
+
# wasn't opened with an explicit framework= — see _merge_child_run below.
|
|
119
|
+
self._captured_framework: Optional[str] = None
|
|
117
120
|
self._input_tokens: int = 0
|
|
118
121
|
self._output_tokens: int = 0
|
|
119
122
|
# Guards _merge_child_run — with Tracer.use_span(), multiple threads
|
|
@@ -186,7 +189,7 @@ class _TraceSpan:
|
|
|
186
189
|
latency_ms=latency_ms,
|
|
187
190
|
error=self._error,
|
|
188
191
|
metadata=self._metadata,
|
|
189
|
-
framework=self._framework,
|
|
192
|
+
framework=self._framework or self._captured_framework,
|
|
190
193
|
model=self._model or self._captured_model,
|
|
191
194
|
tool_calls=self.tool_calls or None,
|
|
192
195
|
session_id=self._session_id,
|
|
@@ -250,6 +253,7 @@ class _TraceSpan:
|
|
|
250
253
|
input: Any = None,
|
|
251
254
|
output: Any = None,
|
|
252
255
|
model: Optional[str] = None,
|
|
256
|
+
framework: Optional[str] = None,
|
|
253
257
|
input_tokens: Optional[int] = None,
|
|
254
258
|
output_tokens: Optional[int] = None,
|
|
255
259
|
) -> None:
|
|
@@ -281,6 +285,8 @@ class _TraceSpan:
|
|
|
281
285
|
self.output = output
|
|
282
286
|
if model and not self._captured_model:
|
|
283
287
|
self._captured_model = model
|
|
288
|
+
if framework and not self._captured_framework:
|
|
289
|
+
self._captured_framework = framework
|
|
284
290
|
if input_tokens:
|
|
285
291
|
self._input_tokens += input_tokens
|
|
286
292
|
if output_tokens:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.6.9"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.6.8"
|
|
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
|