agentx-python 0.4.9__py3-none-any.whl → 0.4.10__py3-none-any.whl

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/agentx.py CHANGED
@@ -23,6 +23,7 @@ class AgentX:
23
23
  from agentx.evaluations.client import EvaluationsClient
24
24
  from agentx.evaluations.runner import EvaluationsRunner
25
25
  from agentx.version import VERSION
26
+
26
27
  _eval_client = EvaluationsClient(
27
28
  api_key=self.api_key,
28
29
  sdk_version=VERSION,
@@ -1,4 +1,5 @@
1
1
  """Minimal ANSI terminal helpers — no external dependencies."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import itertools
@@ -13,23 +14,42 @@ def _c(code: str) -> str:
13
14
  return f"\033[{code}m" if _IS_TTY else ""
14
15
 
15
16
 
16
- RESET = _c("0")
17
- BOLD = _c("1")
18
- DIM = _c("2")
19
- GREEN = _c("32")
17
+ RESET = _c("0")
18
+ BOLD = _c("1")
19
+ DIM = _c("2")
20
+ GREEN = _c("32")
20
21
  YELLOW = _c("33")
21
- RED = _c("31")
22
- CYAN = _c("36")
22
+ RED = _c("31")
23
+ CYAN = _c("36")
23
24
  MAGENTA = _c("35")
24
25
 
25
26
 
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}"
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}"
33
53
 
34
54
 
35
55
  class Spinner:
@@ -64,6 +84,8 @@ class Spinner:
64
84
  for frame in itertools.cycle(self._FRAMES):
65
85
  if self._stop.is_set():
66
86
  break
67
- sys.stdout.write(f"\r {CYAN}{frame}{RESET} {self._message} {DIM}(this may take ~60s+){RESET}")
87
+ sys.stdout.write(
88
+ f"\r {CYAN}{frame}{RESET} {self._message} {DIM}(this may take ~60s+){RESET}"
89
+ )
68
90
  sys.stdout.flush()
69
91
  time.sleep(0.08)
@@ -27,7 +27,9 @@ class PrecomputedAdapter:
27
27
  self._lookup = {str(k): v for k, v in outputs.items()}
28
28
 
29
29
  def run(self, case: EvaluationCase) -> EvaluationResult:
30
- raw = self._lookup.get(case.case_id) or self._lookup.get(str(case.question_index))
30
+ raw = self._lookup.get(case.case_id) or self._lookup.get(
31
+ str(case.question_index)
32
+ )
31
33
  if raw is None:
32
34
  raw = ""
33
35
  return normalize_result(case, raw, latency_ms=0)
@@ -23,8 +23,11 @@ class RawCallableAdapter:
23
23
  def __init__(self, fn: Callable[[EvaluationCase], Any]):
24
24
  self._fn = fn
25
25
 
26
- def run(self, case: EvaluationCase, latency_ms: int | None = None) -> EvaluationResult:
26
+ def run(
27
+ self, case: EvaluationCase, latency_ms: int | None = None
28
+ ) -> EvaluationResult:
27
29
  import time
30
+
28
31
  start = time.monotonic()
29
32
  try:
30
33
  raw = self._fn(case)
@@ -42,26 +42,33 @@ class AgentXValidationError(AgentXEvaluationsError):
42
42
 
43
43
 
44
44
  class EvaluationsClient:
45
- def __init__(self, api_key: str, sdk_version: str = "unknown", base_url: str = None):
45
+ def __init__(
46
+ self, api_key: str, sdk_version: str = "unknown", base_url: str = None
47
+ ):
46
48
  if not api_key:
47
49
  raise AgentXAuthError("AGENTX_API_KEY is required")
48
50
  self._api_key = api_key
49
51
  self._sdk_version = sdk_version
50
52
  # Priority: constructor arg > env var > SDK default
51
53
  # Always append /custom-agent-evaluations so users only need to provide /api/v1
52
- _api_base = (base_url or os.getenv("AGENTX_API_BASE_URL", _UTIL_API_BASE)).rstrip("/")
54
+ _api_base = (
55
+ base_url or os.getenv("AGENTX_API_BASE_URL", _UTIL_API_BASE)
56
+ ).rstrip("/")
53
57
  if not _api_base.endswith("/custom-agent-evaluations"):
54
58
  _api_base = f"{_api_base}/custom-agent-evaluations"
55
59
  self._base_url = _api_base
56
60
  self._session = requests.Session()
57
- self._session.headers.update({
58
- "x-api-key": self._api_key,
59
- "Content-Type": "application/json",
60
- "User-Agent": f"{SDK_NAME}/{self._sdk_version}",
61
- "accept": "*/*",
62
- })
61
+ self._session.headers.update(
62
+ {
63
+ "x-api-key": self._api_key,
64
+ "Content-Type": "application/json",
65
+ "User-Agent": f"{SDK_NAME}/{self._sdk_version}",
66
+ "accept": "*/*",
67
+ }
68
+ )
63
69
  # Expose dataset builder factory
64
70
  from agentx.evaluations.datasets import DatasetClient
71
+
65
72
  self.datasets = DatasetClient(self)
66
73
 
67
74
  # ------------------------------------------------------------------
@@ -86,7 +93,9 @@ class EvaluationsClient:
86
93
  if resp.status_code == 422:
87
94
  raise AgentXValidationError(resp.text)
88
95
  if resp.status_code in _RETRYABLE_STATUS and attempt < _MAX_RETRIES - 1:
89
- logger.debug("Retryable status %d (attempt %d)", resp.status_code, attempt + 1)
96
+ logger.debug(
97
+ "Retryable status %d (attempt %d)", resp.status_code, attempt + 1
98
+ )
90
99
  last_exc = AgentXEvaluationsError(f"HTTP {resp.status_code}")
91
100
  continue
92
101
  if not resp.ok:
@@ -107,7 +116,10 @@ class EvaluationsClient:
107
116
 
108
117
  def list_datasets(self) -> List[Dataset]:
109
118
  data = self._request("GET", "/datasets")
110
- return [Dataset(**d) for d in (data if isinstance(data, list) else data.get("datasets", []))]
119
+ return [
120
+ Dataset(**d)
121
+ for d in (data if isinstance(data, list) else data.get("datasets", []))
122
+ ]
111
123
 
112
124
  def get_dataset(self, dataset_id: str) -> Dataset:
113
125
  data = self._request("GET", f"/datasets/{dataset_id}")
@@ -124,6 +136,7 @@ class EvaluationsClient:
124
136
  python_version: Optional[str] = None,
125
137
  ) -> EvaluationRun:
126
138
  from agentx.version import VERSION
139
+
127
140
  payload = {
128
141
  "datasetId": dataset_id,
129
142
  "evaluationSubject": subject.model_dump(by_alias=True, exclude_none=True),
@@ -138,7 +151,9 @@ class EvaluationsClient:
138
151
  data = self._request("POST", "/runs", json=payload)
139
152
  return EvaluationRun(**data)
140
153
 
141
- def append_results(self, run_id: str, batch_id: str, results: List[EvaluationResult]) -> BatchAppendResponse:
154
+ def append_results(
155
+ self, run_id: str, batch_id: str, results: List[EvaluationResult]
156
+ ) -> BatchAppendResponse:
142
157
  payload = {
143
158
  "batchId": batch_id,
144
159
  "results": [_result_to_payload(r) for r in results],
@@ -147,7 +162,9 @@ class EvaluationsClient:
147
162
  return BatchAppendResponse(**data)
148
163
 
149
164
  def finalize_run(self, run_id: str) -> Dict[str, Any]:
150
- return self._request("POST", f"/runs/{run_id}/finalize", json={"status": "completed"})
165
+ return self._request(
166
+ "POST", f"/runs/{run_id}/finalize", json={"status": "completed"}
167
+ )
151
168
 
152
169
  def analyze_run(self, run_id: str) -> Dict[str, Any]:
153
170
  return self._request("POST", f"/runs/{run_id}/analyze", json={}, timeout=300)
@@ -168,6 +185,7 @@ class EvaluationsClient:
168
185
  # Helpers
169
186
  # ---------------------------------------------------------------------------
170
187
 
188
+
171
189
  def _result_to_payload(r: EvaluationResult) -> dict:
172
190
  d = r.model_dump(by_alias=True, exclude_none=True)
173
191
  # Rename snake_case fields the model may have stored locally
@@ -180,4 +198,5 @@ def _result_to_payload(r: EvaluationResult) -> dict:
180
198
 
181
199
  def _python_version() -> str:
182
200
  import sys
201
+
183
202
  return f"{sys.version_info.major}.{sys.version_info.minor}"
@@ -57,16 +57,22 @@ class DatasetBuilder:
57
57
  main["expectedKnowledgeBase"] = expected_knowledge_base
58
58
  if expected_delegations:
59
59
  main["expectedDelegations"] = expected_delegations
60
- self._payload["questions"].append({
61
- "main_question": main,
62
- "follow_up_questions": follow_up_questions or [],
63
- })
60
+ self._payload["questions"].append(
61
+ {
62
+ "main_question": main,
63
+ "follow_up_questions": follow_up_questions or [],
64
+ }
65
+ )
64
66
  return self
65
67
 
66
68
  def publish(self) -> Dataset:
67
69
  if not self._payload["questions"]:
68
70
  raise ValueError("Dataset must have at least one case before publishing")
69
- logger.info("Publishing dataset '%s' with %d case(s)", self._payload["name"], len(self._payload["questions"]))
71
+ logger.info(
72
+ "Publishing dataset '%s' with %d case(s)",
73
+ self._payload["name"],
74
+ len(self._payload["questions"]),
75
+ )
70
76
  return self._client.create_dataset(self._payload)
71
77
 
72
78
  # ------------------------------------------------------------------
@@ -109,9 +115,15 @@ class DatasetBuilder:
109
115
  builder.add_case(
110
116
  query=query,
111
117
  expected_results=row.get("expected_results", "").strip() or None,
112
- expected_capabilities=_split_semi(row.get("expected_capabilities", "")),
113
- expected_knowledge_base=_split_semi(row.get("expected_knowledge_base", "")),
114
- expected_delegations=_split_semi(row.get("expected_delegations", "")),
118
+ expected_capabilities=_split_semi(
119
+ row.get("expected_capabilities", "")
120
+ ),
121
+ expected_knowledge_base=_split_semi(
122
+ row.get("expected_knowledge_base", "")
123
+ ),
124
+ expected_delegations=_split_semi(
125
+ row.get("expected_delegations", "")
126
+ ),
115
127
  )
116
128
 
117
129
  if invalid:
@@ -150,7 +162,9 @@ class DatasetBuilder:
150
162
  query=query,
151
163
  expected_results=_str_or_none(row.get("expected_results")),
152
164
  expected_capabilities=_split_semi(row.get("expected_capabilities", "")),
153
- expected_knowledge_base=_split_semi(row.get("expected_knowledge_base", "")),
165
+ expected_knowledge_base=_split_semi(
166
+ row.get("expected_knowledge_base", "")
167
+ ),
154
168
  expected_delegations=_split_semi(row.get("expected_delegations", "")),
155
169
  )
156
170
 
@@ -204,6 +218,7 @@ class DatasetClient:
204
218
  # Helpers
205
219
  # ---------------------------------------------------------------------------
206
220
 
221
+
207
222
  def _split_semi(value: Any) -> Optional[List[str]]:
208
223
  if not value:
209
224
  return None
@@ -1,13 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import Any, Dict, List, Literal, Optional, Union
4
- from pydantic import BaseModel, Field
5
-
4
+ from pydantic import BaseModel, Field, model_validator
6
5
 
7
6
  # ---------------------------------------------------------------------------
8
7
  # Observable trace
9
8
  # ---------------------------------------------------------------------------
10
9
 
10
+
11
11
  class TraceEvent(BaseModel):
12
12
  type: str
13
13
  name: Optional[str] = None
@@ -30,6 +30,7 @@ class ObservableTrace(BaseModel):
30
30
  # Dataset
31
31
  # ---------------------------------------------------------------------------
32
32
 
33
+
33
34
  class TestCase(BaseModel):
34
35
  query: str
35
36
  expected_results: Optional[str] = None
@@ -68,8 +69,17 @@ class Dataset(BaseModel):
68
69
  # ---------------------------------------------------------------------------
69
70
 
70
71
  FrameworkKind = Literal[
71
- "raw_python", "openai", "anthropic", "google", "langchain", "llamaindex",
72
- "crewai", "autogen", "n8n", "flowise", "other"
72
+ "raw_python",
73
+ "openai",
74
+ "anthropic",
75
+ "google",
76
+ "langchain",
77
+ "llamaindex",
78
+ "crewai",
79
+ "autogen",
80
+ "n8n",
81
+ "flowise",
82
+ "other",
73
83
  ]
74
84
 
75
85
  RuntimeKind = Literal["local", "ci", "customer_hosted", "low_code"]
@@ -93,10 +103,15 @@ class EvaluationSubject(BaseModel):
93
103
  # Run
94
104
  # ---------------------------------------------------------------------------
95
105
 
106
+
96
107
  class ServerLimits(BaseModel):
97
108
  max_batch_size: int = Field(default=10, alias="maxBatchSize")
98
- max_trace_bytes_per_result: int = Field(default=20000, alias="maxTraceBytesPerResult")
99
- max_metadata_bytes_per_result: int = Field(default=4000, alias="maxMetadataBytesPerResult")
109
+ max_trace_bytes_per_result: int = Field(
110
+ default=20000, alias="maxTraceBytesPerResult"
111
+ )
112
+ max_metadata_bytes_per_result: int = Field(
113
+ default=4000, alias="maxMetadataBytesPerResult"
114
+ )
100
115
 
101
116
  class Config:
102
117
  populate_by_name = True
@@ -119,6 +134,7 @@ class EvaluationRun(BaseModel):
119
134
  # Evaluation case (one item from the dataset, passed to the user's callable)
120
135
  # ---------------------------------------------------------------------------
121
136
 
137
+
122
138
  class EvaluationCase(BaseModel):
123
139
  case_id: str
124
140
  question_index: int
@@ -137,6 +153,7 @@ class EvaluationCase(BaseModel):
137
153
  # Result produced by the user's callable and normalised by the SDK
138
154
  # ---------------------------------------------------------------------------
139
155
 
156
+
140
157
  class ResultError(BaseModel):
141
158
  type: str
142
159
  message: str
@@ -162,7 +179,9 @@ class EvaluationResult(BaseModel):
162
179
  run_number: int
163
180
  input: Dict[str, Any]
164
181
  output: Optional[Dict[str, Any]] = None
165
- observable_trace: Optional[ObservableTrace] = Field(default=None, alias="observableTrace")
182
+ observable_trace: Optional[ObservableTrace] = Field(
183
+ default=None, alias="observableTrace"
184
+ )
166
185
  error: Optional[ResultError] = None
167
186
  timings: Optional[ResultTimings] = None
168
187
  metadata: Optional[Dict[str, Any]] = None
@@ -177,6 +196,7 @@ class EvaluationResult(BaseModel):
177
196
  # Scored result (returned by API after scoring)
178
197
  # ---------------------------------------------------------------------------
179
198
 
199
+
180
200
  class ScoredResult(BaseModel):
181
201
  idempotency_key: str = Field(alias="idempotencyKey")
182
202
  rating: Optional[float] = None
@@ -191,6 +211,7 @@ class ScoredResult(BaseModel):
191
211
  # Batch append response
192
212
  # ---------------------------------------------------------------------------
193
213
 
214
+
194
215
  class BatchAppendResponse(BaseModel):
195
216
  run_id: str = Field(alias="runId")
196
217
  batch_id: str = Field(alias="batchId")
@@ -198,7 +219,9 @@ class BatchAppendResponse(BaseModel):
198
219
  duplicates: int = 0
199
220
  failed_validation: int = Field(default=0, alias="failedValidation")
200
221
  status: str = "in_progress"
201
- scored_results: List[ScoredResult] = Field(default_factory=list, alias="scoredResults")
222
+ scored_results: List[ScoredResult] = Field(
223
+ default_factory=list, alias="scoredResults"
224
+ )
202
225
 
203
226
  class Config:
204
227
  populate_by_name = True
@@ -209,11 +232,14 @@ class BatchAppendResponse(BaseModel):
209
232
  # Analysis / report
210
233
  # ---------------------------------------------------------------------------
211
234
 
235
+
212
236
  class ReportStatistics(BaseModel):
213
237
  number_of_runs: int = Field(default=0, alias="numberOfRuns")
214
238
  average_rating: float = Field(default=0.0, alias="averageRating")
215
239
  min_rating: float = Field(default=0.0, alias="minRating")
216
240
  max_rating: float = Field(default=0.0, alias="maxRating")
241
+ cosine_similarity: Optional[float] = Field(default=None, alias="cosineSimilarity")
242
+ jaccard_similarity: Optional[float] = Field(default=None, alias="jaccardSimilarity")
217
243
 
218
244
  class Config:
219
245
  populate_by_name = True
@@ -242,7 +268,9 @@ class ReportResponsePatterns(BaseModel):
242
268
 
243
269
  class ReportReasoningAnalysis(BaseModel):
244
270
  cot_quality: Optional[str] = Field(default=None, alias="cotQuality")
245
- reasoning_patterns: List[str] = Field(default_factory=list, alias="reasoningPatterns")
271
+ reasoning_patterns: List[str] = Field(
272
+ default_factory=list, alias="reasoningPatterns"
273
+ )
246
274
  reasoning_gaps: List[str] = Field(default_factory=list, alias="reasoningGaps")
247
275
  rating: Optional[str] = None
248
276
 
@@ -278,17 +306,83 @@ class Report(BaseModel):
278
306
  statistics: Optional[ReportStatistics] = None
279
307
  summary: Optional[str] = None
280
308
  consistency_score: Optional[float] = Field(default=None, alias="consistencyScore")
281
- instruction_adherence: Optional[ReportInstructionAdherence] = Field(default=None, alias="instructionAdherence")
282
- response_patterns: Optional[ReportResponsePatterns] = Field(default=None, alias="responsePatterns")
283
- reasoning_analysis: Optional[ReportReasoningAnalysis] = Field(default=None, alias="reasoningAnalysis")
284
- tool_usage_analysis: Optional[ReportToolUsageAnalysis] = Field(default=None, alias="toolUsageAnalysis")
309
+ instruction_adherence: Optional[ReportInstructionAdherence] = Field(
310
+ default=None, alias="instructionAdherence"
311
+ )
312
+ response_patterns: Optional[ReportResponsePatterns] = Field(
313
+ default=None, alias="responsePatterns"
314
+ )
315
+ reasoning_analysis: Optional[ReportReasoningAnalysis] = Field(
316
+ default=None, alias="reasoningAnalysis"
317
+ )
318
+ tool_usage_analysis: Optional[ReportToolUsageAnalysis] = Field(
319
+ default=None, alias="toolUsageAnalysis"
320
+ )
285
321
  strengths: List[str] = Field(default_factory=list)
286
322
  weaknesses: List[str] = Field(default_factory=list)
287
323
  overall_rating: Optional[str] = Field(default=None, alias="overallRating")
288
324
  recommendations: List[ReportRecommendation] = Field(default_factory=list)
289
- low_scoring_cases: List[Dict[str, Any]] = Field(default_factory=list, alias="lowScoringCases")
325
+ low_scoring_cases: List[Dict[str, Any]] = Field(
326
+ default_factory=list, alias="lowScoringCases"
327
+ )
290
328
  dashboard_url: Optional[str] = Field(default=None, alias="dashboardUrl")
291
329
 
292
330
  class Config:
293
331
  populate_by_name = True
294
332
  extra = "ignore"
333
+
334
+ @model_validator(mode="before")
335
+ @classmethod
336
+ def _hoist_similarity_into_statistics(cls, data: Any) -> Any:
337
+ """Backend may send similarity metrics either at the top level (e.g.
338
+ ``cosineSimilarity``) or nested under ``statistics``. Normalize so the
339
+ nested form is always populated when either is present."""
340
+ if not isinstance(data, dict):
341
+ return data
342
+ stats = data.get("statistics")
343
+ stats = dict(stats) if isinstance(stats, dict) else {}
344
+ for top_key, nested_key in (
345
+ ("cosineSimilarity", "cosineSimilarity"),
346
+ ("cosine_similarity", "cosine_similarity"),
347
+ ("jaccardSimilarity", "jaccardSimilarity"),
348
+ ("jaccard_similarity", "jaccard_similarity"),
349
+ ):
350
+ top_val = data.get(top_key)
351
+ if top_val is None:
352
+ continue
353
+ if (
354
+ stats.get("cosineSimilarity") is None
355
+ and stats.get("cosine_similarity") is None
356
+ and "cosine" in nested_key.lower()
357
+ ):
358
+ stats[nested_key] = top_val
359
+ if (
360
+ stats.get("jaccardSimilarity") is None
361
+ and stats.get("jaccard_similarity") is None
362
+ and "jaccard" in nested_key.lower()
363
+ ):
364
+ stats[nested_key] = top_val
365
+ if stats:
366
+ data["statistics"] = stats
367
+ return data
368
+
369
+ @property
370
+ def cosine_similarity(self) -> Optional[float]:
371
+ """Average cosine similarity across scored results, or ``None`` if the
372
+ metric was not enabled for the dataset or no result has a value yet."""
373
+ return (
374
+ self.statistics.cosine_similarity if self.statistics is not None else None
375
+ )
376
+
377
+ @property
378
+ def jaccard_similarity(self) -> Optional[float]:
379
+ """Average Jaccard similarity across scored results, or ``None`` if the
380
+ metric was not enabled for the dataset or no result has a value yet."""
381
+ return (
382
+ self.statistics.jaccard_similarity if self.statistics is not None else None
383
+ )
384
+
385
+ @property
386
+ def average_rating(self) -> Optional[float]:
387
+ """Convenience accessor matching cosine_similarity / jaccard_similarity."""
388
+ return self.statistics.average_rating if self.statistics is not None else None
@@ -5,10 +5,10 @@ from typing import Any, Dict
5
5
 
6
6
  # Patterns that look like secrets
7
7
  _SECRET_PATTERNS = [
8
- re.compile(r"sk-[A-Za-z0-9]{20,}"), # OpenAI / Anthropic style keys
8
+ re.compile(r"sk-[A-Za-z0-9]{20,}"), # OpenAI / Anthropic style keys
9
9
  re.compile(r"Bearer\s+[A-Za-z0-9\-._~+/]+=*", re.IGNORECASE),
10
10
  re.compile(r"(?i)(password|passwd|secret|token|api[_-]?key)\s*[=:]\s*\S+"),
11
- re.compile(r"[A-Za-z0-9+/]{40,}={0,2}"), # long base64-like strings
11
+ re.compile(r"[A-Za-z0-9+/]{40,}={0,2}"), # long base64-like strings
12
12
  ]
13
13
 
14
14
  _REDACTED = "[REDACTED]"
@@ -33,9 +33,21 @@ def redact_dict(obj: Any, _depth: int = 0) -> Any:
33
33
 
34
34
 
35
35
  _SENSITIVE_KEYS = {
36
- "password", "passwd", "secret", "token", "api_key", "apikey",
37
- "authorization", "auth", "cookie", "session", "credential",
38
- "private_key", "privatekey", "access_key", "accesskey",
36
+ "password",
37
+ "passwd",
38
+ "secret",
39
+ "token",
40
+ "api_key",
41
+ "apikey",
42
+ "authorization",
43
+ "auth",
44
+ "cookie",
45
+ "session",
46
+ "credential",
47
+ "private_key",
48
+ "privatekey",
49
+ "access_key",
50
+ "accesskey",
39
51
  }
40
52
 
41
53
 
@@ -2,15 +2,23 @@ from __future__ import annotations
2
2
 
3
3
  from agentx.evaluations.models import Report
4
4
  from agentx.evaluations._term import (
5
- bold, cyan, dim, green, yellow, red, magenta, RESET, BOLD,
5
+ bold,
6
+ cyan,
7
+ dim,
8
+ green,
9
+ yellow,
10
+ red,
11
+ magenta,
12
+ RESET,
13
+ BOLD,
6
14
  )
7
15
 
8
- _SEP = "─" * 60
16
+ _SEP = "─" * 60
9
17
  _THIN = "─" * 40
10
18
 
11
19
  _RATING_COLORS = {"high": green, "medium": yellow, "low": red}
12
- _RATING_ICONS = {"high": "●", "medium": "◑", "low": "○"}
13
- _PRI_COLORS = {"high": red, "medium": yellow, "low": dim}
20
+ _RATING_ICONS = {"high": "●", "medium": "◑", "low": "○"}
21
+ _PRI_COLORS = {"high": red, "medium": yellow, "low": dim}
14
22
 
15
23
 
16
24
  def _rating_badge(rating: str | None) -> str:
@@ -32,14 +40,32 @@ def print_report(report: Report) -> None:
32
40
  print(cyan(_SEP))
33
41
  print(f" {dim('Run :')} {dim(report.run_id)}")
34
42
  print(f" {dim('Dataset :')} {dim(report.dataset_id)}")
35
- print(f" {dim('Status :')} {green(report.status) if report.status == 'completed' else yellow(report.status)}")
43
+ print(
44
+ f" {dim('Status :')} {green(report.status) if report.status == 'completed' else yellow(report.status)}"
45
+ )
36
46
 
37
47
  if report.statistics:
38
48
  s = report.statistics
39
49
  avg = s.average_rating
40
50
  avg_color = green if avg >= 7 else (yellow if avg >= 4 else red)
41
51
  print(f" {dim('Cases :')} {s.number_of_runs}")
42
- print(f" {dim('Rating :')} {avg_color(f'{avg:.1f}/10')} {dim(f'(min {s.min_rating:.1f} / max {s.max_rating:.1f})')}")
52
+ print(
53
+ f" {dim('Rating :')} {avg_color(f'{avg:.1f}/10')} {dim(f'(min {s.min_rating:.1f} / max {s.max_rating:.1f})')}"
54
+ )
55
+
56
+ cos = report.cosine_similarity
57
+ if cos is not None:
58
+ cos_color = green if cos >= 0.85 else (yellow if cos >= 0.6 else red)
59
+ print(
60
+ f" {dim('Cosine :')} {cos_color(f'{cos * 100:.1f}%')} {dim('(vector similarity)')}"
61
+ )
62
+
63
+ jac = report.jaccard_similarity
64
+ if jac is not None:
65
+ jac_color = green if jac >= 0.6 else (yellow if jac >= 0.3 else red)
66
+ print(
67
+ f" {dim('Jaccard :')} {jac_color(f'{jac * 100:.1f}%')} {dim('(token-set overlap)')}"
68
+ )
43
69
 
44
70
  if report.consistency_score is not None:
45
71
  cs = report.consistency_score
@@ -120,8 +146,8 @@ def print_report(report: Report) -> None:
120
146
  )
121
147
  for rec in sorted_recs:
122
148
  pri_fn = _PRI_COLORS.get(rec.priority or "low", dim)
123
- pri = pri_fn(f"[{rec.priority.upper()}]") if rec.priority else ""
124
- cat = dim(f"({rec.category})") if rec.category else ""
149
+ pri = pri_fn(f"[{rec.priority.upper()}]") if rec.priority else ""
150
+ cat = dim(f"({rec.category})") if rec.category else ""
125
151
  print(f" {pri} {cat} {rec.recommendation}")
126
152
  if rec.reasoning:
127
153
  print(f" {dim('→')} {dim(rec.reasoning)}")
@@ -58,9 +58,13 @@ def normalize_result(
58
58
  input_tokens = _to_int(raw.get("input_tokens"))
59
59
  output_tokens = _to_int(raw.get("output_tokens"))
60
60
  if input_tokens is None and isinstance(meta_raw, dict):
61
- input_tokens = _to_int(meta_raw.get("input_tokens") or meta_raw.get("prompt_tokens"))
61
+ input_tokens = _to_int(
62
+ meta_raw.get("input_tokens") or meta_raw.get("prompt_tokens")
63
+ )
62
64
  if output_tokens is None and isinstance(meta_raw, dict):
63
- output_tokens = _to_int(meta_raw.get("output_tokens") or meta_raw.get("completion_tokens"))
65
+ output_tokens = _to_int(
66
+ meta_raw.get("output_tokens") or meta_raw.get("completion_tokens")
67
+ )
64
68
 
65
69
  err_raw = raw.get("error")
66
70
  if err_raw:
@@ -77,7 +81,9 @@ def normalize_result(
77
81
  else:
78
82
  output = {"text": str(raw)} if raw is not None else {"text": ""}
79
83
 
80
- has_timings = latency_ms is not None or input_tokens is not None or output_tokens is not None
84
+ has_timings = (
85
+ latency_ms is not None or input_tokens is not None or output_tokens is not None
86
+ )
81
87
  return EvaluationResult(
82
88
  case_id=case.case_id,
83
89
  question_index=case.question_index,
@@ -86,12 +92,22 @@ def normalize_result(
86
92
  output=output,
87
93
  observableTrace=trace,
88
94
  error=error,
89
- timings=ResultTimings(latencyMs=latency_ms, inputTokens=input_tokens, outputTokens=output_tokens) if has_timings else None,
95
+ timings=(
96
+ ResultTimings(
97
+ latencyMs=latency_ms,
98
+ inputTokens=input_tokens,
99
+ outputTokens=output_tokens,
100
+ )
101
+ if has_timings
102
+ else None
103
+ ),
90
104
  metadata=metadata,
91
105
  )
92
106
 
93
107
 
94
- def normalize_error(case: EvaluationCase, exc: Exception, latency_ms: Optional[int] = None) -> EvaluationResult:
108
+ def normalize_error(
109
+ case: EvaluationCase, exc: Exception, latency_ms: Optional[int] = None
110
+ ) -> EvaluationResult:
95
111
  """Build a failed-case result from an exception."""
96
112
  return EvaluationResult(
97
113
  case_id=case.case_id,
@@ -20,7 +20,15 @@ from agentx.evaluations.redaction import redact_dict
20
20
  from agentx.evaluations.reporting import print_report
21
21
  from agentx.evaluations.results import normalize_result, normalize_error
22
22
  from agentx.evaluations._term import (
23
- bold, cyan, green, yellow, red, dim, BOLD, RESET, Spinner,
23
+ bold,
24
+ cyan,
25
+ green,
26
+ yellow,
27
+ red,
28
+ dim,
29
+ BOLD,
30
+ RESET,
31
+ Spinner,
24
32
  )
25
33
 
26
34
  logger = logging.getLogger(__name__)
@@ -80,7 +88,9 @@ class EvaluationRunContext:
80
88
  if display:
81
89
  print(f" {dim('Agent :')} {display} {dim(f'({framework} / {runtime})')}")
82
90
  print()
83
- print(f"{bold('Executing')} {n_q} question{'s' if n_q != 1 else ''} × {n_r} run{'s' if n_r != 1 else ''}")
91
+ print(
92
+ f"{bold('Executing')} {n_q} question{'s' if n_q != 1 else ''} × {n_r} run{'s' if n_r != 1 else ''}"
93
+ )
84
94
 
85
95
  # Resume: skip already-submitted keys
86
96
  already_done = self._fetch_submitted_keys()
@@ -120,7 +130,9 @@ class EvaluationRunContext:
120
130
  with Spinner(f"Scoring — AI is rating {n} result{'s' if n != 1 else ''}"):
121
131
  try:
122
132
  resp = self._client.append_results(self._run.run_id, batch_id, batch)
123
- print(f" {green('✓')} Scored {resp.accepted} result{'s' if resp.accepted != 1 else ''}")
133
+ print(
134
+ f" {green('✓')} Scored {resp.accepted} result{'s' if resp.accepted != 1 else ''}"
135
+ )
124
136
  logger.info(
125
137
  "Batch %s: accepted=%d duplicates=%d failed=%d",
126
138
  batch_id[:8],
@@ -229,12 +241,17 @@ class EvaluationsRunner:
229
241
  # Helpers
230
242
  # ---------------------------------------------------------------------------
231
243
 
244
+
232
245
  def _wrap_adapter(adapter: AdapterLike) -> Callable[[EvaluationCase], EvaluationResult]:
233
- if isinstance(adapter, (RawCallableAdapter, PrecomputedAdapter, HttpEndpointAdapter)):
246
+ if isinstance(
247
+ adapter, (RawCallableAdapter, PrecomputedAdapter, HttpEndpointAdapter)
248
+ ):
234
249
  return adapter.run
235
250
  if callable(adapter):
236
251
  return RawCallableAdapter(adapter).run
237
- raise TypeError(f"adapter must be callable or an Adapter instance, got {type(adapter)}")
252
+ raise TypeError(
253
+ f"adapter must be callable or an Adapter instance, got {type(adapter)}"
254
+ )
238
255
 
239
256
 
240
257
  def _build_cases(dataset: Dataset) -> List[EvaluationCase]:
@@ -243,16 +260,18 @@ def _build_cases(dataset: Dataset) -> List[EvaluationCase]:
243
260
  for q_idx, question in enumerate(dataset.questions):
244
261
  mq = question.main_question
245
262
  for run_num in range(1, n_runs + 1):
246
- cases.append(EvaluationCase(
247
- case_id=f"case-{q_idx}",
248
- question_index=q_idx,
249
- run_number=run_num,
250
- query=mq.query,
251
- expected_results=mq.expected_results,
252
- expected_capabilities=mq.expected_capabilities,
253
- expected_knowledge_base=mq.expected_knowledge_base,
254
- expected_delegations=mq.expected_delegations,
255
- ))
263
+ cases.append(
264
+ EvaluationCase(
265
+ case_id=f"case-{q_idx}",
266
+ question_index=q_idx,
267
+ run_number=run_num,
268
+ query=mq.query,
269
+ expected_results=mq.expected_results,
270
+ expected_capabilities=mq.expected_capabilities,
271
+ expected_knowledge_base=mq.expected_knowledge_base,
272
+ expected_delegations=mq.expected_delegations,
273
+ )
274
+ )
256
275
  return cases
257
276
 
258
277
 
@@ -84,7 +84,9 @@ class Workforce(BaseModel):
84
84
  self, conversation_id: str, message: str, context: int = -1
85
85
  ) -> Iterator[ChatResponse]:
86
86
  """Send a message to a team conversation and stream the response."""
87
- url = f"{api_base()}/access/teams/conversations/{conversation_id}/jsonmessagesse"
87
+ url = (
88
+ f"{api_base()}/access/teams/conversations/{conversation_id}/jsonmessagesse"
89
+ )
88
90
  response = requests.post(
89
91
  url, headers=get_headers(), json={"message": message, "context": context}
90
92
  )
agentx/version.py CHANGED
@@ -1 +1 @@
1
- VERSION = "0.4.9"
1
+ VERSION = "0.4.10"
@@ -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
+ ![Logo](https://agentx-resources.s3.us-west-1.amazonaws.com/AgentX-logo-387x60.png)
31
+
32
+ [![PyPI version](https://img.shields.io/pypi/v/agentx-python)](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,27 @@
1
+ agentx/__init__.py,sha256=AiY83tsCtjSMIBVsvNuxOgSU4i-rCREQrqDrNmCoSd0,279
2
+ agentx/agentx.py,sha256=_I2n77Db0rV3TSDZ44h1Trb6DluDoCBiMsu_KgdNU8I,3012
3
+ agentx/util.py,sha256=yOV3VVdc0KUbw5pEkSRiO2lZzEtL1vOsnB_f5tFk948,665
4
+ agentx/version.py,sha256=QnLb5RDU7syePvc1qNaT3NKjL1mC7ZCUkl8JHLvJm_8,19
5
+ agentx/evaluations/__init__.py,sha256=OmdCHiBO3Qcsck0UkOyVqSvfRs6nXbSi-2FXzyRfsog,89
6
+ agentx/evaluations/_term.py,sha256=S4blZgH66wHvkt2hR29h8JE5xnakBeD7dlZdUW9Wfoo,2065
7
+ agentx/evaluations/client.py,sha256=6y6_o0rZgMKOQLVIZEfgDhD7H_oMDXG3Em5pql7rJlU,7048
8
+ agentx/evaluations/datasets.py,sha256=_VaL6fQ8q33qpiawDr7qORQID8oJwNwMVgdNxM9FOns,8107
9
+ agentx/evaluations/models.py,sha256=G-F_LRK-pT1fYLSqVzHxSCtuRVPpmKALmR1Zr0W-gac,12773
10
+ agentx/evaluations/redaction.py,sha256=arvY9f9XxKHl6-K8P60Xzhb330nXF8a3wOp15d9i0bo,1410
11
+ agentx/evaluations/reporting.py,sha256=JJejSUhgnlYDJXEPgLqR9JoJXAPWDiu3fBGnKHM5EIM,5851
12
+ agentx/evaluations/results.py,sha256=tbUbewEpOyDlcUKxnSRk2m2pCDWk-NuHaAnmI8YZTGQ,4052
13
+ agentx/evaluations/runner.py,sha256=gJY9qSwhNmeKxMBm9AvDNk-WsdR9qU-OuasZbqYxikM,10593
14
+ agentx/evaluations/tracing.py,sha256=MSJD9bzfInMi7E70mc8mWUc0fx5DLkOgUzRru2mNQoc,1825
15
+ agentx/evaluations/adapters/__init__.py,sha256=fK8Hx75usbiY03XUSmnLnSrwBXip2CQs24YZcdEgj6w,287
16
+ agentx/evaluations/adapters/http_endpoint.py,sha256=3YDwpf9u6u3Xj8kLhERmShBeGMviKHCA0Dhr0t6KMmA,2061
17
+ agentx/evaluations/adapters/precomputed.py,sha256=BNNLQuzYFWyLSwsrMGv6MvRTroKY5Hz-Lm4F2RszESU,1210
18
+ agentx/evaluations/adapters/raw.py,sha256=FkDq_mdf21-xt5EHnGyIGcS6VZxkEkFcmM9VTd-T5sA,1130
19
+ agentx/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ agentx/resources/agent.py,sha256=ZKpxDYzJbKgOX4-W86U__b4gko7XlbwB4q6L8Az9uo0,2011
21
+ agentx/resources/conversation.py,sha256=sDaIhTQPiM3MvAKBK0SKVtBF-LppBXXfRv1-r_aFLoc,4431
22
+ agentx/resources/workforce.py,sha256=lfGVkoV9lcOp-lZScjTvwRarJMkhzfMfLm4syJDujGc,4168
23
+ agentx_python-0.4.10.dist-info/licenses/LICENSE,sha256=6ZbiPNFmv3xBb44LGhAa3PZYK0ROAztsd5LRFZDlGFE,1074
24
+ agentx_python-0.4.10.dist-info/METADATA,sha256=vpaVPSOKSN3GaQ_p-i8rKQtXwgYTYMDdzGbsaiFT6gM,6078
25
+ agentx_python-0.4.10.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
26
+ agentx_python-0.4.10.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
27
+ agentx_python-0.4.10.dist-info/RECORD,,
@@ -1,141 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: agentx-python
3
- Version: 0.4.9
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
- ![Logo](https://agentx-resources.s3.us-west-1.amazonaws.com/AgentX-logo-387x60.png)
31
-
32
- [![PyPI version](https://img.shields.io/pypi/v/agentx-python)](https://pypi.org/project/agentx-python/)
33
-
34
- ---
35
-
36
- ## A fast way to build AI Agents and create agent workforces
37
-
38
- The official AgentX Python SDK for [AgentX](https://www.agentx.so/)
39
-
40
- Why build AI agents with AgentX?
41
-
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
- - Multi-agent workforce orchestration.
49
- - Multiple agents working together with a designated manager agent.
50
- - Cross-LLM-vendor, multi-agent orchestration.
51
- - A2A — agent-to-agent protocol (coming soon)
52
-
53
- ## Installation
54
-
55
- ```bash
56
- pip install --upgrade agentx-python
57
- ```
58
-
59
- ## Quick Start
60
-
61
- ```python
62
- from agentx import AgentX
63
-
64
- client = AgentX(api_key="your-api-key-here")
65
-
66
- agents = client.list_agents()
67
- print(f"You have {len(agents)} agents")
68
-
69
- if agents:
70
- agent = agents[0]
71
- conversation = agent.new_conversation()
72
- response = conversation.chat("Hello! What can you help me with?")
73
- print(response)
74
- ```
75
-
76
- ## Usage
77
-
78
- Provide an `api_key` inline or set `AGENTX_API_KEY` as an environment variable.
79
- Get your API key at https://app.agentx.so
80
-
81
- ### Agent
82
-
83
- ```python
84
- from agentx import AgentX
85
-
86
- client = AgentX(api_key="<your api key here>")
87
- print(client.list_agents())
88
- ```
89
-
90
- ### Conversation
91
-
92
- ```python
93
- my_agent = client.get_agent(id="<agent id here>")
94
-
95
- existing_conversations = my_agent.list_conversations()
96
- last_conversation = existing_conversations[-1]
97
- msgs = last_conversation.list_messages()
98
- print(msgs)
99
- ```
100
-
101
- ### Chat
102
-
103
- ```python
104
- a_conversation = my_agent.get_conversation(id="<conversation id here>")
105
-
106
- response = a_conversation.chat_stream("Hello, what is your name?")
107
- for chunk in response:
108
- print(chunk)
109
- ```
110
-
111
- \*`cot` stands for chain-of-thought
112
-
113
- ### Workforce
114
-
115
- ```python
116
- from agentx import AgentX
117
-
118
- client = AgentX(api_key="<your api key here>")
119
-
120
- workforces = client.list_workforces()
121
- workforce = workforces[0]
122
- print(f"Workforce: {workforce.name}")
123
- print(f"Manager: {workforce.manager.name}")
124
- print(f"Agents: {[agent.name for agent in workforce.agents]}")
125
- ```
126
-
127
- #### Chat with Workforce
128
-
129
- ```python
130
- conversation = workforce.new_conversation()
131
- response = workforce.chat_stream(conversation.id, "How can you help me with this project?")
132
- for chunk in response:
133
- if chunk.text:
134
- print(chunk.text, end="")
135
- ```
136
-
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.
@@ -1,27 +0,0 @@
1
- agentx/__init__.py,sha256=AiY83tsCtjSMIBVsvNuxOgSU4i-rCREQrqDrNmCoSd0,279
2
- agentx/agentx.py,sha256=ICZ5oT-WESgkVBuYExLOAGrYoJUdGoAKYa3BsfD6W_g,3011
3
- agentx/util.py,sha256=yOV3VVdc0KUbw5pEkSRiO2lZzEtL1vOsnB_f5tFk948,665
4
- agentx/version.py,sha256=3FouI0PeO06qTMDNHCv_qKuT_7JOUBQr5L9BYxVd794,18
5
- agentx/evaluations/__init__.py,sha256=OmdCHiBO3Qcsck0UkOyVqSvfRs6nXbSi-2FXzyRfsog,89
6
- agentx/evaluations/_term.py,sha256=Vcfx89TiORVvh-tGGQfvxtBJEPJsxUv5S7LUplubaAA,2023
7
- agentx/evaluations/client.py,sha256=Wd44taWQMTjn8SdJohb8K_PIbU0Rzck76G9wpn-RO7k,6858
8
- agentx/evaluations/datasets.py,sha256=s9B-S7i83JChP9MDjtCMGPMkvz3emXohyQClJD1YVMg,7849
9
- agentx/evaluations/models.py,sha256=p9yDqJY6YP5FbMZZHS_RsngfCUb7v8LakodrtL3NQ8o,10041
10
- agentx/evaluations/redaction.py,sha256=zljolnX64kaBUiqrPGD_kSFZvb65z2CvMrryoJtwY3k,1374
11
- agentx/evaluations/reporting.py,sha256=g1zLJ_BR0IfO3VHEM0xm8wyEkgkQvLJQ-NdIW8lNMD0,5266
12
- agentx/evaluations/results.py,sha256=-K122Z1_fIaDAeP0neLI9EXwEnPESbu8z5qo_aXRrZY,3859
13
- agentx/evaluations/runner.py,sha256=5_QIpU8PSAQniFdubW1CnJK4CQ3HQhyN1u683t926hA,10406
14
- agentx/evaluations/tracing.py,sha256=MSJD9bzfInMi7E70mc8mWUc0fx5DLkOgUzRru2mNQoc,1825
15
- agentx/evaluations/adapters/__init__.py,sha256=fK8Hx75usbiY03XUSmnLnSrwBXip2CQs24YZcdEgj6w,287
16
- agentx/evaluations/adapters/http_endpoint.py,sha256=3YDwpf9u6u3Xj8kLhERmShBeGMviKHCA0Dhr0t6KMmA,2061
17
- agentx/evaluations/adapters/precomputed.py,sha256=yuJrDv6SH1mRSCz3EgxPSUu9iBJeTY2uJflK5S0QxcM,1188
18
- agentx/evaluations/adapters/raw.py,sha256=AqSHTM3p45LK4Wo1lkUboclMY97SYFx9kzDQ6GNbGA8,1115
19
- agentx/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- agentx/resources/agent.py,sha256=ZKpxDYzJbKgOX4-W86U__b4gko7XlbwB4q6L8Az9uo0,2011
21
- agentx/resources/conversation.py,sha256=sDaIhTQPiM3MvAKBK0SKVtBF-LppBXXfRv1-r_aFLoc,4431
22
- agentx/resources/workforce.py,sha256=QpyQNBQR24MbcAPtrPZZsdfSuXDSOyNmlcfWeHudXmY,4144
23
- agentx_python-0.4.9.dist-info/licenses/LICENSE,sha256=6ZbiPNFmv3xBb44LGhAa3PZYK0ROAztsd5LRFZDlGFE,1074
24
- agentx_python-0.4.9.dist-info/METADATA,sha256=-o556esk8h8G-_rhhOxXTUbfUaV4Z_-xK3i1mzK9DYo,3691
25
- agentx_python-0.4.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
26
- agentx_python-0.4.9.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
27
- agentx_python-0.4.9.dist-info/RECORD,,