大更新,架构调整,数据分析能力提升,

This commit is contained in:
2026-04-19 21:30:08 +08:00
parent 9d01f004d4
commit 00bd48e7e7
26 changed files with 4375 additions and 252 deletions

233
tests/test_phase3.py Normal file
View File

@@ -0,0 +1,233 @@
# -*- coding: utf-8 -*-
"""
Unit tests for Phase 3: Agent Changes
Run: python -m pytest tests/test_phase3.py -v
"""
import os
import sys
import json
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pytest
from data_analysis_agent import DataAnalysisAgent
from prompts import data_analysis_system_prompt, final_report_system_prompt
# ===========================================================================
# Task 8.1: _summarize_result
# ===========================================================================
class TestSummarizeResult:
@pytest.fixture
def agent(self):
"""Create a minimal DataAnalysisAgent for testing."""
agent = DataAnalysisAgent.__new__(DataAnalysisAgent)
agent._session_ref = None
return agent
def test_success_with_evidence_rows(self, agent):
"""8.1: Success with evidence rows produces DataFrame summary."""
result = {
"success": True,
"evidence_rows": [{"a": 1, "b": 2}, {"a": 3, "b": 4}],
"auto_exported_files": [{"variable_name": "df", "filename": "df.csv", "rows": 150, "cols": 8, "columns": []}],
}
summary = agent._summarize_result(result)
assert "执行成功" in summary
assert "DataFrame" in summary
assert "150" in summary
assert "8" in summary
def test_success_with_evidence_no_auto_files(self, agent):
"""8.1: Success with evidence but no auto_exported_files uses evidence length."""
result = {
"success": True,
"evidence_rows": [{"x": 1}, {"x": 2}, {"x": 3}],
"auto_exported_files": [],
}
summary = agent._summarize_result(result)
assert "执行成功" in summary
assert "DataFrame" in summary
def test_success_with_output(self, agent):
"""8.1: Success with output but no evidence shows first line."""
result = {
"success": True,
"evidence_rows": [],
"output": "Hello World\nSecond line",
}
summary = agent._summarize_result(result)
assert "执行成功" in summary
assert "Hello World" in summary
def test_success_no_output(self, agent):
"""8.1: Success with no output or evidence."""
result = {"success": True, "evidence_rows": [], "output": ""}
summary = agent._summarize_result(result)
assert summary == "执行成功"
def test_failure_short_error(self, agent):
"""8.1: Failure with short error message."""
result = {"success": False, "error": "KeyError: 'col_x'"}
summary = agent._summarize_result(result)
assert "执行失败" in summary
assert "KeyError" in summary
def test_failure_long_error_truncated(self, agent):
"""8.1: Failure with long error is truncated to 100 chars."""
long_error = "A" * 200
result = {"success": False, "error": long_error}
summary = agent._summarize_result(result)
assert "执行失败" in summary
assert "..." in summary
# The error portion should be at most 103 chars (100 + "...")
error_part = summary.split("执行失败: ")[1]
assert len(error_part) <= 104
def test_failure_no_error_field(self, agent):
"""8.1: Failure with missing error field."""
result = {"success": False}
summary = agent._summarize_result(result)
assert "执行失败" in summary
# ===========================================================================
# Task 8.2-8.4: Round_Data construction and session integration
# ===========================================================================
class TestRoundDataConstruction:
def test_handle_generate_code_returns_reasoning(self):
"""8.2: _handle_generate_code returns reasoning from yaml_data."""
agent = DataAnalysisAgent.__new__(DataAnalysisAgent)
agent._session_ref = None
# We need a minimal executor mock
from unittest.mock import MagicMock
agent.executor = MagicMock()
agent.executor.execute_code.return_value = {
"success": True, "output": "ok", "error": "",
"variables": {}, "evidence_rows": [],
"auto_exported_files": [], "prompt_saved_files": [],
}
yaml_data = {"code": "x = 1", "reasoning": "Testing reasoning field"}
result = agent._handle_generate_code("response text", yaml_data)
assert result["reasoning"] == "Testing reasoning field"
def test_handle_generate_code_empty_reasoning(self):
"""8.2: _handle_generate_code returns empty reasoning when not in yaml_data."""
agent = DataAnalysisAgent.__new__(DataAnalysisAgent)
agent._session_ref = None
from unittest.mock import MagicMock
agent.executor = MagicMock()
agent.executor.execute_code.return_value = {
"success": True, "output": "", "error": "",
"variables": {}, "evidence_rows": [],
"auto_exported_files": [], "prompt_saved_files": [],
}
yaml_data = {"code": "x = 1"}
result = agent._handle_generate_code("response text", yaml_data)
assert result["reasoning"] == ""
# ===========================================================================
# Task 8.3: set_session_ref
# ===========================================================================
class TestSetSessionRef:
def test_session_ref_default_none(self):
"""8.3: _session_ref defaults to None."""
agent = DataAnalysisAgent()
assert agent._session_ref is None
def test_set_session_ref(self):
"""8.3: set_session_ref stores the session reference."""
agent = DataAnalysisAgent()
class FakeSession:
rounds = []
data_files = []
session = FakeSession()
agent.set_session_ref(session)
assert agent._session_ref is session
# ===========================================================================
# Task 9.1: Prompt - intermediate data saving instructions
# ===========================================================================
class TestPromptDataSaving:
def test_data_saving_instructions_in_system_prompt(self):
"""9.1: data_analysis_system_prompt contains DATA_FILE_SAVED instructions."""
assert "[DATA_FILE_SAVED]" in data_analysis_system_prompt
assert "中间数据保存规则" in data_analysis_system_prompt
def test_data_saving_example_in_prompt(self):
"""9.1: Prompt contains example of saving and printing marker."""
assert "to_csv" in data_analysis_system_prompt
assert "session_output_dir" in data_analysis_system_prompt
# ===========================================================================
# Task 9.2: Prompt - evidence annotation instructions
# ===========================================================================
class TestPromptEvidenceAnnotation:
def test_evidence_annotation_in_report_prompt(self):
"""9.2: final_report_system_prompt contains evidence annotation instructions."""
assert "evidence:round_" in final_report_system_prompt
assert "证据标注规则" in final_report_system_prompt
def test_evidence_annotation_example(self):
"""9.2: Prompt contains example of evidence annotation."""
assert "<!-- evidence:round_3 -->" in final_report_system_prompt
# ===========================================================================
# Task 9.3: _build_final_report_prompt includes evidence
# ===========================================================================
class TestBuildFinalReportPromptEvidence:
def test_evidence_included_when_session_has_rounds(self):
"""9.3: _build_final_report_prompt includes evidence data when rounds exist."""
agent = DataAnalysisAgent.__new__(DataAnalysisAgent)
agent.analysis_results = []
agent.current_round = 2
agent.session_output_dir = "/tmp/test"
agent.data_profile = "test profile"
class FakeSession:
rounds = [
{
"round": 1,
"reasoning": "分析车型分布",
"result_summary": "执行成功,输出 DataFrame (10行×3列)",
"evidence_rows": [{"车型": "A", "数量": 42}],
},
{
"round": 2,
"reasoning": "分析模块分布",
"result_summary": "执行成功",
"evidence_rows": [],
},
]
agent._session_ref = FakeSession()
prompt = agent._build_final_report_prompt([])
assert "各轮次分析证据数据" in prompt
assert "第1轮" in prompt
assert "第2轮" in prompt
assert "车型" in prompt
def test_no_evidence_when_no_session_ref(self):
"""9.3: _build_final_report_prompt works without session ref."""
agent = DataAnalysisAgent.__new__(DataAnalysisAgent)
agent.analysis_results = []
agent.current_round = 1
agent.session_output_dir = "/tmp/test"
agent.data_profile = "test profile"
agent._session_ref = None
prompt = agent._build_final_report_prompt([])
assert "各轮次分析证据数据" not in prompt