feat: 重构资料包模型与会话绑定主链路
This commit is contained in:
52
apps/chat/migrations/0001_initial.py
Normal file
52
apps/chat/migrations/0001_initial.py
Normal 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
34
apps/chat/models.py
Normal 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
26
apps/chat/services.py
Normal 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}"
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user