diff --git a/apps/chat/forms.py b/apps/chat/forms.py
index 90624ac..5f5d286 100644
--- a/apps/chat/forms.py
+++ b/apps/chat/forms.py
@@ -2,6 +2,8 @@ from django import forms
class ChatForm(forms.Form):
+ # 该表单只负责收集用户问题和可选文档范围,
+ # 不承载任何 Agent 业务逻辑,便于在 View 层保持轻量。
message = forms.CharField(
label="问题",
max_length=4000,
@@ -9,7 +11,12 @@ class ChatForm(forms.Form):
"required": "请输入要咨询的问题。",
"max_length": "问题过长,请控制在 4000 字以内。",
},
- widget=forms.Textarea(attrs={"rows": 6}),
+ widget=forms.Textarea(
+ attrs={
+ "rows": 8,
+ "placeholder": "例如:请结合已上传 SOP,分析当前异常的原因、风险等级和建议动作。",
+ }
+ ),
)
document_ids = forms.MultipleChoiceField(
label="文档范围",
@@ -22,9 +29,12 @@ class ChatForm(forms.Form):
def __init__(self, *args, documents=None, **kwargs):
super().__init__(*args, **kwargs)
documents = documents or []
+ # 仅允许选择当前场景且已完成入库的文档,
+ # 避免前端把无效文件范围传入 Agent Core。
self.fields["document_ids"].choices = [
(str(document.id), document.original_name) for document in documents
]
def clean_document_ids(self):
+ # View 与 Agent Core 都使用整型文档 ID,统一在表单层完成转换。
return [int(document_id) for document_id in self.cleaned_data.get("document_ids", [])]
diff --git a/apps/chat/views.py b/apps/chat/views.py
index 38eeb53..c7af5c2 100644
--- a/apps/chat/views.py
+++ b/apps/chat/views.py
@@ -10,6 +10,8 @@ from .forms import ChatForm
def index(request, scenario_id: str):
+ # View 只负责请求编排、表单校验和模板渲染。
+ # 具体 Agent 执行、审计写入和文档筛选规则分别交给独立模块处理。
try:
scenario = get_scenario(scenario_id)
except ScenarioNotFound:
@@ -34,6 +36,7 @@ def index(request, scenario_id: str):
if request.method == "POST" and form.is_valid():
message = form.cleaned_data["message"]
try:
+ # 只把必要的运行选项传给 Agent Core,避免在 View 中散落模型细节。
result = run_agent(
scenario,
message,
@@ -50,6 +53,7 @@ def index(request, scenario_id: str):
"scenario": scenario,
"form": form,
"documents": documents,
+ "document_count": documents.count(),
"result": result,
"audit_log": audit_log,
},
diff --git a/templates/audit/log_detail.html b/templates/audit/log_detail.html
index 7f90f67..fc05fe8 100644
--- a/templates/audit/log_detail.html
+++ b/templates/audit/log_detail.html
@@ -1,37 +1,56 @@
-
-
-
-
- 审计日志详情
-
-
- 审计日志详情 #{{ log.id }}
-
-
- 用户输入
- {{ log.user_input }}
+{% extends "base.html" %}
+
+{% block title %}审计日志详情{% endblock %}
+
+{% block content %}
+
+
+
+
+ 基础信息
+
+
+
+
+ 用户输入
+ {{ log.user_input|linebreaksbr }}
+
+
+
+ 最终回答
+ {{ log.final_answer|linebreaksbr }}
+
+
+
+ 结构化输出
+ {{ log.structured_output }}
+
+
+
+ 引用来源
+ {{ log.retrieved_chunks }}
+
+
+
+ 工具调用
+ {{ log.tool_calls }}
+
+
+ {% if log.error_message %}
+
+ 错误信息
+ {{ log.error_message }}
+
+ {% endif %}
-
- 最终回答
- {{ log.final_answer }}
-
-
- 结构化输出
- {{ log.structured_output }}
-
-
- 引用来源
- {{ log.retrieved_chunks }}
-
-
- 工具调用
- {{ log.tool_calls }}
-
- {% if log.error_message %}
-
- 错误信息
- {{ log.error_message }}
-
- {% endif %}
-
-
+{% endblock %}
diff --git a/templates/audit/log_list.html b/templates/audit/log_list.html
index 54c0823..94f6ffc 100644
--- a/templates/audit/log_list.html
+++ b/templates/audit/log_list.html
@@ -1,37 +1,40 @@
-
-
-
-
- 审计日志
-
-
- 审计日志
-
-
-
-
- | ID |
- 场景 |
- 状态 |
- 模型 |
- 耗时 |
- 详情 |
-
-
-
- {% for log in logs %}
+{% extends "base.html" %}
+
+{% block title %}审计日志{% endblock %}
+
+{% block content %}
+
+
+
+
+
- | {{ log.id }} |
- {{ log.scenario_name }} |
- {{ log.status }} |
- {{ log.model_name }} |
- {{ log.latency_ms }} ms |
- 查看 |
+ ID |
+ 场景 |
+ 状态 |
+ 模型 |
+ 耗时 |
+ 详情 |
- {% empty %}
- | 暂无审计日志。 |
- {% endfor %}
-
-
-
-
+
+
+ {% for log in logs %}
+
+ | {{ log.id }} |
+ {{ log.scenario_name }} |
+ {{ log.status }} |
+ {{ log.model_name }} |
+ {{ log.latency_ms }} ms |
+ 查看详情 |
+
+ {% empty %}
+ | 暂无审计日志,先去执行一次对话吧。 |
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..1545237
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,344 @@
+
+
+
+
+
+ {% block title %}Universal Agent Demo Framework{% endblock %}
+
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
diff --git a/templates/chat/index.html b/templates/chat/index.html
index d9431bf..663ba49 100644
--- a/templates/chat/index.html
+++ b/templates/chat/index.html
@@ -1,61 +1,169 @@
-
-
-
-
- Agent 对话
-
-
- Agent 对话
-
+{% extends "base.html" %}
+{% block title %}{{ scenario.name|default:"Agent 对话" }}{% endblock %}
+
+{% block content %}
{% if error %}
- {{ error }}
+
{% endif %}
{% if scenario %}
-
- {{ scenario.name }}
- {{ scenario.description }}
- 角色:{{ scenario.agent.role }}
- 目标:{{ scenario.agent.goal }}
+
+
+
+
+
+ 提问面板
+ 可以直接提问,也可以勾选部分已入库文档作为当前上下文范围。
+
+
+
+
+ 执行说明
+
+ -
+ 1. 场景配置
+ 系统会先读取当前 YAML 场景配置,确定角色、目标、工具和输出结构。
+
+ -
+ 2. RAG 与工具
+ 如果场景启用了知识库检索,系统会根据你的问题召回相关片段,并执行声明式工具。
+
+ -
+ 3. 结构化结果
+ Agent Core 会优先解析 JSON 输出,解析失败时回退为稳定的展示结构。
+
+
+
+
+
+
+
+ 回答总览
+ {% if result %}
+
+
+
主回答
+
{{ result.answer|linebreaksbr }}
+
+ {% if audit_log %}
+
+ 查看本次审计日志
+
+ {% endif %}
+ {% else %}
+ 提交问题后,这里会展示 Agent 的主回答、模型信息和执行状态。
+ {% endif %}
+
+
+ {% if result %}
+
+ 结构化结果
+
+
+ {% for key, value in result.structured_output.items %}
+
+ | {{ key }} |
+
+ {% if key == "answer" or key == "summary" or key == "reply" %}
+ {{ value|linebreaksbr }}
+ {% else %}
+ {{ value }}
+ {% endif %}
+ |
+
+ {% endfor %}
+
+
+
+
+
+ 引用片段
+ {% if result.references %}
+
+ {% else %}
+ 当前回答没有引用知识库片段。
+ {% endif %}
+
+
+
+ 工具调用
+ {% if result.tool_calls %}
+
+ {% else %}
+ 当前场景没有声明工具,或本次执行无需调用工具。
+ {% endif %}
+
+
+ {% if result.error %}
+
+ 错误信息
+ {{ result.error }}
+
+ {% endif %}
+ {% endif %}
+
-
-
-
- {% if result %}
-
- 回答
- {{ result.answer }}
- 模型:{{ result.model_name }}
- 状态:{{ result.status }}
- 耗时:{{ result.latency_ms }} ms
-
-
- 结构化输出
- {{ result.structured_output }}
-
-
- 引用来源
- {{ result.references }}
-
-
- 工具调用
- {{ result.tool_calls }}
-
- {% if audit_log %}
- 查看审计日志
- {% endif %}
- {% if result.error %}
-
- 错误信息
- {{ result.error }}
-
- {% endif %}
- {% endif %}
{% endif %}
-
-
+{% endblock %}
diff --git a/templates/documents/document_list.html b/templates/documents/document_list.html
index 9903645..5e76cfb 100644
--- a/templates/documents/document_list.html
+++ b/templates/documents/document_list.html
@@ -1,50 +1,53 @@
-
-
-
-
- 文件列表
-
-
- 文件列表
-
-
-
-
- | 文件名 |
- 场景 |
- 类型 |
- 大小 |
- 状态 |
- 操作 |
-
-
-
- {% for document in documents %}
+{% extends "base.html" %}
+
+{% block title %}文档中心{% endblock %}
+
+{% block content %}
+
+
+
+
+
- | {{ document.original_name }} |
- {{ document.scenario_id }} |
- {{ document.file_type }} |
- {{ document.size }} |
- {{ document.status }} |
-
- {% if document.status != "indexed" %}
-
- {% endif %}
- {% if document.error_message %}
- {{ document.error_message }}
- {% endif %}
- |
+ 文件名 |
+ 场景 |
+ 类型 |
+ 大小 |
+ 状态 |
+ 操作 |
- {% empty %}
- | 暂无文件。 |
- {% endfor %}
-
-
-
-
+
+
+ {% for document in documents %}
+
+ | {{ document.original_name }} |
+ {{ document.scenario_id }} |
+ {{ document.file_type }} |
+ {{ document.size }} |
+ {{ document.status }} |
+
+ {% if document.status != "indexed" %}
+
+ {% else %}
+ 已可用于检索
+ {% endif %}
+ {% if document.error_message %}
+ {{ document.error_message }}
+ {% endif %}
+ |
+
+ {% empty %}
+ | 暂无文件,请先上传题目材料。 |
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/templates/documents/upload.html b/templates/documents/upload.html
index 6285233..72499b7 100644
--- a/templates/documents/upload.html
+++ b/templates/documents/upload.html
@@ -1,26 +1,39 @@
-
-
-
-
- 上传文件
-
-
- 上传文件
-
-
-
-
+{% extends "base.html" %}
+
+{% block title %}上传文件{% endblock %}
+
+{% block content %}
+
+
+
+
+
+{% endblock %}
diff --git a/templates/scenarios/index.html b/templates/scenarios/index.html
index c848d37..83528cc 100644
--- a/templates/scenarios/index.html
+++ b/templates/scenarios/index.html
@@ -1,23 +1,30 @@
-
-
-
-
- Universal Agent Demo Framework
-
-
- Universal Agent Demo Framework
- 用于复试展示的通用 AI Agent Demo 框架。
-
+{% extends "base.html" %}
+
+{% block title %}场景首页{% endblock %}
+
+{% block content %}
+
+
+
{% for scenario in scenarios %}
-
+
{{ scenario.name }}
{{ scenario.description }}
- 场景 ID:{{ scenario.id }}
- 进入对话
-
+
+
+ 进入对话
+
+
{% empty %}
- 暂无可用场景,请检查 configs 目录。
+ 暂无可用场景,请检查 `configs/` 目录和 YAML 配置内容。
{% endfor %}
-
-
-
+
+{% endblock %}
diff --git a/tests/test_chat.py b/tests/test_chat.py
index 7cbe541..7bae4cc 100644
--- a/tests/test_chat.py
+++ b/tests/test_chat.py
@@ -1,5 +1,6 @@
from django.urls import reverse
+from agent_core.results import AgentResult
from apps.audit.models import AgentAuditLog
from apps.documents.models import UploadedDocument
@@ -58,3 +59,49 @@ def test_chat_passes_selected_document_ids_to_agent_core(client, db, monkeypatch
assert response.status_code == 200
assert captured["options"]["document_ids"] == [selected.id]
assert other.id not in captured["options"]["document_ids"]
+
+
+def test_chat_renders_structured_output_references_and_tool_calls(client, db, monkeypatch):
+ def fake_run_agent(scenario_config, user_input, options=None):
+ return AgentResult(
+ answer="建议先隔离现场。",
+ structured_output={
+ "output_type": "quality_report",
+ "summary": "发现异常批次需要立即处置。",
+ "risk_level": "high",
+ "suggested_actions": ["隔离现场", "通知负责人"],
+ },
+ references=[
+ {
+ "source": "sop.md",
+ "content": "异常处理 SOP:先隔离现场,再通知负责人。",
+ }
+ ],
+ tool_calls=[
+ {
+ "tool_name": "query_demo_records",
+ "success": True,
+ "result": {"records": [{"title": "A线缺陷"}]},
+ "error": "",
+ }
+ ],
+ model_name="mock-model",
+ status="success",
+ )
+
+ monkeypatch.setattr("apps.chat.views.run_agent", fake_run_agent)
+
+ response = client.post(
+ reverse("chat:index", args=["quality_analysis"]),
+ {"message": "分析 A 线异常"},
+ )
+
+ content = response.content.decode("utf-8")
+ assert response.status_code == 200
+ assert "结构化结果" in content
+ assert "发现异常批次需要立即处置" in content
+ assert "引用片段" in content
+ assert "sop.md" in content
+ assert "工具调用" in content
+ assert "query_demo_records" in content
+ assert "查看本次审计日志" in content