agentx-python 0.4.8__tar.gz → 0.4.10__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.4.10/PKG-INFO +198 -0
- agentx_python-0.4.10/README.md +169 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx/agentx.py +28 -14
- agentx_python-0.4.10/agentx/evaluations/__init__.py +3 -0
- agentx_python-0.4.10/agentx/evaluations/_term.py +91 -0
- agentx_python-0.4.10/agentx/evaluations/adapters/__init__.py +5 -0
- agentx_python-0.4.10/agentx/evaluations/adapters/http_endpoint.py +69 -0
- agentx_python-0.4.10/agentx/evaluations/adapters/precomputed.py +35 -0
- agentx_python-0.4.10/agentx/evaluations/adapters/raw.py +38 -0
- agentx_python-0.4.10/agentx/evaluations/client.py +202 -0
- agentx_python-0.4.10/agentx/evaluations/datasets.py +235 -0
- agentx_python-0.4.10/agentx/evaluations/models.py +388 -0
- agentx_python-0.4.10/agentx/evaluations/redaction.py +57 -0
- agentx_python-0.4.10/agentx/evaluations/reporting.py +172 -0
- agentx_python-0.4.10/agentx/evaluations/results.py +125 -0
- agentx_python-0.4.10/agentx/evaluations/runner.py +312 -0
- agentx_python-0.4.10/agentx/evaluations/tracing.py +54 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx/resources/agent.py +15 -5
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx/resources/conversation.py +5 -5
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx/resources/workforce.py +9 -7
- agentx_python-0.4.10/agentx/util.py +19 -0
- agentx_python-0.4.10/agentx/version.py +1 -0
- agentx_python-0.4.10/agentx_python.egg-info/PKG-INFO +198 -0
- agentx_python-0.4.10/agentx_python.egg-info/SOURCES.txt +31 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/setup.py +1 -1
- agentx_python-0.4.10/tests/test_integration.py +181 -0
- agentx_python-0.4.8/PKG-INFO +0 -188
- agentx_python-0.4.8/README.md +0 -159
- agentx_python-0.4.8/agentx/util.py +0 -5
- agentx_python-0.4.8/agentx/version.py +0 -1
- agentx_python-0.4.8/agentx_python.egg-info/PKG-INFO +0 -188
- agentx_python-0.4.8/agentx_python.egg-info/SOURCES.txt +0 -16
- {agentx_python-0.4.8 → agentx_python-0.4.10}/LICENSE +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx/__init__.py +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.10}/setup.cfg +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentx-python
|
|
3
|
+
Version: 0.4.10
|
|
4
|
+
Summary: Official Python SDK for AgentX (https://www.agentx.so/)
|
|
5
|
+
Home-page: https://github.com/AgentX-ai/AgentX-python
|
|
6
|
+
Author: Robin Wang and AgentX Team
|
|
7
|
+
Author-email: contact@agentx.so
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: urllib3>=1.26.11
|
|
15
|
+
Requires-Dist: certifi
|
|
16
|
+
Requires-Dist: requests
|
|
17
|
+
Requires-Dist: pydantic
|
|
18
|
+
Requires-Dist: pydantic_core
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/agentx-python/)
|
|
33
|
+
|
|
34
|
+
The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat with, and orchestrate AI agents in a few lines of code.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Contents
|
|
39
|
+
|
|
40
|
+
- [Why AgentX](#why-agentx)
|
|
41
|
+
- [Installation](#installation)
|
|
42
|
+
- [Authentication](#authentication)
|
|
43
|
+
- [Quick start](#quick-start)
|
|
44
|
+
- [Working with agents](#working-with-agents)
|
|
45
|
+
- [List agents](#list-agents)
|
|
46
|
+
- [Start a conversation](#start-a-conversation)
|
|
47
|
+
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
48
|
+
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration)
|
|
49
|
+
- [Agent Evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
50
|
+
- [Links](#links)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Why AgentX
|
|
55
|
+
|
|
56
|
+
- **Simple mental model** — `Agent → Conversation → Message`.
|
|
57
|
+
- **Chain-of-thought** is built in, no extra plumbing.
|
|
58
|
+
- **Bring any LLM** — works across major open and closed-source vendors.
|
|
59
|
+
- **Batteries included** — voice (ASR/TTS), image generation, document/CSV/Excel/OCR, RAG with built-in re-ranking.
|
|
60
|
+
- **MCP support** — connect any Model Context Protocol server.
|
|
61
|
+
- **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
|
|
62
|
+
- **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
|
|
63
|
+
- **A2A** — Each agent can be published with agent-to-agent protocol compatible.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install --upgrade agentx-python
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Requires Python 3.9 or newer.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Authentication
|
|
78
|
+
|
|
79
|
+
Get your API key at [app.agentx.so](https://app.agentx.so), then either pass it inline or expose it as an environment variable.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Option A — pass the key inline
|
|
83
|
+
from agentx import AgentX
|
|
84
|
+
client = AgentX(api_key="your-api-key-here")
|
|
85
|
+
|
|
86
|
+
# Option B — set AGENTX_API_KEY in your environment, then:
|
|
87
|
+
client = AgentX.from_env()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Quick start
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from agentx import AgentX
|
|
96
|
+
|
|
97
|
+
client = AgentX.from_env()
|
|
98
|
+
|
|
99
|
+
# Pick an existing agent and chat with it
|
|
100
|
+
agent = client.list_agents()[0]
|
|
101
|
+
conversation = agent.new_conversation()
|
|
102
|
+
print(conversation.chat("Hello! What can you help me with?"))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
That's it. The remaining sections show the same primitives in more detail.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Working with agents
|
|
110
|
+
|
|
111
|
+
### List agents
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
agents = client.list_agents()
|
|
115
|
+
print(f"You have {len(agents)} agents")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Start a conversation
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
agent = client.get_agent(id="<agent-id>")
|
|
122
|
+
|
|
123
|
+
# Either resume an existing conversation…
|
|
124
|
+
existing = agent.list_conversations()
|
|
125
|
+
last = existing[-1]
|
|
126
|
+
for msg in last.list_messages():
|
|
127
|
+
print(msg)
|
|
128
|
+
|
|
129
|
+
# …or start a fresh one
|
|
130
|
+
conversation = agent.new_conversation()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Chat (streaming and non-streaming)
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# Blocking — returns the full response once it's ready
|
|
137
|
+
response = conversation.chat("What is your name?")
|
|
138
|
+
print(response)
|
|
139
|
+
|
|
140
|
+
# Streaming — yields ChatResponse objects as the model produces them
|
|
141
|
+
for chunk in conversation.chat_stream("Hello, what is your name?"):
|
|
142
|
+
if chunk.text:
|
|
143
|
+
print(chunk.text, end="")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Each `ChatResponse` chunk exposes the agent's `text` and, where applicable, its `cot` (chain-of-thought) reasoning, along with any retrieved references and tasks.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Workforce (multi-agent orchestration)
|
|
151
|
+
|
|
152
|
+
A **workforce** is a team of agents coordinated by a designated manager agent. Workforces can mix LLM vendors and route work between specialists.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
workforces = client.list_workforces()
|
|
156
|
+
workforce = workforces[0]
|
|
157
|
+
|
|
158
|
+
print(f"Workforce: {workforce.name}")
|
|
159
|
+
print(f"Manager: {workforce.manager.name}")
|
|
160
|
+
print(f"Agents: {[a.name for a in workforce.agents]}")
|
|
161
|
+
|
|
162
|
+
# Chat with the workforce — the manager decides which agent(s) to delegate to
|
|
163
|
+
conversation = workforce.new_conversation()
|
|
164
|
+
for chunk in workforce.chat_stream(conversation.id, "How can you help me with this project?"):
|
|
165
|
+
if chunk.text:
|
|
166
|
+
print(chunk.text, end="")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Custom agent evaluations
|
|
172
|
+
|
|
173
|
+
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
report = (
|
|
177
|
+
client.evaluations
|
|
178
|
+
.run(dataset_id="evds_…", subject={"kind": "custom_agent", "framework": "raw_python"})
|
|
179
|
+
.execute(my_agent_fn)
|
|
180
|
+
.finalize()
|
|
181
|
+
.analyze()
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
print(report.average_rating) # LLM-graded score, 0–10
|
|
185
|
+
print(report.cosine_similarity) # embedding cosine, 0–1 (None if not enabled)
|
|
186
|
+
print(report.jaccard_similarity) # token-set overlap, 0–1 (None if not enabled)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, and the complete API reference.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Links
|
|
194
|
+
|
|
195
|
+
- **Dashboard** — [app.agentx.so](https://app.agentx.so)
|
|
196
|
+
- **Website** — [agentx.so](https://www.agentx.so/)
|
|
197
|
+
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
198
|
+
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/agentx-python/)
|
|
4
|
+
|
|
5
|
+
The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat with, and orchestrate AI agents in a few lines of code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Why AgentX](#why-agentx)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Authentication](#authentication)
|
|
14
|
+
- [Quick start](#quick-start)
|
|
15
|
+
- [Working with agents](#working-with-agents)
|
|
16
|
+
- [List agents](#list-agents)
|
|
17
|
+
- [Start a conversation](#start-a-conversation)
|
|
18
|
+
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
19
|
+
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration)
|
|
20
|
+
- [Agent Evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
21
|
+
- [Links](#links)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Why AgentX
|
|
26
|
+
|
|
27
|
+
- **Simple mental model** — `Agent → Conversation → Message`.
|
|
28
|
+
- **Chain-of-thought** is built in, no extra plumbing.
|
|
29
|
+
- **Bring any LLM** — works across major open and closed-source vendors.
|
|
30
|
+
- **Batteries included** — voice (ASR/TTS), image generation, document/CSV/Excel/OCR, RAG with built-in re-ranking.
|
|
31
|
+
- **MCP support** — connect any Model Context Protocol server.
|
|
32
|
+
- **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
|
|
33
|
+
- **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
|
|
34
|
+
- **A2A** — Each agent can be published with agent-to-agent protocol compatible.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install --upgrade agentx-python
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requires Python 3.9 or newer.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Authentication
|
|
49
|
+
|
|
50
|
+
Get your API key at [app.agentx.so](https://app.agentx.so), then either pass it inline or expose it as an environment variable.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
# Option A — pass the key inline
|
|
54
|
+
from agentx import AgentX
|
|
55
|
+
client = AgentX(api_key="your-api-key-here")
|
|
56
|
+
|
|
57
|
+
# Option B — set AGENTX_API_KEY in your environment, then:
|
|
58
|
+
client = AgentX.from_env()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from agentx import AgentX
|
|
67
|
+
|
|
68
|
+
client = AgentX.from_env()
|
|
69
|
+
|
|
70
|
+
# Pick an existing agent and chat with it
|
|
71
|
+
agent = client.list_agents()[0]
|
|
72
|
+
conversation = agent.new_conversation()
|
|
73
|
+
print(conversation.chat("Hello! What can you help me with?"))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That's it. The remaining sections show the same primitives in more detail.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Working with agents
|
|
81
|
+
|
|
82
|
+
### List agents
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
agents = client.list_agents()
|
|
86
|
+
print(f"You have {len(agents)} agents")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Start a conversation
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
agent = client.get_agent(id="<agent-id>")
|
|
93
|
+
|
|
94
|
+
# Either resume an existing conversation…
|
|
95
|
+
existing = agent.list_conversations()
|
|
96
|
+
last = existing[-1]
|
|
97
|
+
for msg in last.list_messages():
|
|
98
|
+
print(msg)
|
|
99
|
+
|
|
100
|
+
# …or start a fresh one
|
|
101
|
+
conversation = agent.new_conversation()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Chat (streaming and non-streaming)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Blocking — returns the full response once it's ready
|
|
108
|
+
response = conversation.chat("What is your name?")
|
|
109
|
+
print(response)
|
|
110
|
+
|
|
111
|
+
# Streaming — yields ChatResponse objects as the model produces them
|
|
112
|
+
for chunk in conversation.chat_stream("Hello, what is your name?"):
|
|
113
|
+
if chunk.text:
|
|
114
|
+
print(chunk.text, end="")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Each `ChatResponse` chunk exposes the agent's `text` and, where applicable, its `cot` (chain-of-thought) reasoning, along with any retrieved references and tasks.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Workforce (multi-agent orchestration)
|
|
122
|
+
|
|
123
|
+
A **workforce** is a team of agents coordinated by a designated manager agent. Workforces can mix LLM vendors and route work between specialists.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
workforces = client.list_workforces()
|
|
127
|
+
workforce = workforces[0]
|
|
128
|
+
|
|
129
|
+
print(f"Workforce: {workforce.name}")
|
|
130
|
+
print(f"Manager: {workforce.manager.name}")
|
|
131
|
+
print(f"Agents: {[a.name for a in workforce.agents]}")
|
|
132
|
+
|
|
133
|
+
# Chat with the workforce — the manager decides which agent(s) to delegate to
|
|
134
|
+
conversation = workforce.new_conversation()
|
|
135
|
+
for chunk in workforce.chat_stream(conversation.id, "How can you help me with this project?"):
|
|
136
|
+
if chunk.text:
|
|
137
|
+
print(chunk.text, end="")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Custom agent evaluations
|
|
143
|
+
|
|
144
|
+
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
report = (
|
|
148
|
+
client.evaluations
|
|
149
|
+
.run(dataset_id="evds_…", subject={"kind": "custom_agent", "framework": "raw_python"})
|
|
150
|
+
.execute(my_agent_fn)
|
|
151
|
+
.finalize()
|
|
152
|
+
.analyze()
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
print(report.average_rating) # LLM-graded score, 0–10
|
|
156
|
+
print(report.cosine_similarity) # embedding cosine, 0–1 (None if not enabled)
|
|
157
|
+
print(report.jaccard_similarity) # token-set overlap, 0–1 (None if not enabled)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, and the complete API reference.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Links
|
|
165
|
+
|
|
166
|
+
- **Dashboard** — [app.agentx.so](https://app.agentx.so)
|
|
167
|
+
- **Website** — [agentx.so](https://www.agentx.so/)
|
|
168
|
+
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
169
|
+
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
@@ -3,37 +3,51 @@ import requests
|
|
|
3
3
|
import os
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
-
from agentx.util import get_headers
|
|
6
|
+
from agentx.util import get_headers, api_base
|
|
7
7
|
from agentx.resources.agent import Agent
|
|
8
8
|
from agentx.resources.workforce import Workforce
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class AgentX:
|
|
12
12
|
|
|
13
|
-
def __init__(self, api_key: str = None):
|
|
13
|
+
def __init__(self, api_key: str = None, base_url: str = None):
|
|
14
14
|
self.api_key = api_key or os.getenv("AGENTX_API_KEY")
|
|
15
15
|
if self.api_key and not os.getenv("AGENTX_API_KEY"):
|
|
16
16
|
os.environ["AGENTX_API_KEY"] = self.api_key
|
|
17
17
|
|
|
18
|
+
# base_url overrides AGENTX_API_BASE_URL env var (and the SDK default)
|
|
19
|
+
self.base_url = base_url or os.getenv("AGENTX_API_BASE_URL")
|
|
20
|
+
if self.base_url:
|
|
21
|
+
os.environ["AGENTX_API_BASE_URL"] = self.base_url
|
|
22
|
+
|
|
23
|
+
from agentx.evaluations.client import EvaluationsClient
|
|
24
|
+
from agentx.evaluations.runner import EvaluationsRunner
|
|
25
|
+
from agentx.version import VERSION
|
|
26
|
+
|
|
27
|
+
_eval_client = EvaluationsClient(
|
|
28
|
+
api_key=self.api_key,
|
|
29
|
+
sdk_version=VERSION,
|
|
30
|
+
base_url=self.base_url,
|
|
31
|
+
)
|
|
32
|
+
self.evaluations = EvaluationsRunner(_eval_client)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_env(cls) -> "AgentX":
|
|
36
|
+
"""Create an AgentX client using AGENTX_API_KEY (and optionally AGENTX_API_BASE_URL) from the environment."""
|
|
37
|
+
return cls()
|
|
38
|
+
|
|
18
39
|
def get_agent(self, id: str) -> Agent:
|
|
19
|
-
url = f"
|
|
40
|
+
url = f"{api_base()}/access/agents/{id}"
|
|
20
41
|
# Make a GET request to the AgentX API
|
|
21
42
|
response = requests.get(url, headers=get_headers())
|
|
22
43
|
# Check if response was successful
|
|
23
44
|
if response.status_code == 200:
|
|
24
|
-
|
|
25
|
-
return Agent(
|
|
26
|
-
id=agent_res.get("_id"),
|
|
27
|
-
name=agent_res.get("name"),
|
|
28
|
-
avatar=agent_res.get("avatar"),
|
|
29
|
-
createdAt=agent_res.get("createdAt"),
|
|
30
|
-
updatedAt=agent_res.get("updatedAt"),
|
|
31
|
-
)
|
|
45
|
+
return Agent(**response.json())
|
|
32
46
|
else:
|
|
33
47
|
raise Exception(f"Failed to retrieve agent: {response.reason}")
|
|
34
48
|
|
|
35
49
|
def list_agents(self) -> List[Agent]:
|
|
36
|
-
url = "
|
|
50
|
+
url = f"{api_base()}/access/agents"
|
|
37
51
|
# Make a GET request to the AgentX API
|
|
38
52
|
response = requests.get(url, headers=get_headers())
|
|
39
53
|
# Check if response was successful
|
|
@@ -45,7 +59,7 @@ class AgentX:
|
|
|
45
59
|
@staticmethod
|
|
46
60
|
def list_workforces() -> List["Workforce"]:
|
|
47
61
|
"""List all workforces/teams."""
|
|
48
|
-
url = "
|
|
62
|
+
url = f"{api_base()}/access/teams"
|
|
49
63
|
response = requests.get(url, headers=get_headers())
|
|
50
64
|
if response.status_code == 200:
|
|
51
65
|
return [Workforce(**workforce) for workforce in response.json()]
|
|
@@ -56,7 +70,7 @@ class AgentX:
|
|
|
56
70
|
|
|
57
71
|
def get_profile(self):
|
|
58
72
|
"""Get the current user's profile information."""
|
|
59
|
-
url = "
|
|
73
|
+
url = f"{api_base()}/access/getProfile"
|
|
60
74
|
response = requests.get(url, headers=get_headers())
|
|
61
75
|
if response.status_code == 200:
|
|
62
76
|
return response.json()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Minimal ANSI terminal helpers — no external dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import itertools
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
_IS_TTY = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _c(code: str) -> str:
|
|
14
|
+
return f"\033[{code}m" if _IS_TTY else ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
RESET = _c("0")
|
|
18
|
+
BOLD = _c("1")
|
|
19
|
+
DIM = _c("2")
|
|
20
|
+
GREEN = _c("32")
|
|
21
|
+
YELLOW = _c("33")
|
|
22
|
+
RED = _c("31")
|
|
23
|
+
CYAN = _c("36")
|
|
24
|
+
MAGENTA = _c("35")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def green(s: str) -> str:
|
|
28
|
+
return f"{GREEN}{s}{RESET}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def yellow(s: str) -> str:
|
|
32
|
+
return f"{YELLOW}{s}{RESET}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def red(s: str) -> str:
|
|
36
|
+
return f"{RED}{s}{RESET}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def cyan(s: str) -> str:
|
|
40
|
+
return f"{CYAN}{s}{RESET}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def bold(s: str) -> str:
|
|
44
|
+
return f"{BOLD}{s}{RESET}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dim(s: str) -> str:
|
|
48
|
+
return f"{DIM}{s}{RESET}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def magenta(s: str) -> str:
|
|
52
|
+
return f"{MAGENTA}{s}{RESET}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Spinner:
|
|
56
|
+
"""Inline spinner that overwrites the current line on a TTY."""
|
|
57
|
+
|
|
58
|
+
_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
59
|
+
|
|
60
|
+
def __init__(self, message: str):
|
|
61
|
+
self._message = message
|
|
62
|
+
self._stop = threading.Event()
|
|
63
|
+
self._thread: threading.Thread | None = None
|
|
64
|
+
|
|
65
|
+
def __enter__(self) -> "Spinner":
|
|
66
|
+
if not _IS_TTY:
|
|
67
|
+
print(f" {self._message}...", flush=True)
|
|
68
|
+
return self
|
|
69
|
+
self._stop.clear()
|
|
70
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
|
71
|
+
self._thread.start()
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def __exit__(self, *_) -> None:
|
|
75
|
+
if not _IS_TTY:
|
|
76
|
+
return
|
|
77
|
+
self._stop.set()
|
|
78
|
+
if self._thread:
|
|
79
|
+
self._thread.join()
|
|
80
|
+
sys.stdout.write(f"\r{' ' * (len(self._message) + 12)}\r")
|
|
81
|
+
sys.stdout.flush()
|
|
82
|
+
|
|
83
|
+
def _spin(self) -> None:
|
|
84
|
+
for frame in itertools.cycle(self._FRAMES):
|
|
85
|
+
if self._stop.is_set():
|
|
86
|
+
break
|
|
87
|
+
sys.stdout.write(
|
|
88
|
+
f"\r {CYAN}{frame}{RESET} {self._message} {DIM}(this may take ~60s+){RESET}"
|
|
89
|
+
)
|
|
90
|
+
sys.stdout.flush()
|
|
91
|
+
time.sleep(0.08)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from agentx.evaluations.adapters.raw import RawCallableAdapter
|
|
2
|
+
from agentx.evaluations.adapters.precomputed import PrecomputedAdapter
|
|
3
|
+
from agentx.evaluations.adapters.http_endpoint import HttpEndpointAdapter
|
|
4
|
+
|
|
5
|
+
__all__ = ["RawCallableAdapter", "PrecomputedAdapter", "HttpEndpointAdapter"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from agentx.evaluations.models import EvaluationCase, EvaluationResult
|
|
9
|
+
from agentx.evaluations.results import normalize_error, normalize_result
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HttpEndpointAdapter:
|
|
13
|
+
"""
|
|
14
|
+
Calls a user-hosted HTTP endpoint for each evaluation case.
|
|
15
|
+
The SDK (running locally) makes the request — the AgentX API never
|
|
16
|
+
touches the customer's endpoint.
|
|
17
|
+
|
|
18
|
+
The endpoint receives a POST with::
|
|
19
|
+
|
|
20
|
+
{"query": "...", "case_id": "...", "metadata": {...}}
|
|
21
|
+
|
|
22
|
+
It should respond with a JSON body containing at least ``output``
|
|
23
|
+
(or ``text``) and optionally ``trace`` and ``metadata``.
|
|
24
|
+
|
|
25
|
+
Usage::
|
|
26
|
+
|
|
27
|
+
adapter = HttpEndpointAdapter(
|
|
28
|
+
url="http://localhost:8080/eval",
|
|
29
|
+
headers={"Authorization": "Bearer my-token"},
|
|
30
|
+
timeout=30,
|
|
31
|
+
)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
url: str,
|
|
37
|
+
headers: Optional[Dict[str, str]] = None,
|
|
38
|
+
timeout: int = 30,
|
|
39
|
+
method: str = "POST",
|
|
40
|
+
):
|
|
41
|
+
self._url = url
|
|
42
|
+
self._headers = headers or {}
|
|
43
|
+
self._timeout = timeout
|
|
44
|
+
self._method = method.upper()
|
|
45
|
+
|
|
46
|
+
def run(self, case: EvaluationCase) -> EvaluationResult:
|
|
47
|
+
payload = {
|
|
48
|
+
"query": case.query,
|
|
49
|
+
"case_id": case.case_id,
|
|
50
|
+
"question_index": case.question_index,
|
|
51
|
+
"run_number": case.run_number,
|
|
52
|
+
}
|
|
53
|
+
start = time.monotonic()
|
|
54
|
+
try:
|
|
55
|
+
resp = requests.request(
|
|
56
|
+
self._method,
|
|
57
|
+
self._url,
|
|
58
|
+
json=payload,
|
|
59
|
+
headers=self._headers,
|
|
60
|
+
timeout=self._timeout,
|
|
61
|
+
)
|
|
62
|
+
resp.raise_for_status()
|
|
63
|
+
elapsed = int((time.monotonic() - start) * 1000)
|
|
64
|
+
raw = resp.json()
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
elapsed = int((time.monotonic() - start) * 1000)
|
|
67
|
+
return normalize_error(case, exc, latency_ms=elapsed)
|
|
68
|
+
|
|
69
|
+
return normalize_result(case, raw, latency_ms=elapsed)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Union
|
|
4
|
+
|
|
5
|
+
from agentx.evaluations.models import EvaluationCase, EvaluationResult
|
|
6
|
+
from agentx.evaluations.results import normalize_result
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PrecomputedAdapter:
|
|
10
|
+
"""
|
|
11
|
+
Adapter for pre-computed outputs — useful when you already have agent
|
|
12
|
+
responses and just want AgentX to score them.
|
|
13
|
+
|
|
14
|
+
Accepts a list or dict keyed by case_id::
|
|
15
|
+
|
|
16
|
+
outputs = {
|
|
17
|
+
"case-0": "You can reset your password from account settings.",
|
|
18
|
+
"case-1": {"output": "Contact support@example.com", "metadata": {...}},
|
|
19
|
+
}
|
|
20
|
+
adapter = PrecomputedAdapter(outputs)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, outputs: Union[List[Any], Dict[str, Any]]):
|
|
24
|
+
if isinstance(outputs, list):
|
|
25
|
+
self._lookup: Dict[str, Any] = {str(i): v for i, v in enumerate(outputs)}
|
|
26
|
+
else:
|
|
27
|
+
self._lookup = {str(k): v for k, v in outputs.items()}
|
|
28
|
+
|
|
29
|
+
def run(self, case: EvaluationCase) -> EvaluationResult:
|
|
30
|
+
raw = self._lookup.get(case.case_id) or self._lookup.get(
|
|
31
|
+
str(case.question_index)
|
|
32
|
+
)
|
|
33
|
+
if raw is None:
|
|
34
|
+
raw = ""
|
|
35
|
+
return normalize_result(case, raw, latency_ms=0)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
from agentx.evaluations.models import EvaluationCase, EvaluationResult
|
|
6
|
+
from agentx.evaluations.results import normalize_error, normalize_result
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RawCallableAdapter:
|
|
10
|
+
"""
|
|
11
|
+
Wraps any Python callable that accepts an EvaluationCase and returns
|
|
12
|
+
str | dict | EvaluationResult.
|
|
13
|
+
|
|
14
|
+
Usage::
|
|
15
|
+
|
|
16
|
+
def my_agent(case: EvaluationCase) -> str:
|
|
17
|
+
return my_llm.invoke(case.query)
|
|
18
|
+
|
|
19
|
+
adapter = RawCallableAdapter(my_agent)
|
|
20
|
+
result = adapter.run(case)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, fn: Callable[[EvaluationCase], Any]):
|
|
24
|
+
self._fn = fn
|
|
25
|
+
|
|
26
|
+
def run(
|
|
27
|
+
self, case: EvaluationCase, latency_ms: int | None = None
|
|
28
|
+
) -> EvaluationResult:
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
start = time.monotonic()
|
|
32
|
+
try:
|
|
33
|
+
raw = self._fn(case)
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
elapsed = int((time.monotonic() - start) * 1000)
|
|
36
|
+
return normalize_error(case, exc, latency_ms=elapsed)
|
|
37
|
+
elapsed = int((time.monotonic() - start) * 1000)
|
|
38
|
+
return normalize_result(case, raw, latency_ms=elapsed)
|