fix: restore chat citations and tool details
This commit is contained in:
@@ -9,7 +9,7 @@ from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import select
|
||||
|
||||
from wisegrade import chat_api, services
|
||||
from wisegrade import agent_tools, chat_api, services
|
||||
from wisegrade.actions import confirm, prepare
|
||||
from wisegrade.agent_tools import execute
|
||||
from wisegrade.db import get_db
|
||||
@@ -70,6 +70,36 @@ def test_tool_scope_snapshots_and_project_citations(db):
|
||||
assert rules["source"] == "batch_snapshot"
|
||||
|
||||
|
||||
def test_get_scores_pages_projects_and_criteria(db, monkeypatch):
|
||||
first = add_project(db, "123450010")
|
||||
second = add_project(db, "123450011")
|
||||
created = services.create_batch(SimpleNamespace(course_version_id=1, rubric_version_id=1), db)
|
||||
conversation = Conversation(title="分页评分", batch_id=created["id"])
|
||||
db.add(conversation)
|
||||
db.commit()
|
||||
|
||||
def result(_db, _batch, item):
|
||||
return {"student_no": f"{item.id}", "name": "学生", "totals": {"overall": 80},
|
||||
"rows": [{"criterion": {"criterion_id": f"C{index}", "name": f"评分项{index}", "max_score": 10},
|
||||
"score_id": index, "model_score": 8, "teacher_score": None, "final_score": 8,
|
||||
"status": "scored", "error": "", "evidence": [{"page": index + 1,
|
||||
"quote": "证据" * 200, "document": "报告.pdf", "image_url": None}]}
|
||||
for index in range(14)]}
|
||||
|
||||
monkeypatch.setattr(agent_tools, "project_result", result)
|
||||
first_page = execute(db, conversation, "get_scores", {"batch_id": created["id"]}, "")
|
||||
assert first_page["count"] == 2 and len(first_page["results"]) == 1
|
||||
assert first_page["results"][0]["project_id"] == first.id
|
||||
assert len(first_page["results"][0]["rows"]) == 6
|
||||
assert first_page["next_criterion_offset"] == 6 and first_page["next_project_offset"] == 1
|
||||
assert len(json.dumps(first_page, ensure_ascii=False)) < 12000
|
||||
later = execute(db, conversation, "get_scores", {"batch_id": created["id"],
|
||||
"project_offset": 1, "criterion_offset": 12}, "")
|
||||
assert later["results"][0]["project_id"] == second.id
|
||||
assert [row["criterion_id"] for row in later["results"][0]["rows"]] == ["C12", "C13"]
|
||||
assert later["next_project_offset"] is None and later["next_criterion_offset"] is None
|
||||
|
||||
|
||||
def test_import_confirmation_hash_change_index_and_idempotency(environment, db, monkeypatch, session_factory):
|
||||
folder = environment / "inbox" / "batch" / "123450003_张三_机器人"
|
||||
folder.mkdir(parents=True)
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user