Fin-EvalOps-v2 / backend /tests /test_data_agent_context.py
codex-sync
sync from GitHub: 50e5d60 harden evaluations and scoped data analysis
0c2dfb3
Raw History Blame Contribute Delete
4.38 kB
from __future__ import annotations
from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from app.db import Base
from app import models
from app.services import data_agent
from app.services.llm_client import LLMResult
def _result(data: dict) -> LLMResult:
return LLMResult(
data=data,
raw_text="",
tokens_in=1,
tokens_out=1,
latency_ms=1,
model="test-model",
provider="test",
)
def test_data_agent_executes_sql_then_synthesizes_with_real_rows(monkeypatch):
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as db:
db.add(models.AgentSession(id="session", title="新对话", model="test-model"))
db.commit()
@contextmanager
def session_scope():
with Session(engine) as db:
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
calls: list[dict] = []
def fake_call(**kwargs):
calls.append(kwargs)
if len(calls) == 1:
return _result({
"answer": "准备查询",
"sql": "SELECT status, count(*) AS n FROM runs GROUP BY status",
"chart_spec": None,
})
assert '"rows": [{"status": "failed", "n": 3}]' in kwargs["user"]
return _result({"answer": "共有 3 条失败记录,主要应先处理执行异常。"})
monkeypatch.setattr(data_agent, "db_session", session_scope)
monkeypatch.setattr(data_agent, "_execute_sql", lambda sql: [{"status": "failed", "n": 3}])
monkeypatch.setattr(data_agent, "_load_analysis_context", lambda context: {"scope": "category"})
monkeypatch.setattr(data_agent.llm_client, "call_with_schema", fake_call)
payload = data_agent.reply(
"session",
"这个分类失败了多少条?",
analysis_context={"scope": "category", "category_code": "01"},
)
assert len(calls) == 2
assert payload["answer"].startswith("共有 3 条失败记录")
assert payload["row_count"] == 1
assert payload["data_preview"] == [{"status": "failed", "n": 3}]
with Session(engine) as db:
session = db.get(models.AgentSession, "session")
assert session.title == "这个分类失败了多少条?"
assert [message.role for message in session.messages] == ["user", "assistant"]
def test_testcase_context_contains_details_and_distinguishes_failed_runs(monkeypatch):
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as db:
db.add(models.TestCategory(code="01", slug="one", name_zh="事件选股", name_en="one"))
db.add(models.TestCase(
id="tc", source_id="source", source="manual", category_code="01",
question="哪些股票受益?", agent_answer="回答内容",
reasoning_trace=[{"step": 1}], language="zh",
inferred_difficulty="medium", imported_from="manual",
))
db.add(models.Run(
id="run", testcase_id="tc", skill_id="self/01",
judge_model="m", judge_provider="p", status="failed",
error_msg="Judge 输出不完整",
))
db.commit()
@contextmanager
def session_scope():
with Session(engine) as db:
yield db
monkeypatch.setattr(data_agent, "db_session", session_scope)
context = data_agent._load_analysis_context({"scope": "testcase", "testcase_id": "tc"})
assert context["testcase"]["question"] == "哪些股票受益?"
assert context["testcase"]["agent_answer"] == "回答内容"
assert context["evaluation_runs"][0]["status"] == "failed"
assert context["evaluation_runs"][0]["final_score"] is None
def test_data_agent_caps_rows_inside_database(monkeypatch):
engine = create_engine("sqlite:///:memory:")
monkeypatch.setattr(data_agent, "_read_only_engine", lambda: engine)
rows = data_agent._execute_sql(
"""WITH RECURSIVE numbers(n) AS (
SELECT 1 UNION ALL SELECT n + 1 FROM numbers WHERE n < 300
) SELECT n FROM numbers ORDER BY n""",
row_cap=25,
)
assert len(rows) == 25
assert rows[0] == {"n": 1}
assert rows[-1] == {"n": 25}