agentx-python 0.6.7__tar.gz → 0.6.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {agentx_python-0.6.7 → agentx_python-0.6.8}/PKG-INFO +12 -3
  2. {agentx_python-0.6.7 → agentx_python-0.6.8}/README.md +11 -2
  3. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/agentx.py +2 -1
  4. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/datasets.py +32 -1
  5. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/evaluation_settings.py +13 -0
  6. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/models.py +52 -4
  7. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/results.py +6 -0
  8. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/runner.py +33 -5
  9. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/monitor/__init__.py +5 -1
  10. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/monitor/client.py +35 -1
  11. agentx_python-0.6.8/agentx/monitor/models.py +85 -0
  12. agentx_python-0.6.8/agentx/monitor/signals.py +41 -0
  13. agentx_python-0.6.8/agentx/version.py +1 -0
  14. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx_python.egg-info/PKG-INFO +12 -3
  15. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx_python.egg-info/SOURCES.txt +1 -0
  16. agentx_python-0.6.7/agentx/monitor/models.py +0 -37
  17. agentx_python-0.6.7/agentx/version.py +0 -1
  18. {agentx_python-0.6.7 → agentx_python-0.6.8}/LICENSE +0 -0
  19. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/__init__.py +0 -0
  20. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/__init__.py +0 -0
  21. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/_term.py +0 -0
  22. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/adapters/__init__.py +0 -0
  23. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/adapters/http_endpoint.py +0 -0
  24. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/adapters/precomputed.py +0 -0
  25. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/adapters/raw.py +0 -0
  26. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/client.py +0 -0
  27. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/redaction.py +0 -0
  28. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/reporting.py +0 -0
  29. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/evaluations/tracing.py +0 -0
  30. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/exceptions.py +0 -0
  31. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/__init__.py +0 -0
  32. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/_perf.py +0 -0
  33. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/anthropic.py +0 -0
  34. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/crewai.py +0 -0
  35. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/google_adk.py +0 -0
  36. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/google_genai.py +0 -0
  37. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/langchain.py +0 -0
  38. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/integrations/openai_agents.py +0 -0
  39. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/monitor/patterns.py +0 -0
  40. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/py.typed +0 -0
  41. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/resources/__init__.py +0 -0
  42. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/resources/agent.py +0 -0
  43. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/resources/conversation.py +0 -0
  44. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/resources/workforce.py +0 -0
  45. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/tracing/__init__.py +0 -0
  46. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/tracing/ci_types.py +0 -0
  47. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/tracing/ingest_client.py +0 -0
  48. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/tracing/tracer.py +0 -0
  49. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx/util.py +0 -0
  50. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx_python.egg-info/dependency_links.txt +0 -0
  51. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx_python.egg-info/not-zip-safe +0 -0
  52. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx_python.egg-info/requires.txt +0 -0
  53. {agentx_python-0.6.7 → agentx_python-0.6.8}/agentx_python.egg-info/top_level.txt +0 -0
  54. {agentx_python-0.6.7 → agentx_python-0.6.8}/setup.cfg +0 -0
  55. {agentx_python-0.6.7 → agentx_python-0.6.8}/setup.py +0 -0
  56. {agentx_python-0.6.7 → agentx_python-0.6.8}/tests/test_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.7
3
+ Version: 0.6.8
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -85,7 +85,7 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
85
85
  - **MCP support** — connect any Model Context Protocol server.
86
86
  - **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
87
87
  - **Production tracing** — one decorator or context manager records every agent run (input, output, latency, tool calls, token usage) into your workspace, for any framework.
88
- - **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
88
+ - **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics. Configurable judge prompt/model (OpenAI or Anthropic), per-question judge guidelines, and smoke testing for phrasing robustness.
89
89
  - **A2A** — Each agent can be published with agent-to-agent protocol compatible.
90
90
 
91
91
  ---
@@ -266,6 +266,13 @@ with client.tracer.trace("support-agent", monitor=True, pattern_ids=[pattern.id]
266
266
 
267
267
  This works independently of the dashboard's per-agent monitoring toggle (Governance > Observe > Agents), which still auto-checks every trace from an agent once enabled there, with no code changes needed either way.
268
268
 
269
+ Read the resulting alerts back with `client.monitor.signals.list()`/`.get()`, no dashboard required:
270
+
271
+ ```python
272
+ for signal in client.monitor.signals.list(severity="high"):
273
+ print(signal.summary, signal.occurrence_count)
274
+ ```
275
+
269
276
  See **[TRACING.md](TRACING.md)** for the complete Monitor guide.
270
277
 
271
278
  ---
@@ -293,7 +300,9 @@ print(report.recommendations) # list of prioritized, actionable fixes
293
300
 
294
301
  `.analyze()` also generates a full qualitative report (strengths, weaknesses, instruction adherence, reasoning quality, and recommendations), running the same durable, multi-judge pipeline as the dashboard's "Analyze" button. `analyze(mode=..., quality_mode=..., judges=[...])` controls how items are scored and by which models. See [AI analysis report](EVALUATIONS.md#ai-analysis-report) in the full guide for the complete field and parameter reference.
295
302
 
296
- See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide dataset builder, framework adapters, similarity metrics, and the complete API reference.
303
+ Ask a case's question several extra ways each run, LLM-paraphrased server-side, to catch agents that break on phrasing rather than substance, and override the judge's prompt/model per config, see [Smoke testing](EVALUATIONS.md#smoke-testing-phrasing-robustness) and [Configuring the judge](EVALUATIONS.md#configuring-the-judge) in the full guide.
304
+
305
+ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, smoke testing, judge configuration, and the complete API reference.
297
306
 
298
307
  ---
299
308
 
@@ -37,7 +37,7 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
37
37
  - **MCP support** — connect any Model Context Protocol server.
38
38
  - **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
39
39
  - **Production tracing** — one decorator or context manager records every agent run (input, output, latency, tool calls, token usage) into your workspace, for any framework.
40
- - **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
40
+ - **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics. Configurable judge prompt/model (OpenAI or Anthropic), per-question judge guidelines, and smoke testing for phrasing robustness.
41
41
  - **A2A** — Each agent can be published with agent-to-agent protocol compatible.
42
42
 
43
43
  ---
@@ -218,6 +218,13 @@ with client.tracer.trace("support-agent", monitor=True, pattern_ids=[pattern.id]
218
218
 
219
219
  This works independently of the dashboard's per-agent monitoring toggle (Governance > Observe > Agents), which still auto-checks every trace from an agent once enabled there, with no code changes needed either way.
220
220
 
221
+ Read the resulting alerts back with `client.monitor.signals.list()`/`.get()`, no dashboard required:
222
+
223
+ ```python
224
+ for signal in client.monitor.signals.list(severity="high"):
225
+ print(signal.summary, signal.occurrence_count)
226
+ ```
227
+
221
228
  See **[TRACING.md](TRACING.md)** for the complete Monitor guide.
222
229
 
223
230
  ---
@@ -245,7 +252,9 @@ print(report.recommendations) # list of prioritized, actionable fixes
245
252
 
246
253
  `.analyze()` also generates a full qualitative report (strengths, weaknesses, instruction adherence, reasoning quality, and recommendations), running the same durable, multi-judge pipeline as the dashboard's "Analyze" button. `analyze(mode=..., quality_mode=..., judges=[...])` controls how items are scored and by which models. See [AI analysis report](EVALUATIONS.md#ai-analysis-report) in the full guide for the complete field and parameter reference.
247
254
 
248
- See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide dataset builder, framework adapters, similarity metrics, and the complete API reference.
255
+ Ask a case's question several extra ways each run, LLM-paraphrased server-side, to catch agents that break on phrasing rather than substance, and override the judge's prompt/model per config, see [Smoke testing](EVALUATIONS.md#smoke-testing-phrasing-robustness) and [Configuring the judge](EVALUATIONS.md#configuring-the-judge) in the full guide.
256
+
257
+ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, smoke testing, judge configuration, and the complete API reference.
249
258
 
250
259
  ---
251
260
 
@@ -43,7 +43,8 @@ class AgentX:
43
43
  self.evaluations = EvaluationsRunner(_eval_client)
44
44
 
45
45
  # Monitor: create/reuse patterns (client.monitor.patterns) that a trace can be checked
46
- # against at send time via tracer.trace(..., monitor=True, pattern_ids=[...]).
46
+ # against at send time via tracer.trace(..., monitor=True, pattern_ids=[...]), then read
47
+ # back the resulting alerts/findings with client.monitor.signals.
47
48
  self.monitor = MonitorClient(
48
49
  api_key=self.api_key,
49
50
  sdk_version=VERSION,
@@ -27,6 +27,8 @@ class DatasetBuilder:
27
27
  acceptance_criteria: Optional[str] = None,
28
28
  rejection_criteria: Optional[str] = None,
29
29
  evaluation_criteria: Optional[str] = None,
30
+ judge_prompt: Optional[str] = None,
31
+ judge_model: Optional[str] = None,
30
32
  vector_similarity: bool = False,
31
33
  jaccard_similarity: bool = False,
32
34
  bleu_score: bool = False,
@@ -44,6 +46,13 @@ class DatasetBuilder:
44
46
  "evaluationCriteria": evaluation_criteria,
45
47
  "questions": [],
46
48
  }
49
+ # LLM-as-judge overrides for this dataset's own grading config. Omit either to keep the
50
+ # server default (raw prompt template / OpenAI gpt-5.5, see EVALUATIONS.md). judge_model
51
+ # must be one of client.evaluations.list_models() (OpenAI or Anthropic).
52
+ if judge_prompt is not None:
53
+ self._payload["judgePrompt"] = judge_prompt
54
+ if judge_model is not None:
55
+ self._payload["judgeModel"] = judge_model
47
56
  # Opt-in similarity metrics, surfaced on the report as cosine_similarity /
48
57
  # jaccard_similarity / bleu_score / rouge_score (computed against each
49
58
  # case's expected_results).
@@ -74,8 +83,20 @@ class DatasetBuilder:
74
83
  expected_knowledge_base: Optional[List[str]] = None,
75
84
  expected_delegations: Optional[List[str]] = None,
76
85
  follow_up_questions: Optional[List[Dict[str, Any]]] = None,
86
+ judge_guideline: Optional[str] = None,
87
+ smoke_test_count: Optional[int] = None,
88
+ smoke_test_guidance: Optional[str] = None,
77
89
  ) -> "DatasetBuilder":
78
- main = {"query": query}
90
+ """Add a case. `judge_guideline` is optional grading guidance specific to this question.
91
+
92
+ `smoke_test_count`, when set (1-10), asks this question that many extra ways each
93
+ evaluation run, LLM-paraphrased server-side, to catch agents that are brittle to phrasing
94
+ rather than genuinely wrong. `smoke_test_guidance` optionally steers what kind of variants
95
+ get generated (e.g. tone, adversarial phrasing, different languages); the SDK never
96
+ generates or counts variants itself, both fields are only ever consumed server-side.
97
+ Ignored on `follow_up_questions`, only the opening question of a case can be smoke-tested.
98
+ """
99
+ main: Dict[str, Any] = {"query": query}
79
100
  if expected_results:
80
101
  main["expectedResults"] = expected_results
81
102
  if expected_capabilities:
@@ -84,6 +105,12 @@ class DatasetBuilder:
84
105
  main["expectedKnowledgeBase"] = expected_knowledge_base
85
106
  if expected_delegations:
86
107
  main["expectedDelegations"] = expected_delegations
108
+ if judge_guideline:
109
+ main["judgeGuideline"] = judge_guideline
110
+ if smoke_test_count:
111
+ main["smokeTest"] = {"enabled": True, "count": smoke_test_count}
112
+ if smoke_test_guidance:
113
+ main["smokeTest"]["guidance"] = smoke_test_guidance
87
114
  self._payload["questions"].append(
88
115
  {
89
116
  "main_question": main,
@@ -217,6 +244,8 @@ class DatasetClient:
217
244
  acceptance_criteria: Optional[str] = None,
218
245
  rejection_criteria: Optional[str] = None,
219
246
  evaluation_criteria: Optional[str] = None,
247
+ judge_prompt: Optional[str] = None,
248
+ judge_model: Optional[str] = None,
220
249
  vector_similarity: bool = False,
221
250
  jaccard_similarity: bool = False,
222
251
  bleu_score: bool = False,
@@ -232,6 +261,8 @@ class DatasetClient:
232
261
  acceptance_criteria=acceptance_criteria,
233
262
  rejection_criteria=rejection_criteria,
234
263
  evaluation_criteria=evaluation_criteria,
264
+ judge_prompt=judge_prompt,
265
+ judge_model=judge_model,
235
266
  vector_similarity=vector_similarity,
236
267
  jaccard_similarity=jaccard_similarity,
237
268
  bleu_score=bleu_score,
@@ -24,6 +24,8 @@ class EvaluationSettingsBuilder:
24
24
  acceptance_criteria: Optional[str] = None,
25
25
  rejection_criteria: Optional[str] = None,
26
26
  evaluation_criteria: Optional[str] = None,
27
+ judge_prompt: Optional[str] = None,
28
+ judge_model: Optional[str] = None,
27
29
  vector_similarity: bool = False,
28
30
  jaccard_similarity: bool = False,
29
31
  bleu_score: bool = False,
@@ -40,6 +42,13 @@ class EvaluationSettingsBuilder:
40
42
  "rejectionCriteria": rejection_criteria,
41
43
  "evaluationCriteria": evaluation_criteria,
42
44
  }
45
+ # LLM-as-judge overrides. Omit either to keep the server default (raw prompt template /
46
+ # OpenAI gpt-5.5, see EVALUATIONS.md). judge_model must be one of
47
+ # client.evaluations.list_models() (OpenAI or Anthropic).
48
+ if judge_prompt is not None:
49
+ self._payload["judgePrompt"] = judge_prompt
50
+ if judge_model is not None:
51
+ self._payload["judgeModel"] = judge_model
43
52
  # Opt-in similarity metrics, mirrors DatasetBuilder's config kwargs.
44
53
  if vector_similarity:
45
54
  vs: Dict[str, Any] = {"enabled": True}
@@ -79,6 +88,8 @@ class EvaluationSettingsClient:
79
88
  acceptance_criteria: Optional[str] = None,
80
89
  rejection_criteria: Optional[str] = None,
81
90
  evaluation_criteria: Optional[str] = None,
91
+ judge_prompt: Optional[str] = None,
92
+ judge_model: Optional[str] = None,
82
93
  vector_similarity: bool = False,
83
94
  jaccard_similarity: bool = False,
84
95
  bleu_score: bool = False,
@@ -94,6 +105,8 @@ class EvaluationSettingsClient:
94
105
  acceptance_criteria=acceptance_criteria,
95
106
  rejection_criteria=rejection_criteria,
96
107
  evaluation_criteria=evaluation_criteria,
108
+ judge_prompt=judge_prompt,
109
+ judge_model=judge_model,
97
110
  vector_similarity=vector_similarity,
98
111
  jaccard_similarity=jaccard_similarity,
99
112
  bleu_score=bleu_score,
@@ -31,14 +31,30 @@ class ObservableTrace(BaseModel):
31
31
  # ---------------------------------------------------------------------------
32
32
 
33
33
 
34
+ class SmokeTestSettings(BaseModel):
35
+ """Only meaningful on a question's main_question. See DatasetBuilder.add_case's
36
+ smoke_test_count/smoke_test_guidance for how to set this."""
37
+
38
+ enabled: bool = False
39
+ count: int = 1
40
+ guidance: Optional[str] = None
41
+
42
+ class Config:
43
+ populate_by_name = True
44
+ extra = "ignore"
45
+
46
+
34
47
  class TestCase(BaseModel):
35
48
  query: str
36
- expected_results: Optional[str] = None
37
- expected_capabilities: Optional[List[str]] = None
38
- expected_knowledge_base: Optional[List[str]] = None
39
- expected_delegations: Optional[List[str]] = None
49
+ expected_results: Optional[str] = Field(default=None, alias="expectedResults")
50
+ expected_capabilities: Optional[List[str]] = Field(default=None, alias="expectedCapabilities")
51
+ expected_knowledge_base: Optional[List[str]] = Field(default=None, alias="expectedKnowledgeBase")
52
+ expected_delegations: Optional[List[str]] = Field(default=None, alias="expectedDelegations")
53
+ judge_guideline: Optional[str] = Field(default=None, alias="judgeGuideline")
54
+ smoke_test: Optional[SmokeTestSettings] = Field(default=None, alias="smokeTest")
40
55
 
41
56
  class Config:
57
+ populate_by_name = True
42
58
  extra = "ignore"
43
59
 
44
60
 
@@ -89,6 +105,10 @@ class EvaluationSettings(BaseModel):
89
105
  acceptance_criteria: Optional[str] = Field(default=None, alias="acceptanceCriteria")
90
106
  rejection_criteria: Optional[str] = Field(default=None, alias="rejectionCriteria")
91
107
  evaluation_criteria: Optional[str] = Field(default=None, alias="evaluationCriteria")
108
+ # LLM-as-judge overrides. None means "use the server default" (raw prompt template / OpenAI
109
+ # gpt-5.5). See client.evaluations.settings.builder(judge_prompt=..., judge_model=...).
110
+ judge_prompt: Optional[str] = Field(default=None, alias="judgePrompt")
111
+ judge_model: Optional[str] = Field(default=None, alias="judgeModel")
92
112
  status: str = "published"
93
113
  # Sovereignty & Portability — models selected to compare when this config runs.
94
114
  # Hoisted from the nested ``sovereigntyIndex`` object when enabled.
@@ -202,12 +222,30 @@ class LiveStatistics(BaseModel):
202
222
  extra = "ignore"
203
223
 
204
224
 
225
+ class SmokeTestVariantGroup(BaseModel):
226
+ """Paraphrased variants for one question, generated server-side (reusing the same
227
+ generation the dashboard's native runs use) and frozen for the lifetime of the run.
228
+ The SDK never generates or counts these itself, it only consumes what's returned here."""
229
+
230
+ question_index: int = Field(alias="questionIndex")
231
+ variants: List[str] = Field(default_factory=list)
232
+
233
+ class Config:
234
+ populate_by_name = True
235
+ extra = "ignore"
236
+
237
+
205
238
  class EvaluationRun(BaseModel):
206
239
  run_id: str = Field(alias="runId")
207
240
  dataset_id: str = Field(alias="datasetId")
208
241
  dataset_version_id: Optional[str] = Field(default=None, alias="datasetVersionId")
209
242
  status: str = "in_progress"
210
243
  limits: ServerLimits = Field(default_factory=ServerLimits)
244
+ # Present only when at least one question in the dataset has smokeTest.enabled. See
245
+ # SmokeTestVariantGroup.
246
+ smoke_test_variants: Optional[List[SmokeTestVariantGroup]] = Field(
247
+ default=None, alias="smokeTestVariants"
248
+ )
211
249
 
212
250
  class Config:
213
251
  populate_by_name = True
@@ -232,6 +270,12 @@ class EvaluationCase(BaseModel):
232
270
  # dataset selects comparison models; your callable can read it to pick the
233
271
  # model. The SDK also tags the submitted result with it.
234
272
  model: Optional[str] = None
273
+ # Smoke test: True when `query` is a server-generated paraphrase variant rather than the
274
+ # dataset's original question text (see SmokeTestVariantGroup). Your callable doesn't need
275
+ # to branch on this, `query` is already the text to ask, but it's available if you want to
276
+ # log or handle variants differently.
277
+ is_smoke_test_variant: bool = False
278
+ smoke_test_variant_text: Optional[str] = None
235
279
 
236
280
  class Config:
237
281
  extra = "ignore"
@@ -278,6 +322,10 @@ class EvaluationResult(BaseModel):
278
322
  # the dashboard's "Message Trace Details -> Execution Timeline" render the full execution
279
323
  # trace for this case, not just the lightweight observable_trace events above.
280
324
  trace_id: Optional[str] = Field(default=None, alias="traceId")
325
+ # Smoke test: set by execute() from the originating EvaluationCase, not something you need to
326
+ # set yourself when returning a plain str/dict from your callable.
327
+ is_smoke_test_variant: Optional[bool] = Field(default=None, alias="isSmokeTestVariant")
328
+ smoke_test_variant_text: Optional[str] = Field(default=None, alias="smokeTestVariantText")
281
329
 
282
330
  class Config:
283
331
  populate_by_name = True
@@ -30,6 +30,8 @@ def normalize_result(
30
30
  raw.case_id = case.case_id
31
31
  raw.question_index = case.question_index
32
32
  raw.run_number = case.run_number
33
+ raw.is_smoke_test_variant = case.is_smoke_test_variant
34
+ raw.smoke_test_variant_text = case.smoke_test_variant_text
33
35
  return raw
34
36
 
35
37
  output: Optional[dict] = None
@@ -106,6 +108,8 @@ def normalize_result(
106
108
  ),
107
109
  metadata=metadata,
108
110
  traceId=trace_id,
111
+ isSmokeTestVariant=case.is_smoke_test_variant,
112
+ smokeTestVariantText=case.smoke_test_variant_text,
109
113
  )
110
114
 
111
115
 
@@ -126,4 +130,6 @@ def normalize_error(
126
130
  retryable=False,
127
131
  ),
128
132
  timings=ResultTimings(latencyMs=latency_ms) if latency_ms is not None else None,
133
+ isSmokeTestVariant=case.is_smoke_test_variant,
134
+ smokeTestVariantText=case.smoke_test_variant_text,
129
135
  )
@@ -93,7 +93,7 @@ class EvaluationRunContext:
93
93
  def execute(self, adapter: AdapterLike) -> "EvaluationRunContext":
94
94
  """Run all cases locally and submit batches to AgentX."""
95
95
  normalized = _wrap_adapter(adapter)
96
- cases = _build_cases(self._dataset, self._evaluation_settings)
96
+ cases = _build_cases(self._dataset, self._run, self._evaluation_settings)
97
97
  max_batch = self._run.limits.max_batch_size
98
98
 
99
99
  # Banner
@@ -108,6 +108,7 @@ class EvaluationRunContext:
108
108
  if self._evaluation_settings
109
109
  else self._dataset.number_of_requests
110
110
  )
111
+ n_smoke = sum(1 for c in cases if c.is_smoke_test_variant)
111
112
 
112
113
  print(cyan(sep))
113
114
  print(f" {bold('AgentX Evaluation')} {dim('—')} {name}")
@@ -116,9 +117,11 @@ class EvaluationRunContext:
116
117
  if display:
117
118
  print(f" {dim('Agent :')} {display} {dim(f'({framework} / {runtime})')}")
118
119
  print()
119
- print(
120
- f"{bold('Executing')} {n_q} question{'s' if n_q != 1 else ''} × {n_r} run{'s' if n_r != 1 else ''}"
121
- )
120
+ exec_line = f"{bold('Executing')} {n_q} question{'s' if n_q != 1 else ''} × {n_r} run{'s' if n_r != 1 else ''}"
121
+ if n_smoke:
122
+ variant_word = "variant" if n_smoke == 1 else "variants"
123
+ exec_line += f" {dim(f'(+{n_smoke} smoke-test {variant_word})')}"
124
+ print(exec_line)
122
125
 
123
126
  # Resume: skip already-submitted keys
124
127
  already_done = self._fetch_submitted_keys()
@@ -400,7 +403,9 @@ def _wrap_adapter(adapter: AdapterLike) -> Callable[[EvaluationCase], Evaluation
400
403
 
401
404
 
402
405
  def _build_cases(
403
- dataset: Dataset, evaluation_settings: Optional[EvaluationSettings] = None
406
+ dataset: Dataset,
407
+ run: EvaluationRun,
408
+ evaluation_settings: Optional[EvaluationSettings] = None,
404
409
  ) -> List[EvaluationCase]:
405
410
  cases: List[EvaluationCase] = []
406
411
  # When an independent evaluation_settings was chosen (evaluation_settings_id
@@ -419,6 +424,14 @@ def _build_cases(
419
424
  evaluation_settings.sovereignty_models if evaluation_settings else dataset.sovereignty_models
420
425
  )
421
426
  models: List[Optional[str]] = list(sovereignty_models) or [None]
427
+ # Smoke test: variant text is generated and counted entirely server-side (POST /runs, reusing
428
+ # the same generation the dashboard's native runs use) and handed back on `run`. The SDK never
429
+ # re-derives eligibility, count, or text itself, it only turns what the server already decided
430
+ # into extra cases. Not multiplied across sovereignty_models, matching the server's counting in
431
+ # finalize/missing-results (one case per variant per question, regardless of comparison models).
432
+ smoke_variants_by_question = {
433
+ group.question_index: group.variants for group in (run.smoke_test_variants or [])
434
+ }
422
435
  for q_idx, question in enumerate(dataset.questions):
423
436
  mq = question.main_question
424
437
  for run_num in range(1, n_runs + 1):
@@ -437,6 +450,21 @@ def _build_cases(
437
450
  model=model,
438
451
  )
439
452
  )
453
+ for variant_idx, variant_text in enumerate(smoke_variants_by_question.get(q_idx, [])):
454
+ cases.append(
455
+ EvaluationCase(
456
+ case_id=f"case-{q_idx}",
457
+ question_index=q_idx,
458
+ run_number=n_runs + variant_idx + 1,
459
+ query=variant_text,
460
+ expected_results=mq.expected_results,
461
+ expected_capabilities=mq.expected_capabilities,
462
+ expected_knowledge_base=mq.expected_knowledge_base,
463
+ expected_delegations=mq.expected_delegations,
464
+ is_smoke_test_variant=True,
465
+ smoke_test_variant_text=variant_text,
466
+ )
467
+ )
440
468
  return cases
441
469
 
442
470
 
@@ -1,10 +1,14 @@
1
1
  from agentx.monitor.client import MonitorClient
2
- from agentx.monitor.models import MonitorPattern
2
+ from agentx.monitor.models import MonitorPattern, MonitorSignal, SignalOccurrence
3
3
  from agentx.monitor.patterns import MonitorPatternBuilder, MonitorPatternClient
4
+ from agentx.monitor.signals import MonitorSignalClient
4
5
 
5
6
  __all__ = [
6
7
  "MonitorClient",
7
8
  "MonitorPattern",
8
9
  "MonitorPatternBuilder",
9
10
  "MonitorPatternClient",
11
+ "MonitorSignal",
12
+ "SignalOccurrence",
13
+ "MonitorSignalClient",
10
14
  ]
@@ -7,7 +7,7 @@ from typing import Any, List, Optional
7
7
 
8
8
  import requests
9
9
 
10
- from agentx.monitor.models import MonitorPattern
10
+ from agentx.monitor.models import MonitorPattern, MonitorSignal
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -69,8 +69,10 @@ class MonitorClient:
69
69
  )
70
70
 
71
71
  from agentx.monitor.patterns import MonitorPatternClient
72
+ from agentx.monitor.signals import MonitorSignalClient
72
73
 
73
74
  self.patterns = MonitorPatternClient(self)
75
+ self.signals = MonitorSignalClient(self)
74
76
 
75
77
  # ------------------------------------------------------------------
76
78
  # Low-level HTTP
@@ -132,3 +134,35 @@ class MonitorClient:
132
134
  "GET", f"/patterns/{pattern_id}", params=self._workspace_params()
133
135
  )
134
136
  return MonitorPattern(**data["pattern"])
137
+
138
+ # ------------------------------------------------------------------
139
+ # Signal endpoints
140
+ # ------------------------------------------------------------------
141
+
142
+ def list_signals(
143
+ self,
144
+ polarity: Optional[str] = None,
145
+ status: Optional[str] = None,
146
+ severity: Optional[str] = None,
147
+ agent_id: Optional[str] = None,
148
+ limit: int = 50,
149
+ ) -> List[MonitorSignal]:
150
+ params = {**(self._workspace_params() or {})}
151
+ if polarity is not None:
152
+ params["polarity"] = polarity
153
+ if status is not None:
154
+ params["status"] = status
155
+ if severity is not None:
156
+ params["severity"] = severity
157
+ if agent_id is not None:
158
+ params["agentId"] = agent_id
159
+ if limit is not None:
160
+ params["limit"] = limit
161
+ data = self._request("GET", "/signals", params=params)
162
+ return [MonitorSignal(**s) for s in data.get("signals", [])]
163
+
164
+ def get_signal(self, signal_id: str) -> MonitorSignal:
165
+ data = self._request(
166
+ "GET", f"/signals/{signal_id}", params=self._workspace_params()
167
+ )
168
+ return MonitorSignal(**data["signal"])
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class MonitorPattern(BaseModel):
8
+ """A detection rule checked against production traces. Built via
9
+ ``client.monitor.patterns.builder(...).publish()`` and referenced by id as a
10
+ ``pattern_ids`` entry in ``tracer.trace(..., monitor=True, pattern_ids=[...])``.
11
+
12
+ A "failure" pattern (the default) raises a signal to triage; a "proper" pattern logs a
13
+ healthy tally instead. Only one of ``include_terms``/``regex``/``semantic_prompt`` is
14
+ meaningful at a time, selected by ``detector_kind``.
15
+ """
16
+
17
+ id: str = Field(alias="_id")
18
+ key: str
19
+ name: str
20
+ description: Optional[str] = None
21
+ category: Optional[str] = None
22
+ detector_kind: str = Field(default="contains", alias="detectorKind")
23
+ match_target: List[str] = Field(default_factory=lambda: ["response"], alias="matchTarget")
24
+ match_mode: str = Field(default="any", alias="matchMode")
25
+ include_terms: List[str] = Field(default_factory=list, alias="includeTerms")
26
+ exclude_terms: List[str] = Field(default_factory=list, alias="excludeTerms")
27
+ regex: Optional[str] = None
28
+ semantic_prompt: Optional[str] = Field(default=None, alias="semanticPrompt")
29
+ severity: str = "medium"
30
+ polarity: str = "failure"
31
+ enabled: bool = True
32
+ sample_rate: float = Field(default=1.0, alias="sampleRate")
33
+ scope_mode: str = Field(default="all", alias="scopeMode")
34
+ agent_ids: List[str] = Field(default_factory=list, alias="agentIds")
35
+
36
+ class Config:
37
+ extra = "ignore"
38
+
39
+
40
+ class SignalOccurrence(BaseModel):
41
+ """One hit behind a signal, capped at the server's most recent N per signal."""
42
+
43
+ agent_id: Optional[Dict[str, Any]] = Field(default=None, alias="agentId")
44
+ conversation_id: Optional[str] = Field(default=None, alias="conversationId")
45
+ message_id: Optional[str] = Field(default=None, alias="messageId")
46
+ trace_id: Optional[str] = Field(default=None, alias="traceId")
47
+ seen_at: Optional[str] = Field(default=None, alias="seenAt")
48
+
49
+ class Config:
50
+ populate_by_name = True
51
+ extra = "ignore"
52
+
53
+
54
+ class MonitorSignal(BaseModel):
55
+ """An alert/finding produced when a trace matched a pattern (or, for a "proper" pattern,
56
+ a healthy tally). Read-only from the SDK, see ``client.monitor.signals.list()/get()``, a
57
+ signal is the system's output from checking traces against patterns, not something an SDK
58
+ caller constructs."""
59
+
60
+ id: str = Field(alias="_id")
61
+ workspace_id: Optional[str] = Field(default=None, alias="workspaceId")
62
+ # Populated to {"_id", "name", "avatar"} when set, since the server populates this field.
63
+ agent_id: Optional[Dict[str, Any]] = Field(default=None, alias="agentId")
64
+ conversation_id: Optional[str] = Field(default=None, alias="conversationId")
65
+ message_id: Optional[str] = Field(default=None, alias="messageId")
66
+ type: str
67
+ severity: str = "medium"
68
+ polarity: str = "failure"
69
+ status: str = "open"
70
+ score: Optional[float] = None
71
+ threshold: Optional[float] = None
72
+ summary: str
73
+ pattern_key: str = Field(alias="patternKey")
74
+ evidence: Optional[Dict[str, Any]] = None
75
+ root_cause: Optional[str] = Field(default=None, alias="rootCause")
76
+ recommended_actions: List[str] = Field(default_factory=list, alias="recommendedActions")
77
+ review_status: Optional[str] = Field(default=None, alias="reviewStatus")
78
+ first_seen_at: Optional[str] = Field(default=None, alias="firstSeenAt")
79
+ last_seen_at: Optional[str] = Field(default=None, alias="lastSeenAt")
80
+ occurrence_count: int = Field(default=1, alias="occurrenceCount")
81
+ occurrences: List[SignalOccurrence] = Field(default_factory=list)
82
+
83
+ class Config:
84
+ populate_by_name = True
85
+ extra = "ignore"
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import List, Optional, TYPE_CHECKING
5
+
6
+ from agentx.monitor.models import MonitorSignal
7
+
8
+ if TYPE_CHECKING:
9
+ from agentx.monitor.client import MonitorClient
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class MonitorSignalClient:
15
+ """Thin wrapper surfaced as ``client.monitor.signals``. Read-only: a signal is produced by
16
+ checking a trace against patterns (see ``tracer.trace(..., monitor=True, pattern_ids=[...])``),
17
+ not something the SDK creates directly."""
18
+
19
+ def __init__(self, client: "MonitorClient"):
20
+ self._client = client
21
+
22
+ def list(
23
+ self,
24
+ polarity: Optional[str] = None,
25
+ status: Optional[str] = None,
26
+ severity: Optional[str] = None,
27
+ agent_id: Optional[str] = None,
28
+ limit: int = 50,
29
+ ) -> List[MonitorSignal]:
30
+ """List this workspace's signals, most recently seen first.
31
+
32
+ `polarity` defaults server-side to failures only ("proper", the healthy tally, is
33
+ excluded); pass ``polarity="all"`` to include both, or ``"proper"``/``"failure"`` to
34
+ narrow to one kind. `limit` is capped at 100 server-side.
35
+ """
36
+ return self._client.list_signals(
37
+ polarity=polarity, status=status, severity=severity, agent_id=agent_id, limit=limit
38
+ )
39
+
40
+ def get(self, signal_id: str) -> MonitorSignal:
41
+ return self._client.get_signal(signal_id)
@@ -0,0 +1 @@
1
+ VERSION = "0.6.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.7
3
+ Version: 0.6.8
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -85,7 +85,7 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
85
85
  - **MCP support** — connect any Model Context Protocol server.
86
86
  - **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
87
87
  - **Production tracing** — one decorator or context manager records every agent run (input, output, latency, tool calls, token usage) into your workspace, for any framework.
88
- - **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
88
+ - **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics. Configurable judge prompt/model (OpenAI or Anthropic), per-question judge guidelines, and smoke testing for phrasing robustness.
89
89
  - **A2A** — Each agent can be published with agent-to-agent protocol compatible.
90
90
 
91
91
  ---
@@ -266,6 +266,13 @@ with client.tracer.trace("support-agent", monitor=True, pattern_ids=[pattern.id]
266
266
 
267
267
  This works independently of the dashboard's per-agent monitoring toggle (Governance > Observe > Agents), which still auto-checks every trace from an agent once enabled there, with no code changes needed either way.
268
268
 
269
+ Read the resulting alerts back with `client.monitor.signals.list()`/`.get()`, no dashboard required:
270
+
271
+ ```python
272
+ for signal in client.monitor.signals.list(severity="high"):
273
+ print(signal.summary, signal.occurrence_count)
274
+ ```
275
+
269
276
  See **[TRACING.md](TRACING.md)** for the complete Monitor guide.
270
277
 
271
278
  ---
@@ -293,7 +300,9 @@ print(report.recommendations) # list of prioritized, actionable fixes
293
300
 
294
301
  `.analyze()` also generates a full qualitative report (strengths, weaknesses, instruction adherence, reasoning quality, and recommendations), running the same durable, multi-judge pipeline as the dashboard's "Analyze" button. `analyze(mode=..., quality_mode=..., judges=[...])` controls how items are scored and by which models. See [AI analysis report](EVALUATIONS.md#ai-analysis-report) in the full guide for the complete field and parameter reference.
295
302
 
296
- See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide dataset builder, framework adapters, similarity metrics, and the complete API reference.
303
+ Ask a case's question several extra ways each run, LLM-paraphrased server-side, to catch agents that break on phrasing rather than substance, and override the judge's prompt/model per config, see [Smoke testing](EVALUATIONS.md#smoke-testing-phrasing-robustness) and [Configuring the judge](EVALUATIONS.md#configuring-the-judge) in the full guide.
304
+
305
+ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, smoke testing, judge configuration, and the complete API reference.
297
306
 
298
307
  ---
299
308
 
@@ -34,6 +34,7 @@ agentx/monitor/__init__.py
34
34
  agentx/monitor/client.py
35
35
  agentx/monitor/models.py
36
36
  agentx/monitor/patterns.py
37
+ agentx/monitor/signals.py
37
38
  agentx/resources/__init__.py
38
39
  agentx/resources/agent.py
39
40
  agentx/resources/conversation.py
@@ -1,37 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import List, Optional
4
- from pydantic import BaseModel, Field
5
-
6
-
7
- class MonitorPattern(BaseModel):
8
- """A detection rule checked against production traces. Built via
9
- ``client.monitor.patterns.builder(...).publish()`` and referenced by id as a
10
- ``pattern_ids`` entry in ``tracer.trace(..., monitor=True, pattern_ids=[...])``.
11
-
12
- A "failure" pattern (the default) raises a signal to triage; a "proper" pattern logs a
13
- healthy tally instead. Only one of ``include_terms``/``regex``/``semantic_prompt`` is
14
- meaningful at a time, selected by ``detector_kind``.
15
- """
16
-
17
- id: str = Field(alias="_id")
18
- key: str
19
- name: str
20
- description: Optional[str] = None
21
- category: Optional[str] = None
22
- detector_kind: str = Field(default="contains", alias="detectorKind")
23
- match_target: List[str] = Field(default_factory=lambda: ["response"], alias="matchTarget")
24
- match_mode: str = Field(default="any", alias="matchMode")
25
- include_terms: List[str] = Field(default_factory=list, alias="includeTerms")
26
- exclude_terms: List[str] = Field(default_factory=list, alias="excludeTerms")
27
- regex: Optional[str] = None
28
- semantic_prompt: Optional[str] = Field(default=None, alias="semanticPrompt")
29
- severity: str = "medium"
30
- polarity: str = "failure"
31
- enabled: bool = True
32
- sample_rate: float = Field(default=1.0, alias="sampleRate")
33
- scope_mode: str = Field(default="all", alias="scopeMode")
34
- agent_ids: List[str] = Field(default_factory=list, alias="agentIds")
35
-
36
- class Config:
37
- extra = "ignore"
@@ -1 +0,0 @@
1
- VERSION = "0.6.7"
File without changes
File without changes
File without changes