From 57f9181d58999620589f2b73c015a35e6635cd96 Mon Sep 17 00:00:00 2001 From: bruce Date: Sun, 7 Jun 2026 20:15:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(application-form-fill):=20=E6=96=B0?= =?UTF-8?q?=E9=99=84=E4=BB=B6=E5=85=88=E6=B1=87=E6=80=BB=E5=86=8D=E5=A1=AB?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- review_agent/services.py | 18 +++++- tests/test_application_form_fill_workflow.py | 61 ++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/review_agent/services.py b/review_agent/services.py index 252502a..ac3af8d 100644 --- a/review_agent/services.py +++ b/review_agent/services.py @@ -10,7 +10,7 @@ from django.utils import timezone from .file_summary.skills.attachment_reader import AttachmentReaderSkill from .file_summary.workflow import create_file_summary_batch, start_file_summary_workflow from .llm import LLMConfigurationError, LLMRequestError, generate_reply, stream_reply -from .models import Conversation, FileAttachment, FileSummaryBatch, Message +from .models import Conversation, FileAttachment, FileSummaryBatch, FileSummaryBatchAttachment, Message from .application_form_fill.workflow import ( create_application_form_fill_batch, find_latest_successful_summary_batch as find_latest_successful_form_fill_summary_batch, @@ -231,6 +231,8 @@ def stream_message(conversation: Conversation, content: str): if route.starts_application_form_fill: source_summary_batch = find_latest_successful_form_fill_summary_batch(conversation) + if source_summary_batch and not _summary_covers_active_attachments(conversation, source_summary_batch): + source_summary_batch = None if not source_summary_batch: if not _has_active_attachments(conversation): reply_content = "请先在当前对话右侧上传需要填表的产品资料或压缩包,我会先自动汇总再继续生成申报模板。" @@ -480,6 +482,20 @@ def _has_active_attachments(conversation: Conversation) -> bool: ) +def _summary_covers_active_attachments(conversation: Conversation, batch: FileSummaryBatch) -> bool: + active_ids = set( + FileAttachment.objects.filter(conversation=conversation, is_active=True) + .exclude(upload_status=FileAttachment.UploadStatus.DELETED) + .values_list("id", flat=True) + ) + if not active_ids: + return True + bound_ids = set( + FileSummaryBatchAttachment.objects.filter(batch=batch).values_list("attachment_id", flat=True) + ) + return active_ids.issubset(bound_ids) + + def _format_attachment_reader_reply(attachments: list[dict[str, object]], message: str) -> str: if not attachments: return message or "当前对话没有可读取的附件。" diff --git a/tests/test_application_form_fill_workflow.py b/tests/test_application_form_fill_workflow.py index abfe369..fec7d38 100644 --- a/tests/test_application_form_fill_workflow.py +++ b/tests/test_application_form_fill_workflow.py @@ -11,6 +11,7 @@ from review_agent.models import ( Conversation, FileAttachment, FileSummaryBatch, + FileSummaryBatchAttachment, Message, WorkflowEvent, WorkflowNodeRun, @@ -270,3 +271,63 @@ def test_stream_message_auto_runs_summary_before_application_form_fill( assert "汇总完成后继续自动填表" in joined assert FileSummaryBatch.objects.filter(conversation=conversation, status=FileSummaryBatch.Status.SUCCESS).exists() assert ApplicationFormFillBatch.objects.filter(conversation=conversation).exists() + + +def test_stream_message_reruns_summary_when_new_attachment_not_in_latest_batch( + monkeypatch, settings, tmp_path, django_user_model +): + settings.MEDIA_ROOT = tmp_path + settings.APPLICATION_FORM_FILL_ASYNC = False + user = django_user_model.objects.create_user(username="owner", password="pass") + conversation = Conversation.objects.create(user=user, title="会话") + old_path = tmp_path / "old.txt" + old_path.write_text("旧资料", encoding="utf-8") + old_attachment = FileAttachment.objects.create( + conversation=conversation, + user=user, + original_name="旧资料.txt", + storage_path=str(old_path), + file_size=old_path.stat().st_size, + is_active=True, + ) + old_summary = FileSummaryBatch.objects.create( + conversation=conversation, + user=user, + batch_no="FS-OLD", + status=FileSummaryBatch.Status.SUCCESS, + ) + FileSummaryBatchAttachment.objects.create(batch=old_summary, attachment=old_attachment) + new_path = tmp_path / "ifu.txt" + new_path.write_text("【产品名称】\n新型冠状病毒2019-nCoV核酸检测试剂盒(荧光PCR法)", encoding="utf-8") + FileAttachment.objects.create( + conversation=conversation, + user=user, + original_name="目标产品说明书.docx", + storage_path=str(new_path), + file_size=new_path.stat().st_size, + is_active=True, + ) + monkeypatch.setattr( + "review_agent.services.route_message_intent", + lambda conversation, content: SkillRoute( + action="application_form_fill", + workflow_type="application_form_fill", + confidence=0.9, + ), + ) + + def finish_summary(batch, async_run=True): + batch.status = FileSummaryBatch.Status.SUCCESS + batch.save(update_fields=["status"]) + + monkeypatch.setattr("review_agent.services.start_file_summary_workflow", finish_summary) + + frames = list(stream_message(conversation, "请基于当前对话最近成功汇总的产品资料,自动提取产品关键信息并填入申报文件模板")) + joined = "".join(frames) + + assert '"workflow_type": "file_summary"' in joined + assert "汇总完成后继续自动填表" in joined + latest_summary = FileSummaryBatch.objects.order_by("-id").first() + form_batch = ApplicationFormFillBatch.objects.get(conversation=conversation) + assert latest_summary != old_summary + assert form_batch.source_summary_batch == latest_summary