fix: restore chat citations and tool details

This commit is contained in:
2026-09-27 12:06:44 +08:00
parent 09d730058c
commit c7d1ba1cd6
10 changed files with 281 additions and 63 deletions
+74
View File
@@ -168,6 +168,80 @@ def test_tool_record_redacts_text_and_secrets():
assert "学生原文" not in str(saved) and "secret" not in str(saved) and "敏感评语" not in str(saved)
assert "学生原文" not in chat_api._result_summary({"found": True, "citations": [{"quote": "学生原文"}]})
assert "学生原文" not in str(chat_api._safe_result({"citations": [{"quote": "学生原文"}]}))
search = chat_api._safe_result({"found": True, "citations": [{"title": "报告.pdf", "page": 48,
"quote": "可核验片段", "url": "/api/pages/48", "api_key": "secret"}]}, "search_project_material")
assert search['citations'][0]['quote'] == '可核验片段'
assert 'secret' not in str(search)
def test_late_citations_and_search_snippets_survive_restoration(session_factory, monkeypatch):
class FakeClient:
def tool_call(self, messages, _tools):
if not any(message['role'] == 'tool' for message in messages):
return {"role": "assistant", "content": None, "tool_calls": [
{"id": f"find-{index}", "type": "function", "function": {"name": "search_project_material",
"arguments": '{"project_id": 1, "query": "检索原文"}'}} for index in range(5)]}
return {"role": "assistant", "content": "综合报告第48页给出改进方向。", "tool_calls": []}
count = 0
def execute(_db, _conversation, _name, _arguments, _text):
nonlocal count
count += 1
page = 48 if count == 5 else count
return {"found": True, "citations": [{"document_id": 10, "chunk_id": count * 10 + index,
"title": "综合报告.pdf", "page": page, "quote": f"第{page}页原文片段{index}",
"url": f"/api/knowledge/documents/10/pages/{page}"} for index in range(6)]}
monkeypatch.setattr(chat_api, "client_for_chat", lambda _db: FakeClient())
monkeypatch.setattr(chat_api, "execute", execute)
client = client_for(session_factory)
try:
created = client.post('/api/conversations', json={"title": "引用恢复"}).json()
response = client.post(f"/api/conversations/{created['id']}/messages", json={"content": "总结"})
assert response.status_code == 200
detail = client.get(f"/api/conversations/{created['id']}").json()
answer = detail['messages'][-1]
assert len(answer['citations']) == 30
assert [item['page'] for item in answer['citations'][-6:]] == [48] * 6
assert answer['tool_calls'][-1]['result']['citations'][0]['quote'] == '第48页原文片段0'
assert detail['runs'][0]['tool_calls'][-1]['result']['citations'][0]['page'] == 48
finally:
app.dependency_overrides.clear()
def test_large_score_result_keeps_structured_rows_for_model_and_history(session_factory, monkeypatch):
seen = []
class FakeClient:
def tool_call(self, messages, _tools):
if not any(message['role'] == 'tool' for message in messages):
return {"role": "assistant", "content": None, "tool_calls": [{"id": "score-call", "type": "function",
"function": {"name": "get_scores", "arguments": '{"batch_id": 3}'}}]}
seen.append(messages[-1]['content'])
return {"role": "assistant", "content": "已读取评分项目。", "tool_calls": []}
def execute(_db, _conversation, _name, _arguments, _text):
return {"batch_id": 3, "count": 1, "project_offset": 0, "criterion_offset": 0,
"next_project_offset": None, "next_criterion_offset": 6,
"results": [{"project_id": 1, "student_no": "1001", "name": "学生甲", "totals": {},
"url": "/?page=scores&batch_id=3&project_id=1", "total_rows": 22,
"rows": [{"criterion_id": "C1", "criterion_name": "功能", "score_id": 7,
"model_score": 8, "teacher_score": None, "final_score": 8,
"max_score": 10, "status": "scored", "error": "", "evidence": []}]}],
"citations": [], "unrelated_large_field": "x" * 13000}
monkeypatch.setattr(chat_api, "client_for_chat", lambda _db: FakeClient())
monkeypatch.setattr(chat_api, "execute", execute)
client = client_for(session_factory)
try:
created = client.post('/api/conversations', json={"title": "评分恢复"}).json()
assert client.post(f"/api/conversations/{created['id']}/messages", json={"content": "查分"}).status_code == 200
assert seen and 'C1' in seen[0] and 'next_criterion_offset' in seen[0]
assert '结果过长' not in seen[0]
result = client.get(f"/api/conversations/{created['id']}").json()['messages'][-1]['tool_calls'][0]['result']
assert result['results'][0]['rows'][0]['criterion_id'] == 'C1'
assert result['next_criterion_offset'] == 6
finally:
app.dependency_overrides.clear()
def test_upgrade_from_legacy_0003_preserves_history(session_factory):