fix(regulatory): 自动执行法规核查前置汇总
This commit is contained in:
@@ -10,7 +10,7 @@ from django.utils import timezone
|
|||||||
from .file_summary.skills.attachment_reader import AttachmentReaderSkill
|
from .file_summary.skills.attachment_reader import AttachmentReaderSkill
|
||||||
from .file_summary.workflow import create_file_summary_batch, start_file_summary_workflow
|
from .file_summary.workflow import create_file_summary_batch, start_file_summary_workflow
|
||||||
from .llm import LLMConfigurationError, LLMRequestError, generate_reply, stream_reply
|
from .llm import LLMConfigurationError, LLMRequestError, generate_reply, stream_reply
|
||||||
from .models import Conversation, FileAttachment, Message
|
from .models import Conversation, FileAttachment, FileSummaryBatch, Message
|
||||||
from .regulatory_review.workflow import (
|
from .regulatory_review.workflow import (
|
||||||
create_regulatory_review_batch,
|
create_regulatory_review_batch,
|
||||||
find_latest_successful_summary_batch,
|
find_latest_successful_summary_batch,
|
||||||
@@ -227,18 +227,51 @@ def stream_message(conversation: Conversation, content: str):
|
|||||||
if route.starts_regulatory_review:
|
if route.starts_regulatory_review:
|
||||||
source_summary_batch = find_latest_successful_summary_batch(conversation)
|
source_summary_batch = find_latest_successful_summary_batch(conversation)
|
||||||
if not source_summary_batch:
|
if not source_summary_batch:
|
||||||
reply_content = "请先执行自动汇总,生成成功的文件汇总批次后再启动法规核查。"
|
if not _has_active_attachments(conversation):
|
||||||
assistant_message = append_assistant_message(conversation, reply_content)
|
reply_content = "请先在当前对话右侧上传需要核查的文件或压缩包,我会先自动汇总再继续法规核查。"
|
||||||
yield sse_event("chunk", {"delta": reply_content})
|
assistant_message = append_assistant_message(conversation, reply_content)
|
||||||
|
yield sse_event("chunk", {"delta": reply_content})
|
||||||
|
yield sse_event(
|
||||||
|
"done",
|
||||||
|
{
|
||||||
|
"assistant_message_id": assistant_message.pk,
|
||||||
|
"conversation_id": conversation.pk,
|
||||||
|
"title": conversation.title,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
summary_batch = create_file_summary_batch(
|
||||||
|
conversation=conversation,
|
||||||
|
user=conversation.user,
|
||||||
|
trigger_message=user_message,
|
||||||
|
)
|
||||||
yield sse_event(
|
yield sse_event(
|
||||||
"done",
|
"workflow_started",
|
||||||
{
|
{
|
||||||
"assistant_message_id": assistant_message.pk,
|
"workflow_type": "file_summary",
|
||||||
"conversation_id": conversation.pk,
|
"batch_id": summary_batch.pk,
|
||||||
"title": conversation.title,
|
"batch_no": summary_batch.batch_no,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return
|
start_file_summary_workflow(summary_batch, async_run=False)
|
||||||
|
summary_batch.refresh_from_db()
|
||||||
|
if summary_batch.status != FileSummaryBatch.Status.SUCCESS:
|
||||||
|
reply_content = f"已先启动文件目录与页数自动汇总工作流,批次号:{summary_batch.batch_no},但汇总未成功:{summary_batch.error_message or '原因待查看'}。请处理后再启动法规核查。"
|
||||||
|
assistant_message = append_assistant_message(conversation, reply_content)
|
||||||
|
yield sse_event("chunk", {"delta": reply_content})
|
||||||
|
yield sse_event(
|
||||||
|
"done",
|
||||||
|
{
|
||||||
|
"assistant_message_id": assistant_message.pk,
|
||||||
|
"conversation_id": conversation.pk,
|
||||||
|
"title": conversation.title,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
source_summary_batch = summary_batch
|
||||||
|
reply_prefix = f"已先启动文件目录与页数自动汇总工作流,批次号:{summary_batch.batch_no},汇总完成后继续法规核查。\n"
|
||||||
|
else:
|
||||||
|
reply_prefix = ""
|
||||||
batch = create_regulatory_review_batch(
|
batch = create_regulatory_review_batch(
|
||||||
conversation=conversation,
|
conversation=conversation,
|
||||||
user=conversation.user,
|
user=conversation.user,
|
||||||
@@ -249,7 +282,7 @@ def stream_message(conversation: Conversation, content: str):
|
|||||||
batch,
|
batch,
|
||||||
async_run=getattr(settings, "REGULATORY_REVIEW_ASYNC", True),
|
async_run=getattr(settings, "REGULATORY_REVIEW_ASYNC", True),
|
||||||
)
|
)
|
||||||
reply_content = f"已启动 NMPA 注册资料法规核查工作流,批次号:{batch.batch_no}。"
|
reply_content = f"{reply_prefix}已启动 NMPA 注册资料法规核查工作流,批次号:{batch.batch_no}。"
|
||||||
assistant_message = append_assistant_message(conversation, reply_content)
|
assistant_message = append_assistant_message(conversation, reply_content)
|
||||||
yield sse_event(
|
yield sse_event(
|
||||||
"workflow_started",
|
"workflow_started",
|
||||||
|
|||||||
@@ -310,7 +310,7 @@
|
|||||||
|
|
||||||
function appendConversationMessage(message) {
|
function appendConversationMessage(message) {
|
||||||
if (!message || document.querySelector('.message[data-message-id="' + message.id + '"]')) {
|
if (!message || document.querySelector('.message[data-message-id="' + message.id + '"]')) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
var label = message.role === "assistant" ? "AI " : "用户 ";
|
var label = message.role === "assistant" ? "AI " : "用户 ";
|
||||||
label += document.querySelectorAll(".message").length + 1;
|
label += document.querySelectorAll(".message").length + 1;
|
||||||
@@ -320,6 +320,7 @@
|
|||||||
if (message.role === "user") {
|
if (message.role === "user") {
|
||||||
appendNode(created.article.id, label, true);
|
appendNode(created.article.id, label, true);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshConversationMessages() {
|
async function refreshConversationMessages() {
|
||||||
@@ -337,14 +338,21 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var payload = await response.json();
|
var payload = await response.json();
|
||||||
(payload.messages || []).forEach(appendConversationMessage);
|
var appendedCount = 0;
|
||||||
|
(payload.messages || []).forEach(function (message) {
|
||||||
|
if (appendConversationMessage(message)) {
|
||||||
|
appendedCount += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
if (payload.latest_message_id) {
|
if (payload.latest_message_id) {
|
||||||
latestMessageId = Math.max(latestMessageId, payload.latest_message_id);
|
latestMessageId = Math.max(latestMessageId, payload.latest_message_id);
|
||||||
}
|
}
|
||||||
syncNodeRailVisibility();
|
syncNodeRailVisibility();
|
||||||
bindNodeAnchorClicks();
|
bindNodeAnchorClicks();
|
||||||
setActiveNode();
|
setActiveNode();
|
||||||
scrollChatToBottom();
|
if (appendedCount > 0) {
|
||||||
|
scrollChatToBottom();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Conversation message refresh failed", error);
|
console.error("Conversation message refresh failed", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,6 +187,16 @@ def test_frontend_refreshes_generated_workflow_messages():
|
|||||||
assert "data-message-url-template" in script
|
assert "data-message-url-template" in script
|
||||||
|
|
||||||
|
|
||||||
|
def test_frontend_only_scrolls_after_appending_new_messages():
|
||||||
|
script = open("static/js/app.js", encoding="utf-8").read()
|
||||||
|
|
||||||
|
assert "return false;" in script
|
||||||
|
assert "return true;" in script
|
||||||
|
assert "var appendedCount = 0;" in script
|
||||||
|
assert "if (appendConversationMessage(message))" in script
|
||||||
|
assert "if (appendedCount > 0)" in script
|
||||||
|
|
||||||
|
|
||||||
def test_frontend_can_replace_partial_stream_content():
|
def test_frontend_can_replace_partial_stream_content():
|
||||||
script = open("static/js/app.js", encoding="utf-8").read()
|
script = open("static/js/app.js", encoding="utf-8").read()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import pytest
|
|||||||
from review_agent.models import (
|
from review_agent.models import (
|
||||||
Conversation,
|
Conversation,
|
||||||
ExportedSummaryFile,
|
ExportedSummaryFile,
|
||||||
|
FileAttachment,
|
||||||
FileSummaryBatch,
|
FileSummaryBatch,
|
||||||
FileSummaryItem,
|
FileSummaryItem,
|
||||||
Message,
|
Message,
|
||||||
@@ -132,10 +133,54 @@ def test_stream_message_prompts_for_summary_when_missing(monkeypatch, django_use
|
|||||||
frames = list(stream_message(conversation, "请做法规核查"))
|
frames = list(stream_message(conversation, "请做法规核查"))
|
||||||
|
|
||||||
joined = "".join(frames)
|
joined = "".join(frames)
|
||||||
assert "请先执行自动汇总" in joined
|
assert "请先在当前对话右侧上传需要核查的文件或压缩包" in joined
|
||||||
|
assert "我会先自动汇总再继续法规核查" in joined
|
||||||
assert not RegulatoryReviewBatch.objects.exists()
|
assert not RegulatoryReviewBatch.objects.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_stream_message_auto_runs_summary_before_regulatory_review(
|
||||||
|
monkeypatch, settings, tmp_path, django_user_model
|
||||||
|
):
|
||||||
|
settings.MEDIA_ROOT = tmp_path
|
||||||
|
settings.REGULATORY_REVIEW_ASYNC = False
|
||||||
|
user = django_user_model.objects.create_user(username="owner", password="pass")
|
||||||
|
conversation = Conversation.objects.create(user=user, title="会话")
|
||||||
|
attachment_path = tmp_path / "application.txt"
|
||||||
|
attachment_path.write_text("产品名称:甲胎蛋白检测试剂盒", encoding="utf-8")
|
||||||
|
FileAttachment.objects.create(
|
||||||
|
conversation=conversation,
|
||||||
|
user=user,
|
||||||
|
original_name="application.txt",
|
||||||
|
storage_path=str(attachment_path),
|
||||||
|
file_size=attachment_path.stat().st_size,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"review_agent.services.route_message_intent",
|
||||||
|
lambda conversation, content: SkillRoute(
|
||||||
|
action="regulatory_review",
|
||||||
|
workflow_type="regulatory_review",
|
||||||
|
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, "进行第一章NMPA 法规核查"))
|
||||||
|
joined = "".join(frames)
|
||||||
|
|
||||||
|
assert "\"workflow_type\": \"file_summary\"" in joined
|
||||||
|
assert "\"workflow_type\": \"regulatory_review\"" in joined
|
||||||
|
assert "已先启动文件目录与页数自动汇总工作流" in joined
|
||||||
|
assert FileSummaryBatch.objects.filter(conversation=conversation, status=FileSummaryBatch.Status.SUCCESS).exists()
|
||||||
|
regulatory = RegulatoryReviewBatch.objects.get(conversation=conversation)
|
||||||
|
assert regulatory.condition_json["rule_scope"]["attachment4_chapter"] == "1"
|
||||||
|
|
||||||
|
|
||||||
def test_stream_message_starts_regulatory_workflow(monkeypatch, settings, django_user_model):
|
def test_stream_message_starts_regulatory_workflow(monkeypatch, settings, django_user_model):
|
||||||
settings.REGULATORY_REVIEW_ASYNC = False
|
settings.REGULATORY_REVIEW_ASYNC = False
|
||||||
user = django_user_model.objects.create_user(username="owner", password="pass")
|
user = django_user_model.objects.create_user(username="owner", password="pass")
|
||||||
|
|||||||
Reference in New Issue
Block a user