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.
Files changed (55) hide show
  1. {agentx_python-0.6.8/agentx_python.egg-info → agentx_python-0.6.9}/PKG-INFO +1 -1
  2. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/langchain.py +84 -62
  3. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/tracer.py +7 -1
  4. agentx_python-0.6.9/agentx/version.py +1 -0
  5. {agentx_python-0.6.8 → agentx_python-0.6.9/agentx_python.egg-info}/PKG-INFO +1 -1
  6. agentx_python-0.6.8/agentx/version.py +0 -1
  7. {agentx_python-0.6.8 → agentx_python-0.6.9}/LICENSE +0 -0
  8. {agentx_python-0.6.8 → agentx_python-0.6.9}/README.md +0 -0
  9. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/__init__.py +0 -0
  10. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/agentx.py +0 -0
  11. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/__init__.py +0 -0
  12. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/_term.py +0 -0
  13. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/__init__.py +0 -0
  14. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/http_endpoint.py +0 -0
  15. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/precomputed.py +0 -0
  16. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/adapters/raw.py +0 -0
  17. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/client.py +0 -0
  18. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/datasets.py +0 -0
  19. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/evaluation_settings.py +0 -0
  20. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/models.py +0 -0
  21. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/redaction.py +0 -0
  22. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/reporting.py +0 -0
  23. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/results.py +0 -0
  24. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/runner.py +0 -0
  25. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/evaluations/tracing.py +0 -0
  26. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/exceptions.py +0 -0
  27. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/__init__.py +0 -0
  28. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/_perf.py +0 -0
  29. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/anthropic.py +0 -0
  30. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/crewai.py +0 -0
  31. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/google_adk.py +0 -0
  32. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/google_genai.py +0 -0
  33. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/integrations/openai_agents.py +0 -0
  34. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/__init__.py +0 -0
  35. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/client.py +0 -0
  36. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/models.py +0 -0
  37. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/patterns.py +0 -0
  38. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/monitor/signals.py +0 -0
  39. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/py.typed +0 -0
  40. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/__init__.py +0 -0
  41. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/agent.py +0 -0
  42. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/conversation.py +0 -0
  43. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/resources/workforce.py +0 -0
  44. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/__init__.py +0 -0
  45. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/ci_types.py +0 -0
  46. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/tracing/ingest_client.py +0 -0
  47. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx/util.py +0 -0
  48. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/SOURCES.txt +0 -0
  49. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/dependency_links.txt +0 -0
  50. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/not-zip-safe +0 -0
  51. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/requires.txt +0 -0
  52. {agentx_python-0.6.8 → agentx_python-0.6.9}/agentx_python.egg-info/top_level.txt +0 -0
  53. {agentx_python-0.6.8 → agentx_python-0.6.9}/setup.cfg +0 -0
  54. {agentx_python-0.6.8 → agentx_python-0.6.9}/setup.py +0 -0
  55. {agentx_python-0.6.8 → agentx_python-0.6.9}/tests/test_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -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
- if top and top in self._runs:
489
- usage = {}
490
- if hasattr(response, "llm_output") and isinstance(response.llm_output, dict):
491
- usage = response.llm_output.get("token_usage") or response.llm_output.get("usage") or {}
492
- # Also check generations for token counts (some providers put it there)
493
- if not usage and hasattr(response, "generations"):
494
- for gen_list in (response.generations or []):
495
- for gen in (gen_list or []):
496
- gen_info = getattr(gen, "generation_info", None) or {}
497
- if gen_info.get("prompt_tokens") or gen_info.get("completion_tokens"):
498
- usage = gen_info
499
- break
500
- if usage:
501
- call_input_tokens = int(
502
- usage.get("prompt_tokens") or usage.get("input_tokens") or usage.get("prompt_token_count") or 0
503
- )
504
- call_output_tokens = int(
505
- usage.get("completion_tokens") or usage.get("output_tokens") or usage.get("candidates_token_count") or 0
506
- )
507
- self._runs[top]["input_tokens"] += call_input_tokens
508
- self._runs[top]["output_tokens"] += call_output_tokens
509
-
510
- if start_t is not None and top and top in self._runs:
511
- steps = self._runs[top]["execution_steps"]
512
- steps.append({
513
- "name": f"LLM Call {len(steps) + 1}",
514
- "duration_ms": (end_t - start_t) * 1000,
515
- "start_time": start_t,
516
- "end_time": end_t,
517
- "model": llm_state.get("model"),
518
- "input": llm_state.get("input"),
519
- "output": _extract_llm_output(response),
520
- "inputTokenSize": call_input_tokens,
521
- "outputTokenSize": call_output_tokens,
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
- if top and top in self._runs:
567
- self._runs[top]["tool_calls"].append(tool_call)
568
- self._runs[top]["perf_tool_calls"].append({
569
- "name": state["tool_name"],
570
- "duration_ms": (end_t - start_t) * 1000,
571
- "start_time": start_t,
572
- "end_time": end_t,
573
- "input": state["tool_input"],
574
- "output": tool_call["output"],
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
- if top and top in self._runs:
599
- self._runs[top]["tool_calls"].append(tool_call)
600
- self._runs[top]["perf_tool_calls"].append({
601
- "name": state.get("tool_name", "unknown"),
602
- "duration_ms": (end_t - start_t) * 1000,
603
- "start_time": start_t,
604
- "end_time": end_t,
605
- "input": tool_call["input"],
606
- "output": tool_call["output"],
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -1 +0,0 @@
1
- VERSION = "0.6.8"
File without changes
File without changes
File without changes
File without changes