Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/agent-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: agent-tests

on:
pull_request:
paths: ["agent/**", "core/**", ".github/workflows/agent-tests.yml"]
push:
branches: [main, feature/deep-agent-updated]
paths: ["agent/**", "core/**", ".github/workflows/agent-tests.yml"]

jobs:
unit:
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: agent
env:
# Settings that AgentSettings requires; tests never reach these endpoints.
LANGFUSE_PUBLIC_KEY: pk-lf-ci
LANGFUSE_SECRET_KEY: sk-lf-ci
LANGFUSE_BASE_URL: http://127.0.0.1:9
DEEP_AGENT_LANGFUSE_PROMPTS: "false"
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v6
with:
python-version: "3.12"
enable-cache: true
cache-dependency-glob: agent/uv.lock

# core depends on the private matzpen-agency/python-core-utils over git+ssh.
# A fine-grained token with read access to that repo is used over HTTPS instead.
- name: Access private python-core-utils
env:
TOKEN: ${{ secrets.PYTHON_CORE_UTILS_TOKEN }}
run: git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/"

- name: Install
run: uv sync --frozen

- name: Lint (deep agent)
run: uv run ruff check --extend-select F src/deep_agent

- name: Unit tests
run: >
uv run pytest -q
--ignore=tests/refiner/test_refiner_e2e_real.py
--cov=deep_agent --cov-report=term --cov-fail-under=80
21 changes: 21 additions & 0 deletions agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,24 @@ JARVIS_API_KEY=... # sent as "Authorization: Ap
The registry service identity needs the `datetime:read`, `text:read`, `geo:read` and `trino:read` scopes, and each tool must be
approved in the registry. SQL is never executed through Utils-Server. If the gateway is unreachable the agent starts without
these tools and logs a warning.

### Evaluation (migration go/no-go)

```bash
uv run deep-agent-eval # deep agent + legacy graph on benchmarks/eval_dataset.json
uv run deep-agent-eval --systems deep --out runs/eval.json
uv run deep-agent-eval --gate --p95-slo 60 # exit 1 unless deep accuracy >= legacy and p95 <= SLO
uv run deep-agent-eval --langfuse-dataset text2sql-eval # Langfuse Experiment: linked dataset run + scores
```

Metric: **execution accuracy** — the system's result set vs the gold SQL's (order-insensitive unless the gold query has
a top-level `ORDER BY`; floats to 6 decimals; permuted columns match). Items without `gold_sql` are reported as
`no_gold`. `gold_source: agent_run_unverified` marks gold SQL taken from past agent runs that still needs expert review.
For a Langfuse dataset, items use `input = {"question": ...}` and `expected_output = {"sql": ...}`.

### CI

`.github/workflows/agent-tests.yml` runs ruff (deep agent) and the unit tests with an 80% coverage gate on `deep_agent`
for every PR touching `agent/` or `core/`. It needs a repository secret **`PYTHON_CORE_UTILS_TOKEN`**: a fine-grained
token with read access to `matzpen-agency/python-core-utils` (used instead of the `git+ssh` URL). Real-service e2e
tests (`tests/refiner/test_refiner_e2e_real.py`) are excluded.
117 changes: 117 additions & 0 deletions agent/benchmarks/eval_dataset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"description": "Deep Agent vs legacy graph evaluation set. Metric: execution accuracy against gold_sql result sets. Items with gold_sql=null are reported as no_gold and not scored. gold_source=agent_run_unverified marks gold SQL taken from consistent past agent runs that still needs domain-expert verification.",
"items": [
{
"id": "q01",
"question": "כמה טיסות המריאו מצרפת",
"tags": [
"simple_filter",
"categorical"
],
"gold_sql": "SELECT COUNT(DISTINCT flight_id) AS flight_count FROM \"postgres\".\"public\".\"flights_table\" WHERE launch_country = 'France'",
"gold_source": "agent_run_unverified"
},
{
"id": "q02",
"question": "what cities are in the Haifa district",
"tags": [
"large_categorical",
"join"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q03",
"question": "חשב את מספר התראות הטיסה לנפש עבור כל מחוז. לאחר מכן, עבור 3 המחוזות עם כמות ההתראות הגבוהה ביותר, ציין את 3 הערים הגדולות ביותר בכל מחוז על בסיס שטח הפנים שלהן.",
"tags": [
"per_capita",
"join",
"aggregation",
"top_n"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q04",
"question": "כמה טיסות המריאו מצרפת ונחתו בתל אביב",
"tags": [
"simple_filter",
"categorical",
"large_categorical"
],
"gold_sql": "SELECT COUNT(DISTINCT flight_id) AS flight_count FROM \"postgres\".\"public\".\"flights_table\" WHERE launch_country = 'France' AND city_name LIKE 'Tel Aviv%'",
"gold_source": "agent_run_unverified"
},
{
"id": "q05",
"question": "מצא את אזורי ההנחיה שבהם מדיניות הטיסה אוסרת טיסה בגובה נמוך או סוגרת את המרחב האווירי לחלוטין, ובמקביל מדיניות הנחיתה מחייבת העברה לשדה חלופי. עבור כל אזור הצג את מדיניות הטיסה, את מדיניות הנחיתה ואת מועד העדכון האחרון. מיין מהעדכון החדש ביותר לישן ביותר.",
"tags": [
"categorical",
"temporal_latest"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q06",
"question": "השווה בין ערים לפי מספר ההתרעות שהתקבלו בהן במהלך יוני 2026 ביחס לגודל האוכלוסייה שלהן. הצג רק ערים שיש להן לפחות 10 התרעות בתקופה, והצג את חמש הערים עם מספר ההתרעות הגבוה ביותר לכל 10,000 תושבים. הצג גם את מספר ההתרעות ואת גודל האוכלוסייה ששימשו לחישוב.",
"tags": [
"temporal",
"per_capita",
"join",
"aggregation",
"top_n"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q07",
"question": "מצא את הערים שבהן נרשמו לפחות 5 התרעות במהלך השבועיים הראשונים של יולי 2026, אך לא נרשמה בהן אף נחיתת חירום באותה תקופה. הצג את מספר ההתרעות בכל עיר, ומיין מהמספר הגבוה לנמוך.",
"tags": [
"temporal",
"anti_join",
"aggregation"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q08",
"question": "עבור טיסות שהמריאו במהלך יוני 2026, מצא את אזורי ההנחיה שבהם שיעור הטיסות ארוכות הטווח גבוה מהממוצע של כלל הטיסות בתקופה. הצג את מספר הטיסות בכל אזור, מספר הטיסות ארוכות הטווח, השיעור שלהן והפער בנקודות אחוז מהממוצע הכללי. מיין לפי הפער מהגבוה לנמוך.",
"tags": [
"temporal",
"join",
"aggregation",
"comparison_to_average"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q09",
"question": "זהה את עשר הרשויות שבהן היחס בין מספר האנשים השוהים במתקני האירוח לבין מספר העובדים הוא הגבוה ביותר, בתנאי שיש בהן לפחות 500 אנשים. הצג גם את מספר החדרים, את מספר האנשים לכל חדר ואת מועד העדכון של נתוני האירוח. אל תכלול רשויות שבהן מספר העובדים הוא אפס.",
"tags": [
"ratio",
"join",
"aggregation",
"top_n"
],
"gold_sql": null,
"gold_source": null
},
{
"id": "q10",
"question": "עבור כל אזור הנחיה, הצג את המדיניות העדכנית ביותר הידועה, את תאריך העדכון שלה ואת מספר הטיסות ששויכו לאזור במהלך יולי 2026. הצג גם אזורים שאין להם טיסות בתקופה, וודא שאזור שמופיעות עבורו כמה גרסאות של מדיניות נספר פעם אחת בלבד.",
"tags": [
"temporal_latest",
"left_join",
"deduplication"
],
"gold_sql": null,
"gold_source": null
}
]
}
1 change: 1 addition & 0 deletions agent/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies = [
[project.scripts]
deep-agent = "deep_agent.cli:main"
deep-agent-bench = "deep_agent.benchmark:main"
deep-agent-eval = "deep_agent.evaluation:main"

[tool.uv.sources]
core = { path = "../core", editable = true }
Expand Down
13 changes: 11 additions & 2 deletions agent/src/deep_agent/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ def _token_usage(messages: list) -> tuple[int, int]:


async def run_question(agent, item: dict[str, Any], context: DeepAgentContext, timeout_s: float) -> QuestionRun:
return (await run_question_detailed(agent, item, context, timeout_s))[0]


async def run_question_detailed(
agent, item: dict[str, Any], context: DeepAgentContext, timeout_s: float
) -> tuple[QuestionRun, dict[str, Any], str | None]:
"""Run one question; returns (metrics, final_result with rows, Langfuse trace id)."""
thread_id = f"bench_{item['id']}_{uuid.uuid4().hex[:6]}"
config = observability.build_run_config(thread_id, connection_id=context.connection_id)
start = time.perf_counter()
Expand All @@ -79,14 +86,16 @@ async def run_question(agent, item: dict[str, Any], context: DeepAgentContext, t

messages = state.get("messages") or []
metrics = observability.run_metrics(messages, latency, final_result)
observability.score_run(observability.langfuse_trace_id(config), metrics)
trace_id = observability.langfuse_trace_id(config)
observability.score_run(trace_id, metrics)
input_tokens, output_tokens = _token_usage(messages)
return QuestionRun(
run = QuestionRun(
id=item["id"], question=item["question"], latency_s=metrics.latency_s, model_calls=metrics.model_calls,
tool_calls=metrics.tool_calls, sql_executions=metrics.sql_executions, input_tokens=input_tokens,
output_tokens=output_tokens, status=final_result.get("status", "no_answer"),
verified=bool(final_result.get("verified")), error=error,
)
return run, final_result, trace_id


async def run_benchmark(
Expand Down
Loading
Loading