feat: 重构资料包模型与会话绑定主链路

This commit is contained in:
2026-06-04 00:43:13 +08:00
parent ddf5e7d15c
commit d0841e533f
18 changed files with 1000 additions and 263 deletions

View File

@@ -0,0 +1,52 @@
# Generated by Django 5.2.14 on 2026-06-03 16:39
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Conversation",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"conversation_id",
models.CharField(db_index=True, max_length=64, unique=True),
),
("title", models.CharField(max_length=255)),
(
"product_name",
models.CharField(blank=True, db_index=True, max_length=255),
),
(
"batch_id",
models.CharField(blank=True, db_index=True, max_length=64),
),
(
"task_status",
models.CharField(db_index=True, default="pending", max_length=32),
),
("node_results", models.JSONField(blank=True, default=list)),
("latest_summary", models.JSONField(blank=True, default=dict)),
("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("last_run_at", models.DateTimeField(blank=True, null=True)),
],
options={
"ordering": ["-updated_at", "-created_at"],
},
),
]

34
apps/chat/models.py Normal file
View File

@@ -0,0 +1,34 @@
from django.db import models
class Conversation(models.Model):
"""
审核智能体会话主对象。
会话与资料包一一绑定,标题默认使用解析出的产品名称,
节点结果使用 JSON 挂载,便于页面按节点展示。
"""
STATUS_PENDING = "pending"
STATUS_PROCESSING = "processing"
STATUS_COMPLETED = "completed"
STATUS_REVIEW_REQUIRED = "review_required"
STATUS_BLOCKED = "blocked"
STATUS_FAILED = "failed"
conversation_id = models.CharField(max_length=64, unique=True, db_index=True)
title = models.CharField(max_length=255)
product_name = models.CharField(max_length=255, blank=True, db_index=True)
batch_id = models.CharField(max_length=64, blank=True, db_index=True)
task_status = models.CharField(max_length=32, default=STATUS_PENDING, db_index=True)
node_results = models.JSONField(default=list, blank=True)
latest_summary = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True)
last_run_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ["-updated_at", "-created_at"]
def __str__(self) -> str:
return self.title

26
apps/chat/services.py Normal file
View File

@@ -0,0 +1,26 @@
from .models import Conversation
def create_conversation_for_batch(batch_id: str, product_name: str) -> Conversation:
"""
为资料包创建主会话。
会话标题固定优先使用解析出的产品名称,
缺失时回退到批次号,确保前台始终有稳定标题。
"""
conversation = Conversation.objects.create(
conversation_id=_generate_conversation_id(),
title=product_name or f"未命名资料包-{batch_id}",
product_name=product_name,
batch_id=batch_id,
task_status=Conversation.STATUS_PENDING,
node_results=[
{"code": "package_import", "label": "资料包导入", "status": "已完成"},
{"code": "overview", "label": "目录汇总", "status": "处理中"},
],
)
return conversation
def _generate_conversation_id() -> str:
return f"conv-{Conversation.objects.count() + 1:03d}"

View File

@@ -5,7 +5,8 @@ from . import views
app_name = "chat"
# 当前 V1 仅保留一个场景对话入口,场景详情合并在对话页中展示
# 审核智能体前台以会话为中心
urlpatterns = [
path("<str:scenario_id>/", views.index, name="index"),
path("", views.index, name="index"),
path("<str:conversation_id>/", views.detail, name="detail"),
]

View File

@@ -1,38 +1,43 @@
from django.shortcuts import render
from django.shortcuts import get_object_or_404, redirect, render
from agent_core.orchestrator import run_agent
from agent_core.results import AgentResult
from apps.audit.services import create_audit_log
from apps.documents.models import UploadedDocument
from apps.scenarios.services import ScenarioNotFound, get_scenario
from apps.documents.models import SubmissionBatch, UploadedDocument
from apps.scenarios.services import get_scenario
from .forms import ChatForm
from .models import Conversation
def index(request, scenario_id: str):
# View 只负责请求编排、表单校验和模板渲染。
# 具体 Agent 执行、审计写入和文档筛选规则分别交给独立模块处理。
try:
scenario = get_scenario(scenario_id)
except ScenarioNotFound:
return render(
request,
"chat/index.html",
{
"scenario": None,
"form": ChatForm(),
"error": "场景不存在,请返回首页检查配置。",
},
status=404,
)
def index(request):
conversations = Conversation.objects.all()
if conversations.exists():
return redirect("chat:detail", conversation_id=conversations.first().conversation_id)
return render(
request,
"chat/index.html",
{
"conversation": None,
"conversations": [],
"form": ChatForm(),
"documents": [],
"result": None,
"audit_log": None,
"node_results": [],
"active_node": None,
},
)
def detail(request, conversation_id: str):
conversation = get_object_or_404(Conversation, conversation_id=conversation_id)
batch = SubmissionBatch.objects.filter(batch_id=conversation.batch_id).first()
documents = UploadedDocument.objects.filter(batch=batch)
form = ChatForm(request.POST or None, documents=documents)
result = None
audit_log = None
documents = UploadedDocument.objects.filter(
scenario_id=scenario["id"],
status=UploadedDocument.STATUS_INDEXED,
)
form = ChatForm(request.POST or None, documents=documents)
active_node = None
task_modes = [
{"name": "目录汇总", "description": "汇总文件、页数、章节点和目录型文档。"},
{"name": "完整性检查", "description": "对照法规模板检查齐套性、缺失项和错放项。"},
@@ -41,28 +46,46 @@ def index(request, scenario_id: str):
{"name": "综合风险报告", "description": "形成高优先级问题、建议动作和责任人通知。"},
]
if request.method == "POST" and form.is_valid():
scenario = get_scenario("document_review")
message = form.cleaned_data["message"]
try:
# 只把必要的运行选项传给 Agent Core避免在 View 中散落模型细节。
result = run_agent(
scenario,
message,
options={"document_ids": form.cleaned_data["document_ids"]},
options={
"conversation_id": conversation.conversation_id,
"batch_id": conversation.batch_id,
"product_name": conversation.product_name,
"document_ids": form.cleaned_data["document_ids"],
},
)
except Exception as exc:
result = AgentResult(status="failed", error=str(exc), answer="")
audit_log = create_audit_log(scenario["id"], scenario["name"], message, result)
audit_log = create_audit_log(
"document_review",
"注册审核智能体",
message,
result,
batch_id=conversation.batch_id,
conversation_id=conversation.conversation_id,
product_name=conversation.product_name,
)
active_node = "risk"
return render(
request,
"chat/index.html",
{
"scenario": scenario,
"conversation": conversation,
"conversations": Conversation.objects.all(),
"batch": batch,
"form": form,
"documents": documents,
"document_count": documents.count(),
"result": result,
"audit_log": audit_log,
"task_modes": task_modes,
"node_results": conversation.node_results,
"active_node": active_node,
},
)