agentomatic 0.5.1__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentomatic-0.5.1 → agentomatic-0.6.0}/PKG-INFO +1 -1
- {agentomatic-0.5.1 → agentomatic-0.6.0}/pyproject.toml +2 -1
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/_version.py +1 -1
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/config.py +32 -0
- agentomatic-0.6.0/src/agentomatic/optimize/context.py +276 -0
- agentomatic-0.6.0/src/agentomatic/optimize/dashboard.py +562 -0
- agentomatic-0.6.0/src/agentomatic/optimize/events.py +248 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/fitter.py +207 -7
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/fitter_optimizers.py +195 -102
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/loop.py +148 -25
- agentomatic-0.6.0/src/agentomatic/optimize/progress.py +642 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_fitter.py +3 -3
- agentomatic-0.6.0/tests/test_optimizer_dashboard.py +64 -0
- agentomatic-0.6.0/tests/test_optimizer_events.py +65 -0
- agentomatic-0.6.0/tests/test_optimizer_progress.py +59 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/LICENSE +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/README.md +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/examples/full_agent/README.md +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/cli/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/cli/commands.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/cli/templates.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/config/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/config/defaults.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/config/settings.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/lifespan.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/manifest.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/memory_manager.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/platform.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/registry.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/router_factory.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/core/state.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/demo/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/demo/agent.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/demo/server.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/middleware/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/middleware/auth.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/middleware/feedback.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/middleware/logging.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/middleware/metrics.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/middleware/rate_limit.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/observability/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/observability/concurrency.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/observability/metrics.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/observability/telemetry.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/dataset.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/deployment.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/eval_contract.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/failure_analysis.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/judges.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/llm_caller.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/metrics.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/optimizer.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/report.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/runner.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/search_space.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/strategies.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/optimize/synthesizer.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/prompts/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/prompts/manager.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/protocols/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/protocols/decorators.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/providers/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/providers/embeddings.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/providers/llm.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/py.typed +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/storage/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/storage/base.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/storage/checkpointer.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/storage/memory.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/storage/models.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/storage/sqlalchemy.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/adapter.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/adapters/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/adapters/generic.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/adapters/langchain.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/adapters/langgraph.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/decorators.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/graph_inspector.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/models.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/router.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/run_tracker.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/serve.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/asset-manifest.json +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/imgs/graph_view.png +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/imgs/logo.png +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/imgs/main_screen.png +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/imgs/main_screen_ok.png +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/index.html +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/manifest.json +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/static/css/main.8b3c42dd.css +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/static/css/main.8b3c42dd.css.map +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/static/js/main.cbb2a8ee.js +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/static/js/main.cbb2a8ee.js.LICENSE.txt +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/studio/static/static/js/main.cbb2a8ee.js.map +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/ui/__init__.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/src/agentomatic/ui/chat.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_agentomatic.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_bugfixes_041.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_cli.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_coverage_boost.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_deep_agent.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_integration.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_optimize.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_platform_features.py +0 -0
- {agentomatic-0.5.1 → agentomatic-0.6.0}/tests/test_studio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentomatic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Drop agents, not code — zero-code multi-agent API platform framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/UnicoLab/agentomatic
|
|
6
6
|
Project-URL: Documentation, https://unicolab.github.io/agentomatic
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentomatic"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "Drop agents, not code — zero-code multi-agent API platform framework"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
@@ -85,6 +85,7 @@ Changelog = "https://github.com/UnicoLab/agentomatic/blob/main/CHANGELOG.md"
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
|
|
88
89
|
[dependency-groups]
|
|
89
90
|
dev = [
|
|
90
91
|
"pytest>=8",
|
|
@@ -47,6 +47,28 @@ from typing import Any
|
|
|
47
47
|
|
|
48
48
|
from loguru import logger
|
|
49
49
|
|
|
50
|
+
|
|
51
|
+
def escape_braces(text: str, preserve: list[str] | None = None) -> str:
|
|
52
|
+
"""Escape literal braces while preserving specific template variables.
|
|
53
|
+
|
|
54
|
+
Replaces '{' with '{{' and '}' with '}}', except for variables listed in `preserve`
|
|
55
|
+
(e.g., 'query' for '{query}'). This prevents KeyError during subsequent .format() calls
|
|
56
|
+
on system prompts that contain literal JSON or markdown code blocks.
|
|
57
|
+
"""
|
|
58
|
+
if not text:
|
|
59
|
+
return text
|
|
60
|
+
|
|
61
|
+
# First escape all
|
|
62
|
+
escaped = text.replace("{", "{{").replace("}", "}}")
|
|
63
|
+
|
|
64
|
+
# Then unescape the specific variables we want to preserve
|
|
65
|
+
if preserve:
|
|
66
|
+
for var in preserve:
|
|
67
|
+
escaped = escaped.replace(f"{{{{{var}}}}}", f"{{{var}}}")
|
|
68
|
+
|
|
69
|
+
return escaped
|
|
70
|
+
|
|
71
|
+
|
|
50
72
|
# =====================================================================
|
|
51
73
|
# Runtime configuration
|
|
52
74
|
# =====================================================================
|
|
@@ -88,6 +110,16 @@ class PromptRuntimeConfig:
|
|
|
88
110
|
fallback_model: str | None = None # automatic fallback model
|
|
89
111
|
routing_config: dict[str, Any] = field(default_factory=dict) # A/B weights, local/remote
|
|
90
112
|
|
|
113
|
+
@property
|
|
114
|
+
def safe_system_prompt(self) -> str:
|
|
115
|
+
"""Returns the system prompt with literal braces safely escaped.
|
|
116
|
+
|
|
117
|
+
Assumes the system prompt does not contain runtime format variables.
|
|
118
|
+
Useful when passing the system prompt into LangChain or other formatters
|
|
119
|
+
that might crash on literal JSON blocks.
|
|
120
|
+
"""
|
|
121
|
+
return escape_braces(self.system_prompt)
|
|
122
|
+
|
|
91
123
|
# -- serialization ---------------------------------------------------
|
|
92
124
|
|
|
93
125
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Rich context objects for informed prompt rewriting.
|
|
2
|
+
|
|
3
|
+
Provides :class:`OptimizationContext` — a structured accumulator of
|
|
4
|
+
evaluation state, score history, pipeline metadata, and dataset
|
|
5
|
+
characteristics that optimisers receive to make deeply informed
|
|
6
|
+
rewrite decisions.
|
|
7
|
+
|
|
8
|
+
Example::
|
|
9
|
+
|
|
10
|
+
ctx = OptimizationContext(
|
|
11
|
+
baseline_score=0.72,
|
|
12
|
+
current_score=0.85,
|
|
13
|
+
score_history=[
|
|
14
|
+
RoundStats(round_idx=0, score=0.72, dims={"relevance": 0.68}),
|
|
15
|
+
RoundStats(round_idx=1, score=0.79, dims={"relevance": 0.75}),
|
|
16
|
+
],
|
|
17
|
+
)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
# =====================================================================
|
|
26
|
+
# Score history per round
|
|
27
|
+
# =====================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class RoundStats:
|
|
32
|
+
"""Snapshot of scores and metadata from a single optimisation round.
|
|
33
|
+
|
|
34
|
+
Stored in :attr:`OptimizationContext.score_history` so the rewrite
|
|
35
|
+
model can see performance trends across iterations.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
round_idx: int = 0
|
|
39
|
+
score: float = 0.0
|
|
40
|
+
dims: dict[str, float] = field(default_factory=dict)
|
|
41
|
+
best_candidate: str = ""
|
|
42
|
+
accepted: bool = False
|
|
43
|
+
n_candidates: int = 0
|
|
44
|
+
elapsed_seconds: float = 0.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# =====================================================================
|
|
48
|
+
# Dataset summary
|
|
49
|
+
# =====================================================================
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(slots=True)
|
|
53
|
+
class DatasetSummary:
|
|
54
|
+
"""Lightweight summary of dataset characteristics.
|
|
55
|
+
|
|
56
|
+
Included in :class:`OptimizationContext` so optimisers can adapt
|
|
57
|
+
their strategies based on dataset size and composition.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
n_samples: int = 0
|
|
61
|
+
categories: list[str] = field(default_factory=list)
|
|
62
|
+
avg_query_length: int = 0
|
|
63
|
+
avg_expected_length: int = 0
|
|
64
|
+
has_context: bool = False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# =====================================================================
|
|
68
|
+
# Full optimisation context
|
|
69
|
+
# =====================================================================
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(slots=True)
|
|
73
|
+
class OptimizationContext:
|
|
74
|
+
"""Rich context passed to optimisers for informed prompt rewriting.
|
|
75
|
+
|
|
76
|
+
Accumulates state across iterations so the rewriting model can
|
|
77
|
+
reason about *trends* and *root causes* rather than treating each
|
|
78
|
+
round as an independent rewrite.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
baseline_score: Score of the original, unoptimised config.
|
|
82
|
+
baseline_dims: Per-dimension scores at baseline.
|
|
83
|
+
current_score: Score of the current-best config.
|
|
84
|
+
current_dims: Per-dimension scores for the current best.
|
|
85
|
+
score_history: List of :class:`RoundStats` for previous rounds.
|
|
86
|
+
failure_clusters: Failure-analysis clusters (label, count, fix).
|
|
87
|
+
eval_details: Full per-point evaluation results with pipeline
|
|
88
|
+
context (``retrieval_context``, ``tool_calls``, ``reasoning``,
|
|
89
|
+
``citations``, ``metadata``, ``duration_ms``).
|
|
90
|
+
dataset_summary: Size and shape of the evaluation dataset.
|
|
91
|
+
metric_names: Names of the metrics being evaluated.
|
|
92
|
+
round_idx: Current round index.
|
|
93
|
+
total_rounds: Total planned rounds.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# ── Scores ───────────────────────────────────────────────────────
|
|
97
|
+
baseline_score: float = 0.0
|
|
98
|
+
baseline_dims: dict[str, float] = field(default_factory=dict)
|
|
99
|
+
current_score: float = 0.0
|
|
100
|
+
current_dims: dict[str, float] = field(default_factory=dict)
|
|
101
|
+
|
|
102
|
+
# ── History ──────────────────────────────────────────────────────
|
|
103
|
+
score_history: list[RoundStats] = field(default_factory=list)
|
|
104
|
+
|
|
105
|
+
# ── Failure analysis ─────────────────────────────────────────────
|
|
106
|
+
failure_clusters: list[dict[str, Any]] = field(default_factory=list)
|
|
107
|
+
|
|
108
|
+
# ── Evaluation detail (with pipeline context) ────────────────────
|
|
109
|
+
eval_details: list[dict[str, Any]] = field(default_factory=list)
|
|
110
|
+
|
|
111
|
+
# ── Dataset ──────────────────────────────────────────────────────
|
|
112
|
+
dataset_summary: DatasetSummary = field(default_factory=DatasetSummary)
|
|
113
|
+
metric_names: list[str] = field(default_factory=list)
|
|
114
|
+
|
|
115
|
+
# ── Progress ─────────────────────────────────────────────────────
|
|
116
|
+
round_idx: int = 0
|
|
117
|
+
total_rounds: int = 0
|
|
118
|
+
|
|
119
|
+
# ── Formatting helpers ───────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
def format_score_history(self, max_rounds: int = 5) -> str:
|
|
122
|
+
"""Format recent score history as a human-readable string.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
max_rounds: Maximum number of recent rounds to include.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Multi-line string showing score evolution with trend
|
|
129
|
+
indicators.
|
|
130
|
+
"""
|
|
131
|
+
if not self.score_history:
|
|
132
|
+
return "No previous rounds."
|
|
133
|
+
|
|
134
|
+
recent = self.score_history[-max_rounds:]
|
|
135
|
+
lines: list[str] = []
|
|
136
|
+
for i, rs in enumerate(recent):
|
|
137
|
+
trend = ""
|
|
138
|
+
if i > 0:
|
|
139
|
+
prev = recent[i - 1].score
|
|
140
|
+
delta = rs.score - prev
|
|
141
|
+
trend = f" (Δ {delta:+.4f})" if abs(delta) > 1e-6 else " (=)"
|
|
142
|
+
accepted = "✅" if rs.accepted else "❌"
|
|
143
|
+
lines.append(
|
|
144
|
+
f" Round {rs.round_idx + 1}: {rs.score:.4f}{trend} "
|
|
145
|
+
f"{accepted} ({rs.n_candidates} candidates, "
|
|
146
|
+
f"{rs.elapsed_seconds:.1f}s)"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return "\n".join(lines)
|
|
150
|
+
|
|
151
|
+
def format_score_sparkline(self) -> str:
|
|
152
|
+
"""Format score evolution as an inline sparkline.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
String like ``📈 0.72 → 0.79 → 0.85``.
|
|
156
|
+
"""
|
|
157
|
+
if not self.score_history:
|
|
158
|
+
return f"📊 baseline={self.baseline_score:.3f}"
|
|
159
|
+
|
|
160
|
+
scores = [self.baseline_score] + [rs.score for rs in self.score_history]
|
|
161
|
+
parts = [f"{s:.3f}" for s in scores]
|
|
162
|
+
trend = "📈" if scores[-1] > scores[0] else "📉"
|
|
163
|
+
return f"{trend} {' → '.join(parts)}"
|
|
164
|
+
|
|
165
|
+
def format_dimension_table(self) -> str:
|
|
166
|
+
"""Format per-dimension comparison (baseline vs current).
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Multi-line table of dimension scores with deltas.
|
|
170
|
+
"""
|
|
171
|
+
all_dims = sorted(set(self.baseline_dims) | set(self.current_dims))
|
|
172
|
+
if not all_dims:
|
|
173
|
+
return "No per-dimension scores available."
|
|
174
|
+
|
|
175
|
+
lines: list[str] = [
|
|
176
|
+
f" {'Dimension':<20} {'Baseline':>10} {'Current':>10} {'Delta':>10}",
|
|
177
|
+
f" {'─' * 20} {'─' * 10} {'─' * 10} {'─' * 10}",
|
|
178
|
+
]
|
|
179
|
+
for dim in all_dims:
|
|
180
|
+
base = self.baseline_dims.get(dim, 0.0)
|
|
181
|
+
curr = self.current_dims.get(dim, 0.0)
|
|
182
|
+
delta = curr - base
|
|
183
|
+
lines.append(f" {dim:<20} {base:>10.4f} {curr:>10.4f} {delta:>+10.4f}")
|
|
184
|
+
return "\n".join(lines)
|
|
185
|
+
|
|
186
|
+
def format_failure_clusters(self) -> str:
|
|
187
|
+
"""Format failure clusters for the rewrite prompt.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Multi-line summary of failure categories.
|
|
191
|
+
"""
|
|
192
|
+
if not self.failure_clusters:
|
|
193
|
+
return "No failure clusters identified."
|
|
194
|
+
|
|
195
|
+
lines: list[str] = []
|
|
196
|
+
for i, cluster in enumerate(self.failure_clusters, 1):
|
|
197
|
+
label = cluster.get("label", "unknown")
|
|
198
|
+
count = cluster.get("count", 0)
|
|
199
|
+
fix = cluster.get("suggested_fix", "N/A")
|
|
200
|
+
severity = cluster.get("severity", 0.0)
|
|
201
|
+
lines.append(f" {i}. [{severity:.2f}] {label} ({count} cases): {fix[:120]}")
|
|
202
|
+
return "\n".join(lines)
|
|
203
|
+
|
|
204
|
+
def format_eval_details_for_rewrite(
|
|
205
|
+
self,
|
|
206
|
+
max_failures: int = 5,
|
|
207
|
+
max_successes: int = 3,
|
|
208
|
+
) -> str:
|
|
209
|
+
"""Format evaluation details for inclusion in a rewrite prompt.
|
|
210
|
+
|
|
211
|
+
Includes full pipeline context (retrieval docs, tool calls,
|
|
212
|
+
reasoning) for the lowest-scoring and highest-scoring results.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
max_failures: Number of worst results to include.
|
|
216
|
+
max_successes: Number of best results to include.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Multi-section string with failures and successes.
|
|
220
|
+
"""
|
|
221
|
+
if not self.eval_details:
|
|
222
|
+
return "No evaluation details available."
|
|
223
|
+
|
|
224
|
+
scored = sorted(
|
|
225
|
+
self.eval_details,
|
|
226
|
+
key=lambda r: r.get("score", r.get("avg_score", 0.0)),
|
|
227
|
+
)
|
|
228
|
+
failures = scored[:max_failures]
|
|
229
|
+
successes = scored[-max_successes:]
|
|
230
|
+
|
|
231
|
+
lines: list[str] = []
|
|
232
|
+
|
|
233
|
+
# ── Failures ─────────────────────────────────────────────────
|
|
234
|
+
if failures:
|
|
235
|
+
lines.append("### Failures (lowest-scoring)")
|
|
236
|
+
for idx, f in enumerate(failures, 1):
|
|
237
|
+
score = f.get("score", f.get("avg_score", 0.0))
|
|
238
|
+
lines.append(f"\n**Failure {idx}** (score: {score:.3f})")
|
|
239
|
+
lines.append(f"- Query: {f.get('query', 'N/A')[:300]}")
|
|
240
|
+
lines.append(f"- Expected: {str(f.get('expected', 'N/A'))[:300]}")
|
|
241
|
+
lines.append(f"- Response: {f.get('response', 'N/A')[:300]}")
|
|
242
|
+
|
|
243
|
+
feedback = f.get("feedback") or f.get("reason") or f.get("details", "")
|
|
244
|
+
if feedback:
|
|
245
|
+
lines.append(f"- Feedback: {str(feedback)[:250]}")
|
|
246
|
+
|
|
247
|
+
dims = f.get("dimensions", {})
|
|
248
|
+
if dims:
|
|
249
|
+
dim_str = ", ".join(f"{k}={v:.3f}" for k, v in dims.items())
|
|
250
|
+
lines.append(f"- Dimensions: {dim_str}")
|
|
251
|
+
|
|
252
|
+
# Pipeline context
|
|
253
|
+
ret_ctx = f.get("retrieval_context", [])
|
|
254
|
+
if ret_ctx:
|
|
255
|
+
docs = "; ".join(str(d)[:100] for d in ret_ctx[:3])
|
|
256
|
+
lines.append(f"- Retrieved docs: {docs}")
|
|
257
|
+
|
|
258
|
+
tool_calls = f.get("tool_calls", [])
|
|
259
|
+
if tool_calls:
|
|
260
|
+
tools = ", ".join(str(t.get("name", t)) for t in tool_calls[:3])
|
|
261
|
+
lines.append(f"- Tool calls: {tools}")
|
|
262
|
+
|
|
263
|
+
reasoning = f.get("reasoning", "")
|
|
264
|
+
if reasoning:
|
|
265
|
+
lines.append(f"- Reasoning: {str(reasoning)[:200]}")
|
|
266
|
+
|
|
267
|
+
# ── Successes ────────────────────────────────────────────────
|
|
268
|
+
if successes:
|
|
269
|
+
lines.append("\n### Successes (highest-scoring)")
|
|
270
|
+
for idx, s in enumerate(successes, 1):
|
|
271
|
+
score = s.get("score", s.get("avg_score", 0.0))
|
|
272
|
+
lines.append(f"\n**Success {idx}** (score: {score:.3f})")
|
|
273
|
+
lines.append(f"- Query: {s.get('query', 'N/A')[:200]}")
|
|
274
|
+
lines.append(f"- Response: {s.get('response', 'N/A')[:200]}")
|
|
275
|
+
|
|
276
|
+
return "\n".join(lines)
|