agentx-python 0.4.8__tar.gz → 0.4.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentx_python-0.4.8/agentx_python.egg-info → agentx_python-0.4.9}/PKG-INFO +21 -68
- agentx_python-0.4.9/README.md +112 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx/agentx.py +27 -14
- agentx_python-0.4.9/agentx/evaluations/__init__.py +3 -0
- agentx_python-0.4.9/agentx/evaluations/_term.py +69 -0
- agentx_python-0.4.9/agentx/evaluations/adapters/__init__.py +5 -0
- agentx_python-0.4.9/agentx/evaluations/adapters/http_endpoint.py +69 -0
- agentx_python-0.4.9/agentx/evaluations/adapters/precomputed.py +33 -0
- agentx_python-0.4.9/agentx/evaluations/adapters/raw.py +35 -0
- agentx_python-0.4.9/agentx/evaluations/client.py +183 -0
- agentx_python-0.4.9/agentx/evaluations/datasets.py +220 -0
- agentx_python-0.4.9/agentx/evaluations/models.py +294 -0
- agentx_python-0.4.9/agentx/evaluations/redaction.py +45 -0
- agentx_python-0.4.9/agentx/evaluations/reporting.py +146 -0
- agentx_python-0.4.9/agentx/evaluations/results.py +109 -0
- agentx_python-0.4.9/agentx/evaluations/runner.py +293 -0
- agentx_python-0.4.9/agentx/evaluations/tracing.py +54 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx/resources/agent.py +15 -5
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx/resources/conversation.py +5 -5
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx/resources/workforce.py +7 -7
- agentx_python-0.4.9/agentx/util.py +19 -0
- agentx_python-0.4.9/agentx/version.py +1 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9/agentx_python.egg-info}/PKG-INFO +21 -68
- agentx_python-0.4.9/agentx_python.egg-info/SOURCES.txt +31 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/setup.py +1 -1
- agentx_python-0.4.9/tests/test_integration.py +174 -0
- 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/SOURCES.txt +0 -16
- {agentx_python-0.4.8 → agentx_python-0.4.9}/LICENSE +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx/__init__.py +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.4.8 → agentx_python-0.4.9}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
4
4
|
Summary: Official Python SDK for AgentX (https://www.agentx.so/)
|
|
5
|
-
Home-page: https://github.com/AgentX-ai/AgentX-python
|
|
5
|
+
Home-page: https://github.com/AgentX-ai/AgentX-python
|
|
6
6
|
Author: Robin Wang and AgentX Team
|
|
7
7
|
Author-email: contact@agentx.so
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -33,22 +33,22 @@ Dynamic: summary
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
-
##
|
|
36
|
+
## A fast way to build AI Agents and create agent workforces
|
|
37
37
|
|
|
38
38
|
The official AgentX Python SDK for [AgentX](https://www.agentx.so/)
|
|
39
39
|
|
|
40
|
-
Why build AI
|
|
40
|
+
Why build AI agents with AgentX?
|
|
41
41
|
|
|
42
|
-
- Simplicity
|
|
43
|
-
-
|
|
44
|
-
- Choose from most open and closed
|
|
45
|
-
- Built-in Voice(ASR, TTS), Image Gen, Document, CSV/
|
|
46
|
-
- Support all running MCP (
|
|
47
|
-
-
|
|
42
|
+
- Simplicity — Agent → Conversation → Message structure.
|
|
43
|
+
- Chain-of-thought built in.
|
|
44
|
+
- Choose from most open and closed-source LLM vendors.
|
|
45
|
+
- Built-in Voice (ASR, TTS), Image Gen, Document, CSV/Excel, OCR, and more.
|
|
46
|
+
- Support for all running MCP (Model Context Protocol) servers.
|
|
47
|
+
- RAG with built-in re-rank.
|
|
48
48
|
- Multi-agent workforce orchestration.
|
|
49
49
|
- Multiple agents working together with a designated manager agent.
|
|
50
|
-
- Cross
|
|
51
|
-
- A2A
|
|
50
|
+
- Cross-LLM-vendor, multi-agent orchestration.
|
|
51
|
+
- A2A — agent-to-agent protocol (coming soon)
|
|
52
52
|
|
|
53
53
|
## Installation
|
|
54
54
|
|
|
@@ -58,19 +58,14 @@ pip install --upgrade agentx-python
|
|
|
58
58
|
|
|
59
59
|
## Quick Start
|
|
60
60
|
|
|
61
|
-
Get started with AgentX in just a few lines of code:
|
|
62
|
-
|
|
63
61
|
```python
|
|
64
62
|
from agentx import AgentX
|
|
65
63
|
|
|
66
|
-
# Initialize the client
|
|
67
64
|
client = AgentX(api_key="your-api-key-here")
|
|
68
65
|
|
|
69
|
-
# Get your agents
|
|
70
66
|
agents = client.list_agents()
|
|
71
67
|
print(f"You have {len(agents)} agents")
|
|
72
68
|
|
|
73
|
-
# Start chatting with your first agent
|
|
74
69
|
if agents:
|
|
75
70
|
agent = agents[0]
|
|
76
71
|
conversation = agent.new_conversation()
|
|
@@ -81,7 +76,7 @@ if agents:
|
|
|
81
76
|
## Usage
|
|
82
77
|
|
|
83
78
|
Provide an `api_key` inline or set `AGENTX_API_KEY` as an environment variable.
|
|
84
|
-
|
|
79
|
+
Get your API key at https://app.agentx.so
|
|
85
80
|
|
|
86
81
|
### Agent
|
|
87
82
|
|
|
@@ -89,24 +84,15 @@ You can get an API key from https://app.agentx.so
|
|
|
89
84
|
from agentx import AgentX
|
|
90
85
|
|
|
91
86
|
client = AgentX(api_key="<your api key here>")
|
|
92
|
-
|
|
93
|
-
# Get the list of agents you have
|
|
94
87
|
print(client.list_agents())
|
|
95
88
|
```
|
|
96
89
|
|
|
97
90
|
### Conversation
|
|
98
91
|
|
|
99
|
-
Each Conversation has `agents` and `users` tied to it.
|
|
100
|
-
|
|
101
92
|
```python
|
|
102
|
-
# get agent
|
|
103
93
|
my_agent = client.get_agent(id="<agent id here>")
|
|
104
94
|
|
|
105
|
-
# Get the list of conversation from this agent
|
|
106
95
|
existing_conversations = my_agent.list_conversations()
|
|
107
|
-
print(existing_conversations)
|
|
108
|
-
|
|
109
|
-
# Get the list of history messages from a conversation
|
|
110
96
|
last_conversation = existing_conversations[-1]
|
|
111
97
|
msgs = last_conversation.list_messages()
|
|
112
98
|
print(msgs)
|
|
@@ -114,8 +100,6 @@ print(msgs)
|
|
|
114
100
|
|
|
115
101
|
### Chat
|
|
116
102
|
|
|
117
|
-
A `chat` needs to happen in the conversation. You can do `stream` response too, default `False`.
|
|
118
|
-
|
|
119
103
|
```python
|
|
120
104
|
a_conversation = my_agent.get_conversation(id="<conversation id here>")
|
|
121
105
|
|
|
@@ -124,65 +108,34 @@ for chunk in response:
|
|
|
124
108
|
print(chunk)
|
|
125
109
|
```
|
|
126
110
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
text=None cot='The user is greeting and asking for my ' botId='xxx'
|
|
131
|
-
text=None cot='name, which are casual, straightforward questions.' botId='xxx'
|
|
132
|
-
text=None cot=' I can answer these directly' botId='xxx'
|
|
133
|
-
text='Hello' cot=None botId='xxx'
|
|
134
|
-
text='!' cot=None botId='xxx'
|
|
135
|
-
text=' I' cot=None botId='xxx'
|
|
136
|
-
text=' am' cot=None botId='xxx'
|
|
137
|
-
text=' AgentX' cot=None botId='xxx'
|
|
138
|
-
text=None cot=None botId='xxx'
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
\*`cot` stands for chain-of-thoughts
|
|
111
|
+
\*`cot` stands for chain-of-thought
|
|
142
112
|
|
|
143
113
|
### Workforce
|
|
144
114
|
|
|
145
|
-
A Workforce (team) consists of multiple agents working together with a designated manager agent.
|
|
146
|
-
|
|
147
115
|
```python
|
|
148
116
|
from agentx import AgentX
|
|
149
117
|
|
|
150
118
|
client = AgentX(api_key="<your api key here>")
|
|
151
119
|
|
|
152
|
-
# Get the list of workforces/teams you have
|
|
153
120
|
workforces = client.list_workforces()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# Get a specific workforce
|
|
157
|
-
workforce = workforces[0] # or any specific workforce
|
|
121
|
+
workforce = workforces[0]
|
|
158
122
|
print(f"Workforce: {workforce.name}")
|
|
159
123
|
print(f"Manager: {workforce.manager.name}")
|
|
160
124
|
print(f"Agents: {[agent.name for agent in workforce.agents]}")
|
|
161
125
|
```
|
|
162
126
|
|
|
163
|
-
#### Workforce Conversations
|
|
164
|
-
|
|
165
|
-
```python
|
|
166
|
-
# Create a new conversation with the workforce
|
|
167
|
-
conversation = workforce.new_conversation()
|
|
168
|
-
|
|
169
|
-
# List all existing conversations for the workforce
|
|
170
|
-
conversations = workforce.list_conversations()
|
|
171
|
-
print(conversations)
|
|
172
|
-
```
|
|
173
|
-
|
|
174
127
|
#### Chat with Workforce
|
|
175
128
|
|
|
176
|
-
Chat with the entire workforce team and get streaming responses from all agents.
|
|
177
|
-
|
|
178
129
|
```python
|
|
179
|
-
|
|
130
|
+
conversation = workforce.new_conversation()
|
|
180
131
|
response = workforce.chat_stream(conversation.id, "How can you help me with this project?")
|
|
181
132
|
for chunk in response:
|
|
182
133
|
if chunk.text:
|
|
183
134
|
print(chunk.text, end="")
|
|
184
|
-
if chunk.cot:
|
|
185
|
-
print(f" [COT: {chunk.cot}]")
|
|
186
135
|
```
|
|
187
136
|
|
|
188
|
-
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Custom Agent Evaluations
|
|
140
|
+
|
|
141
|
+
See **[EVALUATIONS.md](EVALUATIONS.md)** for full documentation — installation, dataset builder, framework examples (OpenAI, Anthropic, Google, LangChain, CrewAI, AutoGen, LlamaIndex, HTTP endpoints), and the complete API reference.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/agentx-python/)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## A fast way to build AI Agents and create agent workforces
|
|
8
|
+
|
|
9
|
+
The official AgentX Python SDK for [AgentX](https://www.agentx.so/)
|
|
10
|
+
|
|
11
|
+
Why build AI agents with AgentX?
|
|
12
|
+
|
|
13
|
+
- Simplicity — Agent → Conversation → Message structure.
|
|
14
|
+
- Chain-of-thought built in.
|
|
15
|
+
- Choose from most open and closed-source LLM vendors.
|
|
16
|
+
- Built-in Voice (ASR, TTS), Image Gen, Document, CSV/Excel, OCR, and more.
|
|
17
|
+
- Support for all running MCP (Model Context Protocol) servers.
|
|
18
|
+
- RAG with built-in re-rank.
|
|
19
|
+
- Multi-agent workforce orchestration.
|
|
20
|
+
- Multiple agents working together with a designated manager agent.
|
|
21
|
+
- Cross-LLM-vendor, multi-agent orchestration.
|
|
22
|
+
- A2A — agent-to-agent protocol (coming soon)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install --upgrade agentx-python
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from agentx import AgentX
|
|
34
|
+
|
|
35
|
+
client = AgentX(api_key="your-api-key-here")
|
|
36
|
+
|
|
37
|
+
agents = client.list_agents()
|
|
38
|
+
print(f"You have {len(agents)} agents")
|
|
39
|
+
|
|
40
|
+
if agents:
|
|
41
|
+
agent = agents[0]
|
|
42
|
+
conversation = agent.new_conversation()
|
|
43
|
+
response = conversation.chat("Hello! What can you help me with?")
|
|
44
|
+
print(response)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Provide an `api_key` inline or set `AGENTX_API_KEY` as an environment variable.
|
|
50
|
+
Get your API key at https://app.agentx.so
|
|
51
|
+
|
|
52
|
+
### Agent
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from agentx import AgentX
|
|
56
|
+
|
|
57
|
+
client = AgentX(api_key="<your api key here>")
|
|
58
|
+
print(client.list_agents())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Conversation
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
my_agent = client.get_agent(id="<agent id here>")
|
|
65
|
+
|
|
66
|
+
existing_conversations = my_agent.list_conversations()
|
|
67
|
+
last_conversation = existing_conversations[-1]
|
|
68
|
+
msgs = last_conversation.list_messages()
|
|
69
|
+
print(msgs)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Chat
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
a_conversation = my_agent.get_conversation(id="<conversation id here>")
|
|
76
|
+
|
|
77
|
+
response = a_conversation.chat_stream("Hello, what is your name?")
|
|
78
|
+
for chunk in response:
|
|
79
|
+
print(chunk)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
\*`cot` stands for chain-of-thought
|
|
83
|
+
|
|
84
|
+
### Workforce
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from agentx import AgentX
|
|
88
|
+
|
|
89
|
+
client = AgentX(api_key="<your api key here>")
|
|
90
|
+
|
|
91
|
+
workforces = client.list_workforces()
|
|
92
|
+
workforce = workforces[0]
|
|
93
|
+
print(f"Workforce: {workforce.name}")
|
|
94
|
+
print(f"Manager: {workforce.manager.name}")
|
|
95
|
+
print(f"Agents: {[agent.name for agent in workforce.agents]}")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Chat with Workforce
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
conversation = workforce.new_conversation()
|
|
102
|
+
response = workforce.chat_stream(conversation.id, "How can you help me with this project?")
|
|
103
|
+
for chunk in response:
|
|
104
|
+
if chunk.text:
|
|
105
|
+
print(chunk.text, end="")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Custom Agent Evaluations
|
|
111
|
+
|
|
112
|
+
See **[EVALUATIONS.md](EVALUATIONS.md)** for full documentation — installation, dataset builder, framework examples (OpenAI, Anthropic, Google, LangChain, CrewAI, AutoGen, LlamaIndex, HTTP endpoints), and the complete API reference.
|
|
@@ -3,37 +3,50 @@ 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
|
+
_eval_client = EvaluationsClient(
|
|
27
|
+
api_key=self.api_key,
|
|
28
|
+
sdk_version=VERSION,
|
|
29
|
+
base_url=self.base_url,
|
|
30
|
+
)
|
|
31
|
+
self.evaluations = EvaluationsRunner(_eval_client)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_env(cls) -> "AgentX":
|
|
35
|
+
"""Create an AgentX client using AGENTX_API_KEY (and optionally AGENTX_API_BASE_URL) from the environment."""
|
|
36
|
+
return cls()
|
|
37
|
+
|
|
18
38
|
def get_agent(self, id: str) -> Agent:
|
|
19
|
-
url = f"
|
|
39
|
+
url = f"{api_base()}/access/agents/{id}"
|
|
20
40
|
# Make a GET request to the AgentX API
|
|
21
41
|
response = requests.get(url, headers=get_headers())
|
|
22
42
|
# Check if response was successful
|
|
23
43
|
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
|
-
)
|
|
44
|
+
return Agent(**response.json())
|
|
32
45
|
else:
|
|
33
46
|
raise Exception(f"Failed to retrieve agent: {response.reason}")
|
|
34
47
|
|
|
35
48
|
def list_agents(self) -> List[Agent]:
|
|
36
|
-
url = "
|
|
49
|
+
url = f"{api_base()}/access/agents"
|
|
37
50
|
# Make a GET request to the AgentX API
|
|
38
51
|
response = requests.get(url, headers=get_headers())
|
|
39
52
|
# Check if response was successful
|
|
@@ -45,7 +58,7 @@ class AgentX:
|
|
|
45
58
|
@staticmethod
|
|
46
59
|
def list_workforces() -> List["Workforce"]:
|
|
47
60
|
"""List all workforces/teams."""
|
|
48
|
-
url = "
|
|
61
|
+
url = f"{api_base()}/access/teams"
|
|
49
62
|
response = requests.get(url, headers=get_headers())
|
|
50
63
|
if response.status_code == 200:
|
|
51
64
|
return [Workforce(**workforce) for workforce in response.json()]
|
|
@@ -56,7 +69,7 @@ class AgentX:
|
|
|
56
69
|
|
|
57
70
|
def get_profile(self):
|
|
58
71
|
"""Get the current user's profile information."""
|
|
59
|
-
url = "
|
|
72
|
+
url = f"{api_base()}/access/getProfile"
|
|
60
73
|
response = requests.get(url, headers=get_headers())
|
|
61
74
|
if response.status_code == 200:
|
|
62
75
|
return response.json()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Minimal ANSI terminal helpers — no external dependencies."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import itertools
|
|
5
|
+
import sys
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
_IS_TTY = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _c(code: str) -> str:
|
|
13
|
+
return f"\033[{code}m" if _IS_TTY else ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
RESET = _c("0")
|
|
17
|
+
BOLD = _c("1")
|
|
18
|
+
DIM = _c("2")
|
|
19
|
+
GREEN = _c("32")
|
|
20
|
+
YELLOW = _c("33")
|
|
21
|
+
RED = _c("31")
|
|
22
|
+
CYAN = _c("36")
|
|
23
|
+
MAGENTA = _c("35")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def green(s: str) -> str: return f"{GREEN}{s}{RESET}"
|
|
27
|
+
def yellow(s: str) -> str: return f"{YELLOW}{s}{RESET}"
|
|
28
|
+
def red(s: str) -> str: return f"{RED}{s}{RESET}"
|
|
29
|
+
def cyan(s: str) -> str: return f"{CYAN}{s}{RESET}"
|
|
30
|
+
def bold(s: str) -> str: return f"{BOLD}{s}{RESET}"
|
|
31
|
+
def dim(s: str) -> str: return f"{DIM}{s}{RESET}"
|
|
32
|
+
def magenta(s: str) -> str: return f"{MAGENTA}{s}{RESET}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Spinner:
|
|
36
|
+
"""Inline spinner that overwrites the current line on a TTY."""
|
|
37
|
+
|
|
38
|
+
_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
39
|
+
|
|
40
|
+
def __init__(self, message: str):
|
|
41
|
+
self._message = message
|
|
42
|
+
self._stop = threading.Event()
|
|
43
|
+
self._thread: threading.Thread | None = None
|
|
44
|
+
|
|
45
|
+
def __enter__(self) -> "Spinner":
|
|
46
|
+
if not _IS_TTY:
|
|
47
|
+
print(f" {self._message}...", flush=True)
|
|
48
|
+
return self
|
|
49
|
+
self._stop.clear()
|
|
50
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
|
51
|
+
self._thread.start()
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def __exit__(self, *_) -> None:
|
|
55
|
+
if not _IS_TTY:
|
|
56
|
+
return
|
|
57
|
+
self._stop.set()
|
|
58
|
+
if self._thread:
|
|
59
|
+
self._thread.join()
|
|
60
|
+
sys.stdout.write(f"\r{' ' * (len(self._message) + 12)}\r")
|
|
61
|
+
sys.stdout.flush()
|
|
62
|
+
|
|
63
|
+
def _spin(self) -> None:
|
|
64
|
+
for frame in itertools.cycle(self._FRAMES):
|
|
65
|
+
if self._stop.is_set():
|
|
66
|
+
break
|
|
67
|
+
sys.stdout.write(f"\r {CYAN}{frame}{RESET} {self._message} {DIM}(this may take ~60s+){RESET}")
|
|
68
|
+
sys.stdout.flush()
|
|
69
|
+
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,33 @@
|
|
|
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(str(case.question_index))
|
|
31
|
+
if raw is None:
|
|
32
|
+
raw = ""
|
|
33
|
+
return normalize_result(case, raw, latency_ms=0)
|
|
@@ -0,0 +1,35 @@
|
|
|
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(self, case: EvaluationCase, latency_ms: int | None = None) -> EvaluationResult:
|
|
27
|
+
import time
|
|
28
|
+
start = time.monotonic()
|
|
29
|
+
try:
|
|
30
|
+
raw = self._fn(case)
|
|
31
|
+
except Exception as exc:
|
|
32
|
+
elapsed = int((time.monotonic() - start) * 1000)
|
|
33
|
+
return normalize_error(case, exc, latency_ms=elapsed)
|
|
34
|
+
elapsed = int((time.monotonic() - start) * 1000)
|
|
35
|
+
return normalize_result(case, raw, latency_ms=elapsed)
|