fix: link score citations to their source text pages
This commit is contained in:
@@ -83,7 +83,7 @@ def test_get_scores_pages_projects_and_criteria(db, monkeypatch):
|
||||
"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",
|
||||
"quote": "证据" * 200, "document": "报告.pdf", "document_id": index + 100,
|
||||
"image_url": f"/api/assets/{item.id}/1" if index == 0 else None}]}
|
||||
for index in range(14)]}
|
||||
|
||||
@@ -95,6 +95,7 @@ def test_get_scores_pages_projects_and_criteria(db, monkeypatch):
|
||||
assert first_page["next_criterion_offset"] == 6 and first_page["next_project_offset"] == 1
|
||||
assert first_page["citations"][0]["url"] == f"/?page=scores&batch_id={created['id']}&project_id={first.id}"
|
||||
assert first_page["citations"][0]["image_url"] == f"/api/assets/{first.id}/1"
|
||||
assert first_page["citations"][0]["text_url"] == "/api/knowledge/documents/100/pages/1"
|
||||
assert first_page["citations"][1]["url"] == first_page["citations"][0]["url"]
|
||||
assert first_page["citations"][1]["image_url"] is None
|
||||
assert len(json.dumps(first_page, ensure_ascii=False)) < 12000
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from threading import Event
|
||||
from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from wisegrade import chat_api
|
||||
from wisegrade import agent_tools, chat_api, services
|
||||
from wisegrade.db import get_db
|
||||
from wisegrade.main import app
|
||||
from wisegrade.models import AgentRun, AuditLog, ChatMessage, Conversation, CourseVersion, PendingAction, Project, ScoringBatch, Student, now
|
||||
from wisegrade.models import (AgentRun, AuditLog, ChatMessage, Conversation, CourseVersion, CriterionScore,
|
||||
KnowledgeChunk, KnowledgeDocument, PendingAction, Project, ScoreEvidence,
|
||||
ScoringBatch, Student, SubmissionFile, now)
|
||||
|
||||
|
||||
def client_for(session_factory):
|
||||
@@ -176,6 +179,64 @@ def test_tool_record_keeps_bounded_query_but_redacts_review_text_and_secrets():
|
||||
assert 'secret' not in str(search)
|
||||
|
||||
|
||||
def test_score_citations_link_text_and_restore_legacy_image_only(session_factory):
|
||||
with session_factory() as db:
|
||||
student = Student(student_no="987654320", name="测试学生")
|
||||
db.add(student)
|
||||
db.flush()
|
||||
project = Project(course_version_id=1, student_id=student.id, topic="测试项目", source_dir="test/citation")
|
||||
db.add(project)
|
||||
db.flush()
|
||||
file = SubmissionFile(project_id=project.id, category="final_report", relative_path="test/citation/report.pdf",
|
||||
mime_type="application/pdf", size=1, mtime=1, sha256="a" * 64,
|
||||
recognition_status="recognized", parse_status="ready")
|
||||
db.add(file)
|
||||
db.flush()
|
||||
document = KnowledgeDocument(course_version_id=1, project_id=project.id, submission_file_id=file.id,
|
||||
title="报告.pdf", document_type="final_report", source_hash="a" * 64,
|
||||
index_version=2, status="ready")
|
||||
db.add(document)
|
||||
db.flush()
|
||||
db.add(KnowledgeChunk(document_id=document.id, course_version_id=1, project_id=project.id,
|
||||
document_type="final_report", page=2, section="测试", text="这里有原文证据和分析"))
|
||||
created = services.create_batch(SimpleNamespace(course_version_id=1, rubric_version_id=1), db)
|
||||
batch = db.get(ScoringBatch, created["id"])
|
||||
score = CriterionScore(batch_id=batch.id, project_id=project.id,
|
||||
criterion_id=batch.rubric_snapshot["criteria"][0]["criterion_id"],
|
||||
model_score=8, status="scored")
|
||||
db.add(score)
|
||||
db.flush()
|
||||
db.add(ScoreEvidence(score_id=score.id, document_id=document.id, submission_file_id=file.id,
|
||||
page=2, quote="原文证据", image_path="page-2.png"))
|
||||
conversation = Conversation(title="旧评分引用", course_version_id=1, batch_id=batch.id, project_id=project.id)
|
||||
db.add(conversation)
|
||||
db.flush()
|
||||
image_url = f"/api/assets/{file.id}/2"
|
||||
db.add(ChatMessage(conversation_id=conversation.id, role="assistant", content="历史回答", citations=[
|
||||
{"title": "报告.pdf", "page": 2, "quote": "原文证据", "url": image_url},
|
||||
{"title": "报告.pdf", "page": 2, "quote": "不匹配的引文", "url": image_url}]))
|
||||
db.commit()
|
||||
expected = f"/api/knowledge/documents/{document.id}/pages/2"
|
||||
generated = agent_tools.execute(db, conversation, "get_scores",
|
||||
{"batch_id": batch.id, "project_id": project.id}, "")
|
||||
assert generated["citations"][0]["text_url"] == expected
|
||||
assert chat_api._answer_citations(generated["citations"])[0]["text_url"] == expected
|
||||
assert generated["citations"][0]["image_url"] == image_url
|
||||
conversation_id = conversation.id
|
||||
client = client_for(session_factory)
|
||||
try:
|
||||
detail = client.get(f"/api/conversations/{conversation_id}").json()
|
||||
citations = detail["messages"][0]["citations"]
|
||||
assert citations[0]["text_url"] == expected
|
||||
assert "text_url" not in citations[1]
|
||||
assert "原文证据" in client.get(expected).json()["text"]
|
||||
with session_factory() as db:
|
||||
stored = db.scalar(select(ChatMessage).where(ChatMessage.conversation_id == conversation_id))
|
||||
assert "text_url" not in stored.citations[0]
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
def test_late_citations_and_search_snippets_survive_restoration(session_factory, monkeypatch):
|
||||
class FakeClient:
|
||||
def tool_call(self, messages, _tools):
|
||||
|
||||
Reference in New Issue
Block a user