agentx-dev 3.3.1__tar.gz → 3.4.0__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 (73) hide show
  1. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/CHANGELOG.md +65 -0
  2. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/PKG-INFO +64 -3
  3. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/README.md +63 -2
  4. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Agents/__init__.py +10 -10
  5. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/AsyncTools.py +160 -160
  6. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/AutoSetup.py +164 -164
  7. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Cache.py +471 -471
  8. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/ChatModel.py +1910 -1583
  9. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Config.py +144 -144
  10. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/DefaultTools.py +2053 -2026
  11. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Embeddings.py +681 -681
  12. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Evals.py +514 -514
  13. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Handoffs.py +420 -420
  14. agentx_dev-3.4.0/agentx_dev/Media.py +500 -0
  15. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Memory.py +474 -474
  16. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Observability.py +494 -494
  17. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/AgentRun.py +61 -12
  18. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/AsyncAgentRun.py +12 -4
  19. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/__init__.py +13 -13
  20. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/promptTemplate.yaml +139 -139
  21. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Streaming.py +297 -297
  22. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Tools.py +246 -246
  23. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/qdrant_store.py +239 -239
  24. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/WebTools.py +501 -501
  25. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/__init__.py +4 -0
  26. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/resources/__init__.py +3 -3
  27. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/PKG-INFO +64 -3
  28. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/SOURCES.txt +1 -0
  29. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/pyproject.toml +1 -1
  30. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/AGENTX.md +0 -0
  31. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/CONTRIBUTING.md +0 -0
  32. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/LICENSE +0 -0
  33. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/MANIFEST.in +0 -0
  34. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Agents/Agent.py +0 -0
  35. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Compiler.py +0 -0
  36. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Loader.py +0 -0
  37. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/MCP.py +0 -0
  38. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Planner.py +0 -0
  39. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Session.py +0 -0
  40. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Splitters.py +0 -0
  41. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Supervisor.py +0 -0
  42. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/__init__.py +0 -0
  43. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/chroma_store.py +0 -0
  44. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/pg_store.py +0 -0
  45. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/resources/promptTemplate.yaml +0 -0
  46. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/dependency_links.txt +0 -0
  47. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/requires.txt +0 -0
  48. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/top_level.txt +0 -0
  49. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/README.md +0 -0
  50. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/agentic_rag_demo.py +0 -0
  51. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/async_example.py +0 -0
  52. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/async_quickstart.py +0 -0
  53. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/auto_features_example.py +0 -0
  54. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/caching_example.py +0 -0
  55. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/chatbot_example.py +0 -0
  56. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/complete_example.py +0 -0
  57. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/concurrent_example.py +0 -0
  58. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/concurrent_tool_example.py +0 -0
  59. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/file_agent_demo.py +0 -0
  60. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/function_calling_demo.py +0 -0
  61. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/mcp_demo.py +0 -0
  62. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/mcp_github_triage_demo.py +0 -0
  63. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/observability_example.py +0 -0
  64. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/orchestration_demo.py +0 -0
  65. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/planner_example.py +0 -0
  66. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/robust_link_scraper.py +0 -0
  67. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/supervisor_codebase_analysis_demo.py +0 -0
  68. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/supervisor_example.py +0 -0
  69. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/sync_quickstart.py +0 -0
  70. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/v3_1_1_features_demo.py +0 -0
  71. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/v3_1_comprehensive_demo.py +0 -0
  72. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/v3_1_features_demo.py +0 -0
  73. {agentx_dev-3.3.1 → agentx_dev-3.4.0}/setup.cfg +0 -0
@@ -4,6 +4,71 @@ All notable changes to `agentx-dev` are documented here. Format loosely
4
4
  follows [Keep a Changelog](https://keepachangelog.com/); versioning is
5
5
  [Semver](https://semver.org/).
6
6
 
7
+ ## [3.4.0] - 2026-09-18
8
+
9
+ Media input for GPT and Claude, models that adapt to each generation's
10
+ parameter support, and workspace-rooted paths. Non-breaking; two
11
+ defaults changed -- see docs/guides/upgrading.md.
12
+
13
+ ### Added
14
+
15
+ - **Media input for GPT and Claude.** New `Media` type
16
+ (`Media.image()`, `.document()`, `.audio()`, `.from_path()`,
17
+ `.from_url()`, `.from_bytes()`) and a `media=[...]` argument on
18
+ `AgentRunner.invoke` / `stream` and `AsyncAgentRunner.ainvoke` /
19
+ `astream`. Items can be paths, URLs, `Media`, or content-part dicts
20
+ in either provider's native shape; each model renders them in its
21
+ own wire format (OpenAI `image_url` / `file` / `input_audio`,
22
+ Anthropic `image` / `document`). Unsupported combinations -- audio to
23
+ Claude, a document URL to GPT -- raise `ValueError` before any
24
+ request. Media is stored as plain JSON dicts, so `completion.history`
25
+ and `Session.save()` keep working. Guide: docs/guides/media.md.
26
+
27
+ - **Models adapt to per-generation parameter support.** `GPT` and
28
+ `Claude` read a parameter-compatibility 400 (which names the
29
+ parameter and, for enums, the allowed values), make the smallest
30
+ change it asks for, retry, and remember it per model: an unsupported
31
+ `reasoning_effort` moves to the nearest supported value, `max_tokens`
32
+ becomes `max_completion_tokens` on reasoning models (up front for
33
+ known families), parameters a model lacks are dropped, and Claude's
34
+ `max_tokens` is clamped to the model's cap. Logged at WARNING; other
35
+ 400s raise unchanged. `adapt_params=False` opts out.
36
+
37
+ - **`Claude` gains `top_p`, `top_k`, `thinking`, `stop_sequences`.**
38
+
39
+ ### Changed
40
+
41
+ - `Claude(temperature=)` defaults to `None` (not sent; the API default is
42
+ the old 1.0). Always sending it conflicted with `top_p` on newer models
43
+ and with extended thinking.
44
+ - `GPT(reasoning_effort=)` accepts any string, not just
45
+ `none | low | medium | high`, so `minimal` and `xhigh` are expressible.
46
+
47
+ ### Fixed
48
+
49
+ - **`reasoning_effort="none"` crashed on models that don't take it**
50
+ ("Unsupported value: 'reasoning_effort' does not support 'none' with
51
+ this model"). The models docs even recommended `"none"` as a fix for
52
+ tool-calling conflicts; that advice is gone.
53
+ - **Workspace-rooted paths resolved to the drive root.** With a workspace
54
+ set, `/bruce.jpeg` became `C:\bruce.jpeg` on Windows (a leading slash
55
+ with no drive means the current drive's root) and the sandbox rejected
56
+ it. A leading slash now means the workspace root; the re-rooted path
57
+ is still sandbox-checked, so traversal is rejected as before.
58
+ - **`run_python` ran in the host process's directory, not the
59
+ workspace.** It passed no `cwd`, so `open("bruce.jpeg")` looked
60
+ wherever the program was launched from. It now starts in the workspace,
61
+ matching `run_shell`.
62
+ - **Message-list input stringified media into the prompt.** A user turn
63
+ whose content was a list (text + image) went through `str()`, so an
64
+ attached image reached the model as base64 text. Text and media are
65
+ now split, and earlier turns keep their list content.
66
+ - **Claude returned the wrong block with extended thinking.** It read
67
+ `response.content[0].text`; with thinking the first block is a
68
+ `thinking` block. Text blocks are now joined.
69
+ - `GPT.Initialize` / `stream_text` / `astream_text` now run messages
70
+ through the same OpenAI translator as `call_with_tools`.
71
+
7
72
  ## [3.3.1] - 2026-09-09
8
73
 
9
74
  Correctness fixes in the agent loop, found by driving it with a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-dev
3
- Version: 3.3.1
3
+ Version: 3.4.0
4
4
  Summary: A production-grade Python framework for building LLM agents. Multi-provider chat, permission-gated tools, RAG (in-mem + Chroma/Qdrant/pgvector), semantic memory, Supervisor + Handoffs multi-agent orchestration with streaming, evals harness, prompt caching, Batch API, prompt-optimizer, MCP integration.
5
5
  Author-email: Bruce-Arhin Shadrach <brucearhin098@gmail.com>
6
6
  License: MIT License
@@ -73,6 +73,32 @@ you can paste and run.
73
73
 
74
74
  ---
75
75
 
76
+ ## What's new in 3.4 — media input and models that adapt
77
+
78
+ Send images, PDFs and audio to GPT or Claude with one type. The model
79
+ classes now adjust themselves to whatever each model generation accepts,
80
+ and sandboxed file paths behave the way you'd write them. Nothing
81
+ breaks — see [Upgrading](docs/guides/upgrading.md).
82
+
83
+ | Feature | What you get |
84
+ |---|---|
85
+ | **Media input** | `runner.invoke("Describe this", media=["photo.jpg", "report.pdf"])`. Paths, URLs, bytes or `Media` objects. The same code works on `GPT()` and `Claude()`: each model gets its own format, and either provider's native part shape is accepted too. Unsupported pairs — audio to Claude, a PDF URL to GPT — fail fast with a clear `ValueError`. [Guide](docs/guides/media.md). |
86
+ | **Models that adapt** | Parameter support changes between model generations — `reasoning_effort`'s allowed values, `max_tokens` vs `max_completion_tokens`, Claude's `temperature` + `top_p` conflict. When a model rejects a parameter, `GPT` and `Claude` make the smallest change the error asks for (`'none'` → `'low'`), retry, and remember it for that model. Logged at WARNING; `adapt_params=False` opts out. [Details](docs/concepts/models.md#old-and-new-models). |
87
+ | **More Claude settings** | `Claude(top_p=, top_k=, thinking=, stop_sequences=)`. `temperature` is only sent when you set it. |
88
+ | **Workspace paths** | With a workspace set, `/notes.txt` means the workspace's `notes.txt`, not `C:\notes.txt`. `run_python` now starts in the workspace, so `open("notes.txt")` finds it. |
89
+
90
+ ### Upgrade notes
91
+
92
+ - `Claude(temperature=)` now defaults to unset. The API default is the
93
+ old `1.0`, so output is the same.
94
+ - A parameter a model rejects is adjusted with a WARNING instead of
95
+ raising. Use `adapt_params=False` if you'd rather it fail.
96
+ - `run_python` starts in the workspace, not the launch directory.
97
+ - Drop any `reasoning_effort="none"` workarounds. Earlier docs
98
+ recommended it, and it's the value newer models reject.
99
+
100
+ Full notes: [docs/guides/upgrading.md](docs/guides/upgrading.md).
101
+
76
102
  ## What's new in 3.3 — dependency DAGs for the Supervisor
77
103
 
78
104
  Plans stopped being flat lists. A step declares what it consumes, and
@@ -922,6 +948,35 @@ guarantees a crashed save can't corrupt an existing valid file.
922
948
 
923
949
  ---
924
950
 
951
+ ## Send images, PDFs and audio
952
+
953
+ ```python
954
+ from agentx_dev import AgentRunner, AgentType, Claude, Media
955
+
956
+ runner = AgentRunner(model=Claude(), agent=AgentType.ReAct, tools=[])
957
+
958
+ result = runner.invoke(
959
+ "What does this chart show, and does the report agree?",
960
+ media=["q3_chart.png", "q3_report.pdf"],
961
+ )
962
+ ```
963
+
964
+ Swap in `GPT(model="gpt-4o")` and nothing else changes. You can also put
965
+ media straight into a message for a bare model call:
966
+
967
+ ```python
968
+ llm.invoke([{"role": "user", "content": ["What's in this photo?", Media.image("cat.jpg")]}])
969
+ ```
970
+
971
+ | | GPT | Claude |
972
+ |---|---|---|
973
+ | image (file or URL) | yes | yes |
974
+ | PDF | file | file or URL |
975
+ | audio (wav / mp3) | audio models | no — `ValueError` |
976
+
977
+ Paths are relative to where your program runs, not the agent's
978
+ workspace. See [Media](docs/guides/media.md).
979
+
925
980
  ## Get a typed result back
926
981
 
927
982
  Force the agent's final answer to fit a Pydantic schema:
@@ -959,7 +1014,13 @@ print(receipt.merchant)
959
1014
  ## Stream output token-by-token
960
1015
 
961
1016
  ```python
962
- runner = AgentRunner(model=Claude(), agent=AgentType.ReAct, tools=[weather_tool])
1017
+ # use_function_calling=False is REQUIRED for text_delta: FC mode (the
1018
+ # auto-detected default since 3.1.7) emits structured tool calls, not
1019
+ # streaming text, so stream_tokens=True would yield zero text_delta.
1020
+ runner = AgentRunner(
1021
+ model=Claude(), agent=AgentType.ReAct, tools=[weather_tool],
1022
+ use_function_calling=False,
1023
+ )
963
1024
 
964
1025
  for event in runner.stream("What's the weather in NYC?", stream_tokens=True):
965
1026
  if event["type"] == "text_delta":
@@ -1150,7 +1211,7 @@ print(f"Spent: ${llm.usage.estimate_cost(0.003, 0.015):.4f}")
1150
1211
  exercising the dynamic-spawn machinery and the persistent-state feature.
1151
1212
  - Project-scoped instruction sheet: [`AGENTX.md`](AGENTX.md) —
1152
1213
  read for the conventions this codebase expects.
1153
- - The full framework docs: see [`agentx_dev/README.md`](agentx_dev/README.md)
1214
+ - The full framework docs: see [`docs/README.md`](docs/README.md)
1154
1215
  (every class, every method, every operational lever)
1155
1216
 
1156
1217
  ## Links
@@ -9,6 +9,32 @@ you can paste and run.
9
9
 
10
10
  ---
11
11
 
12
+ ## What's new in 3.4 — media input and models that adapt
13
+
14
+ Send images, PDFs and audio to GPT or Claude with one type. The model
15
+ classes now adjust themselves to whatever each model generation accepts,
16
+ and sandboxed file paths behave the way you'd write them. Nothing
17
+ breaks — see [Upgrading](docs/guides/upgrading.md).
18
+
19
+ | Feature | What you get |
20
+ |---|---|
21
+ | **Media input** | `runner.invoke("Describe this", media=["photo.jpg", "report.pdf"])`. Paths, URLs, bytes or `Media` objects. The same code works on `GPT()` and `Claude()`: each model gets its own format, and either provider's native part shape is accepted too. Unsupported pairs — audio to Claude, a PDF URL to GPT — fail fast with a clear `ValueError`. [Guide](docs/guides/media.md). |
22
+ | **Models that adapt** | Parameter support changes between model generations — `reasoning_effort`'s allowed values, `max_tokens` vs `max_completion_tokens`, Claude's `temperature` + `top_p` conflict. When a model rejects a parameter, `GPT` and `Claude` make the smallest change the error asks for (`'none'` → `'low'`), retry, and remember it for that model. Logged at WARNING; `adapt_params=False` opts out. [Details](docs/concepts/models.md#old-and-new-models). |
23
+ | **More Claude settings** | `Claude(top_p=, top_k=, thinking=, stop_sequences=)`. `temperature` is only sent when you set it. |
24
+ | **Workspace paths** | With a workspace set, `/notes.txt` means the workspace's `notes.txt`, not `C:\notes.txt`. `run_python` now starts in the workspace, so `open("notes.txt")` finds it. |
25
+
26
+ ### Upgrade notes
27
+
28
+ - `Claude(temperature=)` now defaults to unset. The API default is the
29
+ old `1.0`, so output is the same.
30
+ - A parameter a model rejects is adjusted with a WARNING instead of
31
+ raising. Use `adapt_params=False` if you'd rather it fail.
32
+ - `run_python` starts in the workspace, not the launch directory.
33
+ - Drop any `reasoning_effort="none"` workarounds. Earlier docs
34
+ recommended it, and it's the value newer models reject.
35
+
36
+ Full notes: [docs/guides/upgrading.md](docs/guides/upgrading.md).
37
+
12
38
  ## What's new in 3.3 — dependency DAGs for the Supervisor
13
39
 
14
40
  Plans stopped being flat lists. A step declares what it consumes, and
@@ -858,6 +884,35 @@ guarantees a crashed save can't corrupt an existing valid file.
858
884
 
859
885
  ---
860
886
 
887
+ ## Send images, PDFs and audio
888
+
889
+ ```python
890
+ from agentx_dev import AgentRunner, AgentType, Claude, Media
891
+
892
+ runner = AgentRunner(model=Claude(), agent=AgentType.ReAct, tools=[])
893
+
894
+ result = runner.invoke(
895
+ "What does this chart show, and does the report agree?",
896
+ media=["q3_chart.png", "q3_report.pdf"],
897
+ )
898
+ ```
899
+
900
+ Swap in `GPT(model="gpt-4o")` and nothing else changes. You can also put
901
+ media straight into a message for a bare model call:
902
+
903
+ ```python
904
+ llm.invoke([{"role": "user", "content": ["What's in this photo?", Media.image("cat.jpg")]}])
905
+ ```
906
+
907
+ | | GPT | Claude |
908
+ |---|---|---|
909
+ | image (file or URL) | yes | yes |
910
+ | PDF | file | file or URL |
911
+ | audio (wav / mp3) | audio models | no — `ValueError` |
912
+
913
+ Paths are relative to where your program runs, not the agent's
914
+ workspace. See [Media](docs/guides/media.md).
915
+
861
916
  ## Get a typed result back
862
917
 
863
918
  Force the agent's final answer to fit a Pydantic schema:
@@ -895,7 +950,13 @@ print(receipt.merchant)
895
950
  ## Stream output token-by-token
896
951
 
897
952
  ```python
898
- runner = AgentRunner(model=Claude(), agent=AgentType.ReAct, tools=[weather_tool])
953
+ # use_function_calling=False is REQUIRED for text_delta: FC mode (the
954
+ # auto-detected default since 3.1.7) emits structured tool calls, not
955
+ # streaming text, so stream_tokens=True would yield zero text_delta.
956
+ runner = AgentRunner(
957
+ model=Claude(), agent=AgentType.ReAct, tools=[weather_tool],
958
+ use_function_calling=False,
959
+ )
899
960
 
900
961
  for event in runner.stream("What's the weather in NYC?", stream_tokens=True):
901
962
  if event["type"] == "text_delta":
@@ -1086,7 +1147,7 @@ print(f"Spent: ${llm.usage.estimate_cost(0.003, 0.015):.4f}")
1086
1147
  exercising the dynamic-spawn machinery and the persistent-state feature.
1087
1148
  - Project-scoped instruction sheet: [`AGENTX.md`](AGENTX.md) —
1088
1149
  read for the conventions this codebase expects.
1089
- - The full framework docs: see [`agentx_dev/README.md`](agentx_dev/README.md)
1150
+ - The full framework docs: see [`docs/README.md`](docs/README.md)
1090
1151
  (every class, every method, every operational lever)
1091
1152
 
1092
1153
  ## Links
@@ -1,11 +1,11 @@
1
- from .Agent import AgentFormattor,AgentPrompt,AgentType,AgentCompletion,StandardParser,ToolCall
2
-
3
-
4
- __all__ =[
5
- 'AgentFormattor',
6
- 'AgentCompletion',
7
- 'AgentPrompt',
8
- 'AgentType',
9
- 'StandardParser',
10
- 'ToolCall'
1
+ from .Agent import AgentFormattor,AgentPrompt,AgentType,AgentCompletion,StandardParser,ToolCall
2
+
3
+
4
+ __all__ =[
5
+ 'AgentFormattor',
6
+ 'AgentCompletion',
7
+ 'AgentPrompt',
8
+ 'AgentType',
9
+ 'StandardParser',
10
+ 'ToolCall'
11
11
  ]
@@ -1,160 +1,160 @@
1
- """
2
- Async-enabled tools for concurrent execution in agent workflows.
3
-
4
- This module provides async versions of StandardTool and StructuredTool,
5
- enabling concurrent tool execution for improved performance in I/O-bound operations.
6
- """
7
-
8
- from typing import Dict, Callable, List, Type, Coroutine, Any
9
- from pydantic import BaseModel, Field
10
- import asyncio
11
- import logging
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class AsyncStandardTool:
17
- """
18
- Async version of StandardTool that supports concurrent execution.
19
-
20
- This tool type allows async functions to be used as tools, enabling
21
- concurrent I/O operations like API calls, database queries, etc.
22
- """
23
-
24
- def __init__(self, func: Callable[..., Coroutine], name: str | None = None, description: str | None = None):
25
- """
26
- Initializes an AsyncStandardTool.
27
-
28
- Args:
29
- func (Callable): Async function to execute when the tool is called.
30
- name (str, optional): The name of the tool. Defaults to function name.
31
- description (str, optional): Description of the tool. Defaults to function docstring.
32
-
33
- Raises:
34
- ValueError: If name or description cannot be determined.
35
- """
36
- self.func = func
37
-
38
- if name:
39
- self.name = name
40
- elif getattr(func, "__name__", None) and func.__name__ != "<lambda>":
41
- self.name = func.__name__
42
- else:
43
- raise ValueError("Tool name is required (the supplied function has no usable __name__)")
44
-
45
- temp = description if description else self.func.__doc__
46
- if temp:
47
- self.description = temp
48
- else:
49
- raise ValueError("description needed cant be NONE")
50
-
51
- self.__name__ = 'AsyncStandardTool'
52
-
53
- async def execute(self, *args, **kwargs):
54
- """Execute the async tool function."""
55
- return await self.func(*args, **kwargs)
56
-
57
- def __repr__(self) -> str:
58
- return (
59
- f"AsyncStandardTool(name={self.name!r}, description={self.description!r}, "
60
- f"func={self.func!r})"
61
- )
62
-
63
-
64
- class AsyncStructuredTool:
65
- """
66
- Async version of StructuredTool with Pydantic-based argument validation.
67
-
68
- Combines the benefits of structured arguments with async execution capabilities.
69
- """
70
-
71
- def __init__(
72
- self,
73
- func: Callable[..., Coroutine],
74
- args_schema: Type[BaseModel],
75
- name: str | None = None,
76
- description: str | None = None
77
- ):
78
- """
79
- Initializes an AsyncStructuredTool.
80
-
81
- Args:
82
- func (Callable): Async function to execute.
83
- args_schema (Type[BaseModel]): Pydantic model defining expected arguments.
84
- name (str, optional): Tool name. Defaults to function name.
85
- description (str, optional): Tool description. Defaults to function docstring.
86
- """
87
- self.func = func
88
-
89
- if name:
90
- self.name = name
91
- elif getattr(func, "__name__", None) and func.__name__ != "<lambda>":
92
- self.name = func.__name__
93
- else:
94
- raise ValueError("Tool name is required (the supplied function has no usable __name__)")
95
-
96
- temp = description if description else self.func.__doc__
97
- if temp:
98
- self.description = temp
99
- else:
100
- raise ValueError("description needed cant be NONE")
101
-
102
- self.args_schema = args_schema
103
- self.__name__ = 'AsyncStructuredTool'
104
-
105
- def to_openai_tool(self, description: str | None = None) -> Dict:
106
- """OpenAI tools-API spec built from this tool's args_schema."""
107
- from agentx_dev.Agents.Agent import to_openai_tool as _to
108
- spec = _to(self.args_schema, description or self.description)
109
- spec["function"]["name"] = self.name
110
- return spec
111
-
112
- def to_anthropic_tool(self, description: str | None = None) -> Dict:
113
- """Anthropic tool spec built from this tool's args_schema."""
114
- from agentx_dev.Agents.Agent import to_anthropic_tool as _to
115
- spec = _to(self.args_schema, description or self.description)
116
- spec["name"] = self.name
117
- return spec
118
-
119
- async def execute(self, **kwargs):
120
- """Execute the async tool function with validated arguments."""
121
- validated_args = self.args_schema(**kwargs)
122
- return await self.func(**validated_args.model_dump())
123
-
124
- def __repr__(self) -> str:
125
- return (
126
- f"AsyncStructuredTool(name={self.name!r}, description={self.description!r}, "
127
- f"args_schema={self.args_schema.__name__}, func={self.func!r})"
128
- )
129
-
130
-
131
- async def execute_tools_concurrently(tools_to_execute: List[Dict[str, Any]]) -> List[Any]:
132
- """
133
- Execute multiple async tools concurrently.
134
-
135
- Args:
136
- tools_to_execute: List of dicts with 'tool' (AsyncTool instance) and 'args' (dict).
137
-
138
- Returns:
139
- List of results from each tool execution in the same order.
140
-
141
- Example:
142
- >>> tools = [
143
- ... {'tool': weather_tool, 'args': {'location': 'NYC'}},
144
- ... {'tool': news_tool, 'args': {'topic': 'tech'}}
145
- ... ]
146
- >>> results = await execute_tools_concurrently(tools)
147
- """
148
- tasks = []
149
- for item in tools_to_execute:
150
- tool = item['tool']
151
- args = item.get('args', {})
152
-
153
- if isinstance(tool, AsyncStandardTool):
154
- tasks.append(tool.execute(args.get('input', '')))
155
- elif isinstance(tool, AsyncStructuredTool):
156
- tasks.append(tool.execute(**args))
157
- else:
158
- logger.warning(f"Skipping non-async tool: {tool}")
159
-
160
- return await asyncio.gather(*tasks, return_exceptions=True)
1
+ """
2
+ Async-enabled tools for concurrent execution in agent workflows.
3
+
4
+ This module provides async versions of StandardTool and StructuredTool,
5
+ enabling concurrent tool execution for improved performance in I/O-bound operations.
6
+ """
7
+
8
+ from typing import Dict, Callable, List, Type, Coroutine, Any
9
+ from pydantic import BaseModel, Field
10
+ import asyncio
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class AsyncStandardTool:
17
+ """
18
+ Async version of StandardTool that supports concurrent execution.
19
+
20
+ This tool type allows async functions to be used as tools, enabling
21
+ concurrent I/O operations like API calls, database queries, etc.
22
+ """
23
+
24
+ def __init__(self, func: Callable[..., Coroutine], name: str | None = None, description: str | None = None):
25
+ """
26
+ Initializes an AsyncStandardTool.
27
+
28
+ Args:
29
+ func (Callable): Async function to execute when the tool is called.
30
+ name (str, optional): The name of the tool. Defaults to function name.
31
+ description (str, optional): Description of the tool. Defaults to function docstring.
32
+
33
+ Raises:
34
+ ValueError: If name or description cannot be determined.
35
+ """
36
+ self.func = func
37
+
38
+ if name:
39
+ self.name = name
40
+ elif getattr(func, "__name__", None) and func.__name__ != "<lambda>":
41
+ self.name = func.__name__
42
+ else:
43
+ raise ValueError("Tool name is required (the supplied function has no usable __name__)")
44
+
45
+ temp = description if description else self.func.__doc__
46
+ if temp:
47
+ self.description = temp
48
+ else:
49
+ raise ValueError("description needed cant be NONE")
50
+
51
+ self.__name__ = 'AsyncStandardTool'
52
+
53
+ async def execute(self, *args, **kwargs):
54
+ """Execute the async tool function."""
55
+ return await self.func(*args, **kwargs)
56
+
57
+ def __repr__(self) -> str:
58
+ return (
59
+ f"AsyncStandardTool(name={self.name!r}, description={self.description!r}, "
60
+ f"func={self.func!r})"
61
+ )
62
+
63
+
64
+ class AsyncStructuredTool:
65
+ """
66
+ Async version of StructuredTool with Pydantic-based argument validation.
67
+
68
+ Combines the benefits of structured arguments with async execution capabilities.
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ func: Callable[..., Coroutine],
74
+ args_schema: Type[BaseModel],
75
+ name: str | None = None,
76
+ description: str | None = None
77
+ ):
78
+ """
79
+ Initializes an AsyncStructuredTool.
80
+
81
+ Args:
82
+ func (Callable): Async function to execute.
83
+ args_schema (Type[BaseModel]): Pydantic model defining expected arguments.
84
+ name (str, optional): Tool name. Defaults to function name.
85
+ description (str, optional): Tool description. Defaults to function docstring.
86
+ """
87
+ self.func = func
88
+
89
+ if name:
90
+ self.name = name
91
+ elif getattr(func, "__name__", None) and func.__name__ != "<lambda>":
92
+ self.name = func.__name__
93
+ else:
94
+ raise ValueError("Tool name is required (the supplied function has no usable __name__)")
95
+
96
+ temp = description if description else self.func.__doc__
97
+ if temp:
98
+ self.description = temp
99
+ else:
100
+ raise ValueError("description needed cant be NONE")
101
+
102
+ self.args_schema = args_schema
103
+ self.__name__ = 'AsyncStructuredTool'
104
+
105
+ def to_openai_tool(self, description: str | None = None) -> Dict:
106
+ """OpenAI tools-API spec built from this tool's args_schema."""
107
+ from agentx_dev.Agents.Agent import to_openai_tool as _to
108
+ spec = _to(self.args_schema, description or self.description)
109
+ spec["function"]["name"] = self.name
110
+ return spec
111
+
112
+ def to_anthropic_tool(self, description: str | None = None) -> Dict:
113
+ """Anthropic tool spec built from this tool's args_schema."""
114
+ from agentx_dev.Agents.Agent import to_anthropic_tool as _to
115
+ spec = _to(self.args_schema, description or self.description)
116
+ spec["name"] = self.name
117
+ return spec
118
+
119
+ async def execute(self, **kwargs):
120
+ """Execute the async tool function with validated arguments."""
121
+ validated_args = self.args_schema(**kwargs)
122
+ return await self.func(**validated_args.model_dump())
123
+
124
+ def __repr__(self) -> str:
125
+ return (
126
+ f"AsyncStructuredTool(name={self.name!r}, description={self.description!r}, "
127
+ f"args_schema={self.args_schema.__name__}, func={self.func!r})"
128
+ )
129
+
130
+
131
+ async def execute_tools_concurrently(tools_to_execute: List[Dict[str, Any]]) -> List[Any]:
132
+ """
133
+ Execute multiple async tools concurrently.
134
+
135
+ Args:
136
+ tools_to_execute: List of dicts with 'tool' (AsyncTool instance) and 'args' (dict).
137
+
138
+ Returns:
139
+ List of results from each tool execution in the same order.
140
+
141
+ Example:
142
+ >>> tools = [
143
+ ... {'tool': weather_tool, 'args': {'location': 'NYC'}},
144
+ ... {'tool': news_tool, 'args': {'topic': 'tech'}}
145
+ ... ]
146
+ >>> results = await execute_tools_concurrently(tools)
147
+ """
148
+ tasks = []
149
+ for item in tools_to_execute:
150
+ tool = item['tool']
151
+ args = item.get('args', {})
152
+
153
+ if isinstance(tool, AsyncStandardTool):
154
+ tasks.append(tool.execute(args.get('input', '')))
155
+ elif isinstance(tool, AsyncStructuredTool):
156
+ tasks.append(tool.execute(**args))
157
+ else:
158
+ logger.warning(f"Skipping non-async tool: {tool}")
159
+
160
+ return await asyncio.gather(*tasks, return_exceptions=True)