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.
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/CHANGELOG.md +65 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/PKG-INFO +64 -3
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/README.md +63 -2
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Agents/__init__.py +10 -10
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/AsyncTools.py +160 -160
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/AutoSetup.py +164 -164
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Cache.py +471 -471
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/ChatModel.py +1910 -1583
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Config.py +144 -144
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/DefaultTools.py +2053 -2026
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Embeddings.py +681 -681
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Evals.py +514 -514
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Handoffs.py +420 -420
- agentx_dev-3.4.0/agentx_dev/Media.py +500 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Memory.py +474 -474
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Observability.py +494 -494
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/AgentRun.py +61 -12
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/AsyncAgentRun.py +12 -4
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/__init__.py +13 -13
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Runner/promptTemplate.yaml +139 -139
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Streaming.py +297 -297
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Tools.py +246 -246
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/qdrant_store.py +239 -239
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/WebTools.py +501 -501
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/__init__.py +4 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/resources/__init__.py +3 -3
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/PKG-INFO +64 -3
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/SOURCES.txt +1 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/pyproject.toml +1 -1
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/AGENTX.md +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/CONTRIBUTING.md +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/LICENSE +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/MANIFEST.in +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Agents/Agent.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Compiler.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Loader.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/MCP.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Planner.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Session.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Splitters.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/Supervisor.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/__init__.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/chroma_store.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/VectorStores/pg_store.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev/resources/promptTemplate.yaml +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/dependency_links.txt +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/requires.txt +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/agentx_dev.egg-info/top_level.txt +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/README.md +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/agentic_rag_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/async_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/async_quickstart.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/auto_features_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/caching_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/chatbot_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/complete_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/concurrent_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/concurrent_tool_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/file_agent_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/function_calling_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/mcp_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/mcp_github_triage_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/observability_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/orchestration_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/planner_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/robust_link_scraper.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/supervisor_codebase_analysis_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/supervisor_example.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/sync_quickstart.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/v3_1_1_features_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/v3_1_comprehensive_demo.py +0 -0
- {agentx_dev-3.3.1 → agentx_dev-3.4.0}/examples/v3_1_features_demo.py +0 -0
- {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
|
+
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
|
-
|
|
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 [`
|
|
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
|
-
|
|
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 [`
|
|
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)
|