Compare commits

...

10 Commits

89 changed files with 5484 additions and 4225 deletions

View File

@@ -4,14 +4,18 @@
## 项目定位
Universal Agent Demo Framework 是一个用于复试展示的通用 AI Agent Demo 框架。
当前项目已根据真实笔试题切换为:
```text
试剂盒临床注册文件准备与审核智能体平台
```
优先目标:
- 快速适配未知复试题
- 围绕 NMPA 体外诊断试剂注册申报资料场景完成可演示闭环
- 保证本地可运行。
- 保证代码结构清楚,方便讲解。
- 避免为了平台完整性牺牲改题速度
- 允许在保留主架构边界前提下进行大幅度业务重构
## 架构原则
@@ -36,23 +40,23 @@ Django 单体 + 独立 Agent Core + Docker Compose
### apps.scenarios
负责场景列表、场景配置读取、场景元信息展示。
负责注册审核任务列表、任务配置读取、任务元信息展示。
### apps.documents
负责文件上传、文件记录、文件状态和触发 RAG 入库。
负责注册资料上传、文件记录、章节点归类、页数与文本处理状态和触发 RAG 入库。
### apps.chat
负责对话页面、用户输入表单、调用 Agent Core 和展示结果。
负责审核工作台、用户输入表单、调用 Agent Core 和展示结构化审核结果。
### apps.audit
负责审计日志模型、日志写入服务、日志列表和详情页。
负责审计日志模型、日志写入服务、日志列表和详情页,以及审核留痕展示
### agent_core
负责 Agent 编排、RAG、工具注册、LLM Provider结构化输出和 Adapter 扩展
负责注册审核 Agent 编排、RAG、工具注册、规则执行、LLM Provider结构化输出。
## 开发顺序
@@ -69,6 +73,14 @@ Django 单体 + 独立 Agent Core + Docker Compose
9. 完成内置工具系统。
10. 补 Docker Compose 一键启动。
当前仓库状态说明:
- Django 单体骨架已完成。
- 通用场景 YAML、Chat、Documents、Audit 和 Agent Core 已具备可重构基础。
- Agent Core 已具备 Prompt 编排、结构化解析、工具注册和 RAG fallback / Chroma 双路径。
- 全量测试已覆盖主要模块行为,并默认隔离真实 LLM 网络调用。
- 当前需求文档已按真实笔试题重写到 `docs/需求分析/`
## 编码约定
- Python 代码优先保持简单、直观、可讲解。
@@ -79,6 +91,9 @@ Django 单体 + 独立 Agent Core + Docker Compose
- 模型调用必须通过 LLM Provider不允许散落在业务代码中。
- 审计日志要记录成功和失败两种情况。
- 不在日志中保存 API Key、密钥或敏感环境变量。
- 新增或重构模块时,优先补清晰的中文注释,说明职责边界、输入输出和设计取舍。
- 页面模板优先直接表达业务信息,不在模板中堆积复杂逻辑判断。
- 测试优先覆盖服务层和核心编排逻辑,再由页面测试补齐关键展示行为。
## 文档约定
@@ -111,6 +126,14 @@ configs/
- `README.md`
- `docs/需求分析/1.V1总需求文档.md`
- 相关模块需求文档
- `AGENTS.md` 中的协作边界与当前实现状态
推荐同步文档的场景:
- 新增用户可见页面或流程。
- 调整环境变量、生效方式或部署命令。
- 修改 Agent Core 的输入输出合约。
- 新增工具、审计字段或场景配置字段。
## 测试与验证约定
@@ -124,6 +147,20 @@ configs/
- 审计日志能记录。
- Docker Compose 可以启动。
当前默认验证命令:
```bash
pytest
python manage.py check
docker compose config
```
补充约定:
- 若本地 `.env` 存在真实模型密钥,测试仍应保持可离线执行。
- 每完成一项功能或一轮重构后,应先跑相关测试,再跑全量测试或核心回归测试。
- 完成改动后,按逻辑分组使用 Conventional Commit 风格提交到本地。
## 不优先做的事项
第一版不要优先做:

120
README.md
View File

@@ -1,13 +1,13 @@
# Universal Agent Demo Framework
# 试剂盒临床注册文件准备与审核智能体平台
用于复试展示的通用 AI Agent Demo 框架
用于复试展示的体外诊断试剂注册申报资料准备与审核系统
项目目标不是提前猜中某一个具体业务题,而是先准备一个可快速改题的基础平台。拿到复试题目后,可以通过修改场景配置、上传知识库、补充少量工具函数,快速完成一个可演示的企业业务 Agent
当前项目已根据真实笔试题重构目标定位,重点服务于 NMPA 境内第三类体外诊断试剂注册申报场景,覆盖资料整理、目录汇总、法规完整性检查、关键信息抽取、跨文档一致性核查、风险预警和审计留痕
## 核心理念
```text
业务 Agent = 场景配置 + 知识库 + 工具集 + 输出模板 + 审计日志 + 模型适配器
注册审核 Agent = 任务配置 + 资料库 + 法规规则 + 工具集 + 输出模板 + 审计日志 + 模型适配器
```
## 技术路线
@@ -24,18 +24,16 @@ V1 采用:
默认不强依赖 Dify。系统预留 Adapter 设计,后续可以接入 Dify、OpenAI Agents SDK 或其他 Agent 编排平台。
## 适用复试题型
## 当前业务主线
| 题型 | 推荐场景模板 |
|---|---|
| SOP 问答 | `knowledge_qa` |
| 制度问答 | `knowledge_qa` |
| 文档审核 | `document_review` |
| 客服工单 | `ticket_assistant` |
| 质量异常分析 | `quality_analysis` |
| 财务审核 | `risk_audit` |
| 采购审核 | `risk_audit` |
| 合同风险分析 | `document_review``risk_audit` |
当前系统围绕以下注册申报审核闭环展开:
1. 导入注册资料。
2. 汇总文件目录与页数。
3. 对照法规要求检查完整性。
4. 抽取产品关键信息。
5. 核查跨文档一致性。
6. 输出风险预警与处理建议。
## 模块划分
@@ -79,11 +77,11 @@ universal-agent-demo/
schemas/
configs/
knowledge_qa.yaml
document_review.yaml
ticket_assistant.yaml
quality_analysis.yaml
risk_audit.yaml
registration_overview.yaml
registration_completeness_check.yaml
registration_field_extraction.yaml
registration_consistency_review.yaml
registration_risk_report.yaml
data/
uploads/
@@ -96,17 +94,35 @@ universal-agent-demo/
V1 需要完成:
- 场景列表。
- Agent 对话页
- 文件上传
- 文档入库。
- RAG 检索
- 内置工具调用
- 结构化输出展示
- 审计日志
- 注册审核任务列表。
- 审核工作台
- 资料上传与管理
- 文档解析与入库。
- 目录与页数汇总
- 法规完整性检查
- 关键信息抽取与回填预览
- 一致性核查
- 风险预警与审计日志。
- 模型 API 可配置。
- Docker 一键启动。
当前代码基线已经落地的通用能力:
- 首页支持展示场景摘要、RAG 状态、工具数量。
- 非法 YAML 场景配置会被自动跳过,并在首页展示错误摘要。
- 对话页支持问题输入、文档范围选择、结构化结果、引用片段、工具调用和审计入口展示。
- 文档页支持上传、列表查看、手动入库、失败原因提示和重试。
- 审计页支持列表摘要、按场景筛选、详情查看、原始输出展示和敏感信息脱敏。
- Agent Core 已具备 Prompt 编排、OpenAI 兼容 Provider、结构化输出解析、RAG 检索和工具注册机制。
- 测试环境默认固定使用 Mock Provider避免误调用本地真实模型配置。
## 本轮需求文档
本轮已按模块重写需求分析,详见:
- [V1 总需求文档](F:\PyCharm\DEMO-AGENT\docs\需求分析\1.V1总需求文档.md)
- [需求重构总览与待确认事项](F:\PyCharm\DEMO-AGENT\docs\需求分析\0.需求重构总览与待确认事项.md)
V1 暂不重点做:
- 多租户。
@@ -128,6 +144,19 @@ V1 暂不重点做:
6. 用 2 到 3 个问题测试效果。
7. 演示场景配置、知识库引用、工具调用、结构化输出和审计日志。
## 当前页面概览
当前项目包含以下主要页面:
| 页面 | 路径 | 当前能力 |
|---|---|---|
| 场景首页 | `/` | 展示场景名称、描述、适用题型、RAG 状态、工具数和配置异常摘要 |
| 对话页 | `/chat/<scenario_id>/` | 输入问题、勾选已入库文档、查看结构化结果、引用片段、工具调用和审计入口 |
| 文档列表页 | `/documents/` | 查看文档状态、错误信息、上传时间并手动触发入库 |
| 文档上传页 | `/documents/upload/` | 选择场景并上传 `.txt``.md``.pdf``.docx` 文件 |
| 审计列表页 | `/audit/` | 查看执行摘要并按场景筛选 |
| 审计详情页 | `/audit/<log_id>/` | 查看输入、最终回答、结构化输出、引用、工具调用、原始输出和错误信息 |
## 计划启动方式
本地启动:
@@ -146,6 +175,16 @@ docker compose up --build
当前文档目标已统一为完整 V1 闭环:真实 Chroma RAG、OpenAI 兼容 LLM、OpenAI 兼容 Embedding、工具注册和审计日志。开发阶段可以用测试桩验证页面和边界但不作为 V1 验收结果。
推荐首次启动步骤:
```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver
```
## 环境变量
项目当前通过 `os.environ` 读取配置,核心变量如下:
@@ -181,6 +220,31 @@ CHROMA_PATH=data/chroma
- 本地开发:复制 `.env.example``.env`,填入真实参数后运行。
- Docker 演示:确认 `.env` 已配置后,再执行 `docker compose up --build`
## 测试与验证
当前项目已经补有较完整的模块级测试,覆盖:
- 场景配置读取、非法配置容错和首页展示。
- 对话提交、文档范围传递、结构化结果展示。
- 文档上传、文本抽取、入库成功与失败提示。
- 审计日志落库、筛选、原始输出展示和 API Key 脱敏。
- Agent Core 的 Prompt 编排、结构化解析、RAG fallback 检索。
- Tool Registry 和内置工具行为。
- LLM / Embedding Provider 的配置与请求构造。
常用验证命令:
```bash
pytest
python manage.py check
docker compose config
```
说明:
- 测试环境默认通过 `tests/conftest.py` 固定 `LLM_PROVIDER=mock`,避免回归测试误走真实网络请求。
- 当前本地 `.env` 可能包含真实模型配置,但不会影响自动化测试稳定性。
## 文档入口
- [V1 总需求文档](docs/需求分析/1.V1总需求文档.md)

View File

@@ -6,15 +6,20 @@ from urllib.request import Request, urlopen
class LLMConfigurationError(ValueError):
pass
"""LLM 调用缺少关键配置时抛出的业务异常。"""
class EmbeddingConfigurationError(ValueError):
pass
"""Embedding 调用缺少关键配置时抛出的业务异常。"""
@dataclass
class LLMResponse:
"""
统一的模型响应对象。
Agent Core 的 Orchestrator 只依赖这一个结构,而不直接感知底层供应商差异。
"""
content: str = ""
model_name: str = ""
success: bool = True
@@ -22,17 +27,18 @@ class LLMResponse:
class MockLLMProvider:
"""
本地和测试默认使用的 Mock Provider。
设计目标不是拟真对话,而是提供一个稳定、可断言、可结构化解析的响应,
让前后端在未接入真实模型时也能完整演示链路。
"""
def __init__(self, model_name: str = "mock-model"):
self.model_name = model_name or "mock-model"
def generate(self, messages: list[dict], response_format: dict | None = None) -> LLMResponse:
# Mock Provider 的职责是让页面和测试在未接入真实模型时也能闭环。
# 因此这里直接返回稳定 JSON方便后续统一走结构化解析逻辑。
user_content = ""
for message in reversed(messages):
if message.get("role") == "user":
user_content = message.get("content", "")
break
user_content = _find_last_user_message(messages)
return LLMResponse(
content=json.dumps(
{
@@ -48,6 +54,8 @@ class MockLLMProvider:
class OpenAICompatibleProvider:
"""调用 OpenAI Chat Completions 兼容接口的 Provider。"""
def __init__(self, api_key: str, base_url: str, model_name: str):
self.api_key = api_key
self.base_url = base_url
@@ -85,6 +93,8 @@ class OpenAICompatibleProvider:
class OpenAICompatibleEmbeddingProvider:
"""调用 OpenAI Embeddings 兼容接口的 Provider。"""
def __init__(self, api_key: str, base_url: str, model_name: str):
self.api_key = api_key
self.base_url = base_url
@@ -102,7 +112,78 @@ class OpenAICompatibleEmbeddingProvider:
return [item.get("embedding", []) for item in data.get("data", [])]
def create_llm_provider(config: dict | None = None):
"""
根据配置创建 LLM Provider。
默认策略:
- 明确指定 `LLM_PROVIDER=mock` 时使用 Mock
- 未指定但存在 `LLM_API_KEY` 时默认走 OpenAI 兼容接口
- 否则回退到 Mock保证页面仍可闭环
"""
config = config or {}
provider_name = _resolve_provider_name(config)
model_name = config.get("LLM_MODEL", "mock-model")
if provider_name == "mock":
return MockLLMProvider(model_name=model_name)
return OpenAICompatibleProvider(
api_key=config.get("LLM_API_KEY", ""),
base_url=config.get("LLM_BASE_URL", "https://api.openai.com/v1"),
model_name=model_name,
)
def create_embedding_provider(config: dict | None = None):
"""
创建 Embedding Provider。
当未单独配置 Embedding Key 或 Base URL 时,会自动复用 LLM 配置,
以减少复试演示时的环境变量负担。
"""
config = config or {}
return OpenAICompatibleEmbeddingProvider(
api_key=config.get("EMBEDDING_API_KEY", config.get("LLM_API_KEY", "")),
base_url=config.get("EMBEDDING_BASE_URL", config.get("LLM_BASE_URL", "https://api.openai.com/v1")),
model_name=config.get("EMBEDDING_MODEL", "text-embedding-3-small"),
)
def get_runtime_llm_config(overrides: dict | None = None) -> dict:
"""
从环境变量读取运行时配置。
Agent Core 通过这一层读取模型配置,避免直接依赖 Django settings
这样本模块在独立脚本、测试和 Django 环境中都可复用。
"""
config = {
"LLM_PROVIDER": os.environ.get("LLM_PROVIDER", ""),
"LLM_API_KEY": os.environ.get("LLM_API_KEY", ""),
"LLM_BASE_URL": os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
"LLM_MODEL": os.environ.get("LLM_MODEL", "mock-model"),
}
if overrides:
config.update(overrides)
return config
def _resolve_provider_name(config: dict) -> str:
"""统一推导当前应启用的 Provider 名称。"""
provider_name = config.get("LLM_PROVIDER")
if provider_name:
return provider_name
return "openai_compatible" if config.get("LLM_API_KEY") else "mock"
def _find_last_user_message(messages: list[dict]) -> str:
"""从消息列表中提取最后一条用户输入,用于 Mock Provider 回显。"""
for message in reversed(messages):
if message.get("role") == "user":
return message.get("content", "")
return ""
def _post_json(base_url: str, endpoint: str, api_key: str, payload: dict) -> dict:
"""向 OpenAI 兼容接口发送 JSON POST 请求并解析响应。"""
url = f"{base_url.rstrip('/')}/{endpoint}"
request = Request(
url,
@@ -118,45 +199,3 @@ def _post_json(base_url: str, endpoint: str, api_key: str, payload: dict) -> dic
return json.loads(response.read().decode("utf-8"))
except URLError as exc:
raise RuntimeError(f"OpenAI 兼容接口调用失败:{exc}") from exc
def create_llm_provider(config: dict | None = None):
config = config or {}
provider_name = config.get("LLM_PROVIDER")
if not provider_name:
provider_name = "openai_compatible" if config.get("LLM_API_KEY") else "mock"
model_name = config.get("LLM_MODEL", "mock-model")
if provider_name == "mock":
return MockLLMProvider(model_name=model_name)
return OpenAICompatibleProvider(
api_key=config.get("LLM_API_KEY", ""),
base_url=config.get("LLM_BASE_URL", "https://api.openai.com/v1"),
model_name=model_name,
)
def create_embedding_provider(config: dict | None = None):
config = config or {}
return OpenAICompatibleEmbeddingProvider(
api_key=config.get("EMBEDDING_API_KEY", config.get("LLM_API_KEY", "")),
base_url=config.get("EMBEDDING_BASE_URL", config.get("LLM_BASE_URL", "https://api.openai.com/v1")),
model_name=config.get("EMBEDDING_MODEL", "text-embedding-3-small"),
)
def get_runtime_llm_config(overrides: dict | None = None) -> dict:
"""
从环境变量读取运行时配置。
Agent Core 通过这层读取模型配置,避免直接依赖 Django settings
这样本模块在独立脚本、测试和 Django 中都能复用。
"""
config = {
"LLM_PROVIDER": os.environ.get("LLM_PROVIDER", ""),
"LLM_API_KEY": os.environ.get("LLM_API_KEY", ""),
"LLM_BASE_URL": os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
"LLM_MODEL": os.environ.get("LLM_MODEL", "mock-model"),
}
if overrides:
config.update(overrides)
return config

View File

@@ -3,6 +3,14 @@ from dataclasses import dataclass, field
@dataclass
class AgentResult:
"""
Agent Core 对 Django 层暴露的统一结果对象。
任何底层编排实现都必须返回这一结构,确保:
- Chat 页面有稳定字段可展示
- Audit 模块有稳定字段可落库
- 未来替换编排引擎时不影响 Django 业务层
"""
answer: str = ""
structured_output: dict = field(default_factory=dict)
references: list = field(default_factory=list)

View File

@@ -5,6 +5,7 @@ from .models import AgentAuditLog, DemoBusinessRecord
@admin.register(AgentAuditLog)
class AgentAuditLogAdmin(admin.ModelAdmin):
"""便于在 Django Admin 中快速查看一次 Agent 执行的关键信息。"""
list_display = ("id", "scenario_name", "status", "model_name", "latency_ms", "created_at")
list_filter = ("status", "scenario_id")
search_fields = ("scenario_id", "scenario_name", "user_input", "final_answer")
@@ -12,6 +13,7 @@ class AgentAuditLogAdmin(admin.ModelAdmin):
@admin.register(DemoBusinessRecord)
class DemoBusinessRecordAdmin(admin.ModelAdmin):
"""管理工具查询依赖的示例业务记录。"""
list_display = ("id", "title", "scenario_id", "record_type", "created_at")
list_filter = ("scenario_id", "record_type")
search_fields = ("title", "scenario_id", "record_type")

View File

@@ -2,5 +2,6 @@ from django.apps import AppConfig
class AuditConfig(AppConfig):
"""Audit 模块应用配置。"""
default_auto_field = "django.db.models.BigAutoField"
name = "apps.audit"

View File

@@ -2,6 +2,14 @@ from django.db import models
class AgentAuditLog(models.Model):
"""
保存一次 Agent 执行的完整审计快照。
该模型是“系统可解释性”的核心:
- 对话页负责触发执行
- Agent Core 负责生成结果
- Audit 模型负责长期保存输入、引用、工具调用和模型输出
"""
# 审计状态需要同时服务数据库检索和前端展示。
STATUS_SUCCESS = "success"
STATUS_FAILED = "failed"
@@ -41,6 +49,12 @@ class AgentAuditLog(models.Model):
class DemoBusinessRecord(models.Model):
"""
演示用业务记录表。
该表不直接参与页面主流程,而是供内置工具 `query_demo_records`
查询,证明 Agent 除知识库外也可以结合结构化业务数据。
"""
scenario_id = models.CharField(max_length=100, db_index=True)
record_type = models.CharField(max_length=100, db_index=True)
title = models.CharField(max_length=255)

View File

@@ -3,23 +3,20 @@ from agent_core.results import AgentResult
from .models import AgentAuditLog
def _mask_sensitive_text(value: str) -> str:
masked = value
for marker in ("LLM_API_KEY=", "EMBEDDING_API_KEY="):
if marker in masked:
prefix, _, suffix = masked.partition(marker)
secret, separator, rest = suffix.partition(" ")
masked_secret = "sk-***" if secret.startswith("sk-") else "***"
masked = f"{prefix}{marker}{masked_secret}{separator}{rest}"
return masked
def create_audit_log(
scenario_id: str,
scenario_name: str,
user_input: str,
agent_result: AgentResult,
) -> AgentAuditLog:
"""
将一次 Agent 执行结果落库为审计日志。
设计原则:
- 成功与失败都必须记录,方便复盘整条执行链路
- 敏感信息在写库前先脱敏,避免误存 API Key
- 对前端和 Django Model 统一输出稳定字段
"""
return AgentAuditLog.objects.create(
scenario_id=scenario_id,
scenario_name=scenario_name,
@@ -32,5 +29,29 @@ def create_audit_log(
model_name=agent_result.model_name,
latency_ms=max(agent_result.latency_ms, 0),
status=agent_result.status,
error_message=_mask_sensitive_text(agent_result.error),
error_message=mask_sensitive_text(agent_result.error),
)
def mask_sensitive_text(value: str) -> str:
"""
对错误文本中的敏感配置进行脱敏。
当前至少处理:
- `LLM_API_KEY=...`
- `EMBEDDING_API_KEY=...`
"""
masked = value
for marker in ("LLM_API_KEY=", "EMBEDDING_API_KEY="):
masked = _mask_token_after_marker(masked, marker)
return masked
def _mask_token_after_marker(value: str, marker: str) -> str:
"""将 marker 后紧跟的 token 替换为脱敏占位符。"""
if marker not in value:
return value
prefix, _, suffix = value.partition(marker)
secret, separator, rest = suffix.partition(" ")
masked_secret = "sk-***" if secret.startswith("sk-") else "***"
return f"{prefix}{marker}{masked_secret}{separator}{rest}"

View File

@@ -5,6 +5,7 @@ from . import views
app_name = "audit"
# V1 的审计功能由列表页和详情页组成,暂不拆分页或复杂筛选接口。
urlpatterns = [
path("", views.log_list, name="list"),
path("<int:log_id>/", views.log_detail, name="detail"),

View File

@@ -20,5 +20,7 @@ def log_list(request):
def log_detail(request, log_id: int):
# 详情页只负责按主键加载审计快照并渲染;
# 所有脱敏和字段映射都应在服务层完成。
audit_log = get_object_or_404(AgentAuditLog, pk=log_id)
return render(request, "audit/log_detail.html", {"log": audit_log})

View File

@@ -2,5 +2,6 @@ from django.apps import AppConfig
class ChatConfig(AppConfig):
"""Chat 模块应用配置。"""
default_auto_field = "django.db.models.BigAutoField"
name = "apps.chat"

View File

@@ -5,6 +5,7 @@ from . import views
app_name = "chat"
# 当前 V1 仅保留一个场景对话入口,场景详情合并在对话页中展示。
urlpatterns = [
path("<str:scenario_id>/", views.index, name="index"),
]

View File

@@ -33,6 +33,13 @@ def index(request, scenario_id: str):
status=UploadedDocument.STATUS_INDEXED,
)
form = ChatForm(request.POST or None, documents=documents)
task_modes = [
{"name": "目录汇总", "description": "汇总文件、页数、章节点和目录型文档。"},
{"name": "完整性检查", "description": "对照法规模板检查齐套性、缺失项和错放项。"},
{"name": "字段抽取", "description": "抽取产品名称、规格、适用范围、储存条件等核心字段。"},
{"name": "一致性核查", "description": "比较申请表、说明书和产品列表的字段一致性。"},
{"name": "综合风险报告", "description": "形成高优先级问题、建议动作和责任人通知。"},
]
if request.method == "POST" and form.is_valid():
message = form.cleaned_data["message"]
try:
@@ -56,5 +63,6 @@ def index(request, scenario_id: str):
"document_count": documents.count(),
"result": result,
"audit_log": audit_log,
"task_modes": task_modes,
},
)

View File

@@ -5,6 +5,7 @@ from .models import UploadedDocument
@admin.register(UploadedDocument)
class UploadedDocumentAdmin(admin.ModelAdmin):
"""管理上传文档及其入库状态,便于后台排查问题。"""
list_display = ("id", "original_name", "scenario_id", "file_type", "status", "created_at")
list_filter = ("status", "scenario_id", "file_type")
search_fields = ("original_name", "scenario_id")

View File

@@ -2,5 +2,6 @@ from django.apps import AppConfig
class DocumentsConfig(AppConfig):
"""Documents 模块应用配置。"""
default_auto_field = "django.db.models.BigAutoField"
name = "apps.documents"

View File

@@ -2,6 +2,12 @@ from django.db import models
class UploadedDocument(models.Model):
"""
保存用户上传文档的元数据和入库状态。
设计上只记录“文件属于哪个场景、当前是否已入库、失败原因是什么”,
不把 RAG 细节耦合进模型层。
"""
# 文档状态用于驱动前端提示和后续可操作项。
STATUS_UPLOADED = "uploaded"
STATUS_INDEXED = "indexed"

View File

@@ -5,6 +5,7 @@ from . import views
app_name = "documents"
# 文档模块对外暴露三个基础动作:列表、上传、手动入库。
urlpatterns = [
path("", views.document_list, name="list"),
path("upload/", views.upload, name="upload"),

View File

@@ -12,7 +12,34 @@ from .services import create_uploaded_document, index_document
def document_list(request):
# 列表页只负责展示文档元数据和可执行操作,不处理入库细节。
documents = UploadedDocument.objects.all()
return render(request, "documents/document_list.html", {"documents": documents})
status_counts = {
"uploaded": documents.filter(status=UploadedDocument.STATUS_UPLOADED).count(),
"indexed": documents.filter(status=UploadedDocument.STATUS_INDEXED).count(),
"failed": documents.filter(status=UploadedDocument.STATUS_FAILED).count(),
"total": documents.count(),
}
processing_pipeline = [
{"title": "原始文件接收", "detail": "校验格式、大小和场景归属后保存原件。"},
{"title": "文本与表格抽取", "detail": "按 PDF / DOCX / MD / TXT 使用不同解析策略。"},
{"title": "页数统计与可信度评估", "detail": "对 Word 页数采用估算与可信度标记。"},
{"title": "章节点归类", "detail": "基于文件名、标题和正文线索识别 CH 节点。"},
{"title": "切片与索引入库", "detail": "生成知识切片,供 RAG、规则定位和审计引用使用。"},
]
exception_items = [
{"level": "待确认", "title": "CH1.2 监管信息目录.docx", "detail": "目录页码与正文页数存在偏差,建议人工复核。"},
{"level": "低可信度", "title": "目标产品说明书.docx", "detail": "Word 页数为估算值,表格抽取质量良好。"},
{"level": "失败", "title": "沟通记录扫描件.pdf", "detail": "疑似扫描件,需补做 OCR 或重新上传清晰版。"},
]
return render(
request,
"documents/document_list.html",
{
"documents": documents,
"status_counts": status_counts,
"processing_pipeline": processing_pipeline,
"exception_items": exception_items,
},
)
def upload(request):
@@ -28,7 +55,16 @@ def upload(request):
return render(
request,
"documents/upload.html",
{"form": form, "scenarios": list_scenarios()},
{
"form": form,
"scenarios": list_scenarios(),
"upload_checks": [
"文件格式支持 PDF、DOCX、MD、TXT",
"业务资料与法规依据资料需分开归属",
"目录类文件会优先参与完整性校验",
"上传完成后建议立即进入解析与入库流程",
],
},
)

View File

@@ -0,0 +1 @@

6
apps/platform_ui/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class PlatformUiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.platform_ui"

View File

@@ -0,0 +1,172 @@
def get_platform_demo_context():
batch = {
"name": "2026Q2-呼吸道多联检测试剂注册批次",
"product_name": "呼吸道病原体核酸联合检测试剂盒PCR-荧光探针法)",
"owner": "临床注册事务组",
"stage": "法规完整性复核中",
"completion": "74%",
"next_action": "补齐 CH1.11.5 沟通记录并确认产品列表版本",
}
metrics = [
{"label": "资料齐套率", "value": "82%", "note": "42 / 51 个目录项已命中"},
{"label": "法规命中率", "value": "89%", "note": "公告附件包 6 类规则已生效"},
{"label": "字段抽取完成度", "value": "67%", "note": "48 个核心字段已进入统一字段池"},
{"label": "高风险问题", "value": "03", "note": "2 个缺失项1 个跨文档冲突"},
]
workflow_overview = [
{"title": "资料进入系统", "detail": "批量上传 18 份申报资料与 6 份法规原文"},
{"title": "规则与知识装载", "detail": "按章-条-要求项-模板字段建立知识底座"},
{"title": "解析与切片", "detail": "页数统计、目录识别、表格抽取、Chroma 入库"},
{"title": "Agent 审核执行", "detail": "完整性检查、字段抽取、一致性核查"},
{"title": "结论输出", "detail": "形成风险清单、证据引用、责任人动作建议"},
]
risk_board = [
{"level": "", "title": "CH1.11.5 沟通记录缺失", "owner": "监管信息专员", "action": "补充 NMPA 沟通留痕"},
{"level": "", "title": "产品名称跨文档表述不一致", "owner": "产品资料负责人", "action": "统一申请表与说明书命名"},
{"level": "", "title": "2 份 Word 页数为估算值", "owner": "文控支持", "action": "补做版式校验"},
]
quick_links = [
{"title": "知识库配置", "url_name": "platform_ui:knowledge-base", "desc": "维护法规规则树与切片策略"},
{"title": "文件中心", "url_name": "documents:list", "desc": "查看上传、解析、切片与异常状态"},
{"title": "审核工作台", "url_name": "chat:index", "url_arg": "document_review", "desc": "发起审核、抽取与一致性核查演示"},
{"title": "工作台大屏", "url_name": "platform_ui:command-center", "desc": "面向演示的 Agent 流程解释大屏"},
]
knowledge_sources = [
{
"code": "KB-001",
"name": "公告附件包 / 资料要求说明",
"type": "法规依据",
"scope": "registration",
"updated_at": "今天 09:20",
"status": "已生效",
"owner": "法规专员",
},
{
"code": "KB-002",
"name": "批准证明文件格式要求",
"type": "模板规则",
"scope": "registration",
"updated_at": "今天 09:35",
"status": "已生效",
"owner": "模板管理员",
},
{
"code": "KB-003",
"name": "安全和性能基本原则清单",
"type": "原则规则",
"scope": "registration",
"updated_at": "今天 10:02",
"status": "待人工校订",
"owner": "法规专员",
},
{
"code": "KB-004",
"name": "CH1 监管信息目录样例",
"type": "业务资料",
"scope": "batch",
"updated_at": "今天 10:30",
"status": "已入库",
"owner": "文控专员",
},
]
rule_tree = [
{
"code": "RULE-001",
"chapter": "CH1 监管信息",
"item": "CH1.2 监管信息目录",
"requirement": "必须提供目录与页码映射",
"field": "目录文件 / 页码可信度",
"status": "启用",
},
{
"code": "RULE-002",
"chapter": "CH1 监管信息",
"item": "CH1.4 申请表",
"requirement": "申请表字段需与说明书一致",
"field": "产品名称 / 规格 / 申请人",
"status": "启用",
},
{
"code": "RULE-003",
"chapter": "CH1 监管信息",
"item": "CH1.11.5 沟通记录",
"requirement": "涉及沟通事项时需补齐记录",
"field": "沟通对象 / 时间 / 结论",
"status": "待校订",
},
{
"code": "RULE-004",
"chapter": "批准证明文件格式",
"item": "注册证输出模板",
"requirement": "字段映射需满足版式模板",
"field": "注册证字段池 / Word 模板",
"status": "启用",
},
]
knowledge_stats = [
{"label": "法规知识源", "value": "06"},
{"label": "结构化规则项", "value": "128"},
{"label": "业务资料切片", "value": "342"},
{"label": "最近人工校订", "value": "2 次"},
]
mcp_connectors = [
{"name": "飞书任务通知", "kind": "协同办公", "auth": "App Token", "status": "已连接", "sync": "5 分钟前"},
{"name": "法规规则源导入", "kind": "法规服务", "auth": "文件轮询", "status": "待验证", "sync": "今天 08:50"},
{"name": "Word 模板服务", "kind": "文档服务", "auth": "API Key", "status": "已连接", "sync": "刚刚"},
{"name": "企业主数据源", "kind": "业务系统", "auth": "MCP Bridge", "status": "未启用", "sync": "未同步"},
]
skills = [
{"name": "完整性检查 Skill", "trigger": "目录齐套性 / 章节点核查", "tools": "规则树 + RAG + 风险映射", "status": "发布中"},
{"name": "字段抽取 Skill", "trigger": "申请表 / 说明书 / 产品列表抽取", "tools": "表格抽取 + 字段池", "status": "已发布"},
{"name": "一致性核查 Skill", "trigger": "跨文档字段冲突检查", "tools": "字段比对 + 解释生成", "status": "灰度测试"},
{"name": "Word 回填 Skill", "trigger": "报送版 Word 输出", "tools": "模板映射 + 导出服务", "status": "待校验"},
]
workflow_steps = [
{"time": "09:32", "title": "载入批次资料", "detail": "识别 18 份业务资料与 6 份法规原文,建立本轮审核上下文。"},
{"time": "09:34", "title": "规则树装载", "detail": "按注册申报主流程装载 CH1 监管信息与批准证明文件格式规则。"},
{"time": "09:36", "title": "字段池初始化", "detail": "从申请表、说明书、产品列表抽取统一字段并建立来源映射。"},
{"time": "09:39", "title": "一致性检查", "detail": "检测到产品名称和样本类型存在跨文档冲突,升级为人工复核。"},
{"time": "09:42", "title": "风险输出", "detail": "生成 3 条风险项、2 条补件建议与 1 条责任人通知任务。"},
]
knowledge_filters = [
{"label": "全部", "active": True},
{"label": "法规依据", "active": False},
{"label": "模板规则", "active": False},
{"label": "业务资料", "active": False},
]
knowledge_form = {
"title": "新增 / 编辑知识源",
"fields": [
{"label": "知识源名称", "value": "体外诊断试剂注册申报资料要求及说明"},
{"label": "知识源类型", "value": "法规依据"},
{"label": "适用流程", "value": "registration"},
{"label": "状态", "value": "已生效"},
{"label": "切片策略", "value": "按章条切片,保留条款编号"},
],
}
rule_form = {
"title": "新增 / 编辑规则项",
"fields": [
{"label": "规则编码", "value": "RULE-005"},
{"label": "所属章节", "value": "CH1 监管信息"},
{"label": "规则名称", "value": "申请表签章完整性检查"},
{"label": "模板字段", "value": "签章页 / 申请人签字"},
{"label": "规则说明", "value": "若申请表缺少签章,则标记为高优先级缺失项"},
],
}
return {
"batch": batch,
"metrics": metrics,
"workflow_overview": workflow_overview,
"risk_board": risk_board,
"quick_links": quick_links,
"knowledge_sources": knowledge_sources,
"rule_tree": rule_tree,
"knowledge_stats": knowledge_stats,
"knowledge_filters": knowledge_filters,
"knowledge_form": knowledge_form,
"rule_form": rule_form,
"mcp_connectors": mcp_connectors,
"skills": skills,
"workflow_steps": workflow_steps,
}

13
apps/platform_ui/urls.py Normal file
View File

@@ -0,0 +1,13 @@
from django.urls import path
from . import views
app_name = "platform_ui"
urlpatterns = [
path("knowledge-base/", views.knowledge_base, name="knowledge-base"),
path("mcp-center/", views.mcp_center, name="mcp-center"),
path("skills/", views.skill_studio, name="skills"),
path("command-center/", views.command_center, name="command-center"),
]

23
apps/platform_ui/views.py Normal file
View File

@@ -0,0 +1,23 @@
from django.shortcuts import render
from .services import get_platform_demo_context
def knowledge_base(request):
context = get_platform_demo_context()
return render(request, "platform_ui/knowledge_base.html", context)
def mcp_center(request):
context = get_platform_demo_context()
return render(request, "platform_ui/mcp_center.html", context)
def skill_studio(request):
context = get_platform_demo_context()
return render(request, "platform_ui/skill_studio.html", context)
def command_center(request):
context = get_platform_demo_context()
return render(request, "platform_ui/command_center.html", context)

View File

@@ -2,5 +2,6 @@ from django.apps import AppConfig
class ScenariosConfig(AppConfig):
"""Scenarios 模块应用配置。"""
default_auto_field = "django.db.models.BigAutoField"
name = "apps.scenarios"

View File

@@ -4,12 +4,31 @@ from .services import list_scenario_issues, list_scenarios
def index(request):
# 首页只消费服务层给出的场景摘要,不自行拼装配置字段。
# 首页只消费服务层给出的场景摘要和错误摘要
# 不自行读取 YAML更不在 View 里做字段拼装。
return render(
request,
"scenarios/index.html",
{
"scenarios": list_scenarios(),
"scenario_issues": list_scenario_issues(),
"hero_metrics": [
{"label": "资料齐套率", "value": "82%", "note": "已识别 42 / 51 个法规模板项"},
{"label": "法规命中率", "value": "89%", "note": "公告附件包 6 类规则已加载"},
{"label": "字段抽取完成度", "value": "67%", "note": "48 个核心字段进入统一字段池"},
{"label": "高风险问题", "value": "03", "note": "含 1 个缺失项与 1 个命名冲突"},
],
"workflow_overview": [
{"title": "资料进入系统", "detail": "导入本批次注册申报资料、法规原文与模板文件。"},
{"title": "知识底座装载", "detail": "以章-条-要求项-模板字段组织法规规则。"},
{"title": "解析与切片", "detail": "完成页数统计、表格抽取、章节点归类和向量入库。"},
{"title": "Agent 审核执行", "detail": "发起完整性检查、字段抽取和一致性比对。"},
{"title": "结论输出与留痕", "detail": "形成风险清单、证据引用、责任人动作和审计日志。"},
],
"risk_board": [
{"level": "高风险", "title": "CH1.11.5 沟通记录缺失", "detail": "监管沟通事项未形成可追溯文件,无法支撑本轮章节齐套性。"},
{"level": "高风险", "title": "产品名称在申请表与说明书中不一致", "detail": "Agent 判定为跨文档命名冲突,需进入人工复核。"},
{"level": "中风险", "title": "2 份 Word 文档页数可信度不足", "detail": "版式页数使用估算值,建议复核目录页码与正文总页数。"},
],
},
)

View File

@@ -6,6 +6,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent
def load_dotenv(dotenv_path: Path) -> None:
"""
读取根目录 `.env` 并注入进程环境。
这里使用极简解析逻辑,目的是减少额外依赖,
同时让本地 `runserver`、`pytest` 与 Docker Compose 共用一套配置文件。
"""
if not dotenv_path.exists():
return
for raw_line in dotenv_path.read_text(encoding="utf-8").splitlines():
@@ -22,12 +28,14 @@ load_dotenv(BASE_DIR / ".env")
def env_bool(name: str, default: bool = False) -> bool:
"""将常见的字符串布尔值转换为 Python bool。"""
value = os.environ.get(name)
if value is None:
return default
return value.lower() in {"1", "true", "yes", "on"}
# Django 核心运行参数。
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "dev-secret-key")
DEBUG = env_bool("DJANGO_DEBUG", True)
ALLOWED_HOSTS = [
@@ -47,6 +55,7 @@ INSTALLED_APPS = [
"apps.documents",
"apps.chat",
"apps.audit",
"apps.platform_ui",
]
MIDDLEWARE = [
@@ -78,6 +87,7 @@ TEMPLATES = [
WSGI_APPLICATION = "config.wsgi.application"
# V1 默认使用 SQLite确保本地演示零外部依赖。
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
@@ -93,11 +103,15 @@ USE_TZ = True
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "media/"
# 上传根目录可通过环境变量覆盖,便于 Docker 挂载到持久化目录。
MEDIA_ROOT = Path(os.environ.get("UPLOAD_ROOT", BASE_DIR / "data" / "uploads"))
# 配置目录和 Chroma 数据目录都允许外部覆盖,方便复试现场快速切换。
SCENARIO_CONFIG_DIR = Path(os.environ.get("SCENARIO_CONFIG_DIR", BASE_DIR / "configs"))
CHROMA_PATH = Path(os.environ.get("CHROMA_PATH", BASE_DIR / "data" / "chroma"))
# LLM 与 Embedding 默认遵循“尽量少配置也能跑”的策略:
# Embedding 未单独配置时自动复用 LLM 的 Key 和 Base URL。
LLM_API_KEY = os.environ.get("LLM_API_KEY", "")
LLM_BASE_URL = os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1")
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4.1-mini")

View File

@@ -4,13 +4,16 @@ from django.contrib import admin
from django.urls import include, path
# 总路由只承担模块装配职责,不在这里写业务逻辑。
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("apps.scenarios.urls")),
path("chat/", include("apps.chat.urls")),
path("documents/", include("apps.documents.urls")),
path("audit/", include("apps.audit.urls")),
path("platform/", include("apps.platform_ui.urls")),
]
if settings.DEBUG:
# 开发环境下直接通过 Django 提供上传文件访问能力,便于本地演示。
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -1,343 +0,0 @@
# V1 Django Baseline Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build the smallest runnable Django baseline that satisfies the current Chinese requirements and design documents.
**Architecture:** Use a Django monolith with four apps (`scenarios`, `documents`, `chat`, `audit`) plus an independent `agent_core` package. The first implementation returns deterministic mock Agent results so the UI, audit, documents and module boundaries can be verified before real RAG/LLM integration.
**Tech Stack:** Python 3.13, Django 5.x, PyYAML, pytest, pytest-django, SQLite, Docker Compose.
---
## File Structure
- Create `requirements.txt`: runtime and test dependencies.
- Create `manage.py`, `config/settings.py`, `config/urls.py`, `config/wsgi.py`, `config/asgi.py`: Django project shell.
- Create `apps/scenarios/`: YAML scenario loading, homepage, tests.
- Create `apps/documents/`: upload model, upload/list/index views, text extraction, tests.
- Create `apps/chat/`: message form, chat view, Agent Core call, audit write, tests.
- Create `apps/audit/`: audit model, service, list/detail views, tests.
- Create `agent_core/`: dataclasses, orchestrator, mock RAG ingest/retrieve, tool registry, structured output parser.
- Create `configs/*.yaml`: five required scenarios.
- Create `templates/`: minimal Django Templates for pages.
- Create `Dockerfile`, `docker-compose.yml`, `.env.example`: one-command startup.
## Task 1: Dependencies and Django Project Shell
**Files:**
- Create: `requirements.txt`
- Create: `manage.py`
- Create: `config/__init__.py`
- Create: `config/settings.py`
- Create: `config/urls.py`
- Create: `config/wsgi.py`
- Create: `config/asgi.py`
- Test: `pytest.ini`
- [ ] **Step 1: Write failing configuration test**
Create `tests/test_project_configuration.py`:
```python
from django.conf import settings
from django.urls import reverse
def test_core_settings_expose_documented_paths():
assert settings.SCENARIO_CONFIG_DIR.name == "configs"
assert settings.CHROMA_PATH.name == "chroma"
assert settings.MEDIA_ROOT.name == "uploads"
def test_home_url_is_registered(client):
response = client.get(reverse("scenarios:index"))
assert response.status_code == 200
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest tests/test_project_configuration.py -q`
Expected: FAIL because Django project and apps do not exist.
- [ ] **Step 3: Implement minimal project shell**
Add dependencies and project files with settings for installed apps, templates, SQLite, media paths, and URL includes.
- [ ] **Step 4: Run test to verify progress**
Run: `pytest tests/test_project_configuration.py -q`
Expected: either PASS or fail only because `apps.scenarios` is not implemented yet.
## Task 2: Scenarios Module and Five YAML Configs
**Files:**
- Create: `apps/scenarios/services.py`
- Create: `apps/scenarios/views.py`
- Create: `apps/scenarios/urls.py`
- Create: `apps/scenarios/apps.py`
- Create: `configs/knowledge_qa.yaml`
- Create: `configs/document_review.yaml`
- Create: `configs/ticket_assistant.yaml`
- Create: `configs/quality_analysis.yaml`
- Create: `configs/risk_audit.yaml`
- Create: `templates/scenarios/index.html`
- Test: `tests/test_scenarios.py`
- [ ] **Step 1: Write failing scenario tests**
```python
from apps.scenarios.services import get_scenario, list_scenarios
def test_list_scenarios_loads_five_configs():
scenarios = list_scenarios()
assert [scenario["id"] for scenario in scenarios] == [
"knowledge_qa",
"document_review",
"ticket_assistant",
"quality_analysis",
"risk_audit",
]
def test_get_scenario_returns_full_agent_config():
scenario = get_scenario("quality_analysis")
assert scenario["agent"]["role"]
assert scenario["rag"]["enabled"] is True
assert scenario["output"]["type"] == "quality_report"
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest tests/test_scenarios.py -q`
Expected: FAIL because services/configs are missing.
- [ ] **Step 3: Implement scenario loader and homepage**
Use `yaml.safe_load()`, validate required fields, and render scenario cards on `/`.
- [ ] **Step 4: Run tests**
Run: `pytest tests/test_scenarios.py tests/test_project_configuration.py -q`
Expected: PASS.
## Task 3: Agent Core Mock Orchestrator
**Files:**
- Create: `agent_core/results.py`
- Create: `agent_core/orchestrator.py`
- Create: `agent_core/structured_output.py`
- Create: `agent_core/tool_registry.py`
- Create: `agent_core/tools/builtin_tools.py`
- Create: `agent_core/rag/ingest.py`
- Create: `agent_core/rag/retriever.py`
- Test: `tests/test_agent_core.py`
- [ ] **Step 1: Write failing Agent Core tests**
```python
from agent_core.orchestrator import run_agent
def test_run_agent_returns_structured_mock_result():
scenario = {
"id": "knowledge_qa",
"name": "知识库问答助手",
"rag": {"enabled": True, "collection": "knowledge_qa", "top_k": 3},
"tools": ["generate_action_items"],
"output": {"type": "general_answer"},
}
result = run_agent(scenario, "如何处理异常?")
assert result.status == "success"
assert result.answer
assert result.structured_output["output_type"] == "general_answer"
assert result.tool_calls[0]["tool_name"] == "generate_action_items"
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest tests/test_agent_core.py -q`
Expected: FAIL because `agent_core` is missing.
- [ ] **Step 3: Implement deterministic mock AgentResult**
Return stable answer, references, tool calls, model name `mock-model`, and latency.
- [ ] **Step 4: Run tests**
Run: `pytest tests/test_agent_core.py -q`
Expected: PASS.
## Task 4: Audit Module
**Files:**
- Create: `apps/audit/models.py`
- Create: `apps/audit/services.py`
- Create: `apps/audit/views.py`
- Create: `apps/audit/urls.py`
- Create: `apps/audit/admin.py`
- Create: `templates/audit/log_list.html`
- Create: `templates/audit/log_detail.html`
- Test: `tests/test_audit.py`
- [ ] **Step 1: Write failing audit tests**
```python
from apps.audit.models import AgentAuditLog
from apps.audit.services import create_audit_log
from agent_core.results import AgentResult
def test_create_audit_log_records_success_result(db):
result = AgentResult(answer="回答", structured_output={"x": 1}, status="success")
log = create_audit_log("knowledge_qa", "知识库问答助手", "问题", result)
assert AgentAuditLog.objects.count() == 1
assert log.final_answer == "回答"
assert log.structured_output == {"x": 1}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest tests/test_audit.py -q`
Expected: FAIL because audit app is missing.
- [ ] **Step 3: Implement model, service, admin, views**
Use JSONField defaults and avoid storing sensitive environment values.
- [ ] **Step 4: Run migrations and tests**
Run: `python manage.py makemigrations audit && pytest tests/test_audit.py -q`
Expected: PASS.
## Task 5: Documents Module
**Files:**
- Create: `apps/documents/models.py`
- Create: `apps/documents/services.py`
- Create: `apps/documents/forms.py`
- Create: `apps/documents/views.py`
- Create: `apps/documents/urls.py`
- Create: `apps/documents/admin.py`
- Create: `templates/documents/document_list.html`
- Create: `templates/documents/upload.html`
- Test: `tests/test_documents.py`
- [ ] **Step 1: Write failing document tests**
```python
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from apps.documents.models import UploadedDocument
def test_upload_txt_document_creates_uploaded_record(client, db):
file = SimpleUploadedFile("rules.txt", "hello".encode("utf-8"), content_type="text/plain")
response = client.post(reverse("documents:upload"), {"scenario_id": "knowledge_qa", "file": file})
assert response.status_code == 302
document = UploadedDocument.objects.get()
assert document.status == "uploaded"
assert document.file_type == "txt"
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest tests/test_documents.py -q`
Expected: FAIL because documents app is missing.
- [ ] **Step 3: Implement upload/list/index flow**
Support `.txt` and `.md`; index action calls `agent_core.rag.ingest.ingest_document()` and updates status.
- [ ] **Step 4: Run migrations and tests**
Run: `python manage.py makemigrations documents && pytest tests/test_documents.py -q`
Expected: PASS.
## Task 6: Chat Module
**Files:**
- Create: `apps/chat/forms.py`
- Create: `apps/chat/views.py`
- Create: `apps/chat/urls.py`
- Create: `apps/chat/apps.py`
- Create: `templates/chat/index.html`
- Test: `tests/test_chat.py`
- [ ] **Step 1: Write failing chat tests**
```python
from django.urls import reverse
from apps.audit.models import AgentAuditLog
def test_chat_post_returns_agent_result_and_audit_log(client, db):
response = client.post(reverse("chat:index", args=["knowledge_qa"]), {"message": "如何处理异常?"})
assert response.status_code == 200
assert "mock-model" in response.content.decode("utf-8")
assert AgentAuditLog.objects.count() == 1
```
- [ ] **Step 2: Run test to verify it fails**
Run: `pytest tests/test_chat.py -q`
Expected: FAIL because chat app is missing.
- [ ] **Step 3: Implement chat form and view**
Validate message, call `get_scenario()`, `run_agent()`, then `create_audit_log()`.
- [ ] **Step 4: Run tests**
Run: `pytest tests/test_chat.py tests/test_audit.py tests/test_agent_core.py -q`
Expected: PASS.
## Task 7: Docker and Documentation Alignment
**Files:**
- Create: `.env.example`
- Create: `Dockerfile`
- Create: `docker-compose.yml`
- Modify: `README.md`
- Test: all tests and Django checks.
- [ ] **Step 1: Add deployment files**
Use a single web service, install `requirements.txt`, run migrations, and serve `0.0.0.0:8000`.
- [ ] **Step 2: Verify Django and tests**
Run:
```bash
python manage.py check
pytest -q
```
Expected: all checks pass.
- [ ] **Step 3: Verify docs path references**
Run:
```powershell
$patterns = @('docs/需求分析', 'docs/设计文档', 'V1总需求文档', '智能体总体设计')
Get-ChildItem -Recurse -File |
Where-Object {
$_.FullName -notlike '*\.git\*' -and
$_.FullName -notlike '*\.idea\*' -and
$_.FullName -notlike '*docs\superpowers\plans\2026-05-29-v1-django-baseline.md'
} |
Select-String -Pattern $patterns
```
Expected: no matches.
## Self-Review
- Spec coverage: The plan covers Chinese docs, five scenario configs, Django startup, homepage, chat, audit, documents, Agent Core, and Docker baseline.
- Placeholder scan: No implementation step relies on an undefined placeholder; mock LLM/RAG is intentionally scoped as the first runnable baseline.
- Type consistency: Tests use `AgentResult`, `run_agent`, `list_scenarios`, `get_scenario`, `UploadedDocument`, and `AgentAuditLog` consistently.

View File

@@ -0,0 +1,376 @@
# Registration Agent Prototype Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a new high-fidelity, demo-ready prototype UI for the registration review agent platform across homepage, knowledge base, document processing, agent workspace, MCP, Skills, and leadership dashboard screens.
**Architecture:** Keep the existing Django monolith and template routing boundaries, but replace the current visual system with a unified prototype shell and add a dedicated platform app for new governance pages. Use shared presentation data in service/view code so the pages tell one coherent business story without coupling design code to the existing agent execution internals.
**Tech Stack:** Django templates, Django views/URLs, Python service helpers, shared inline CSS in base template, existing forms/models where useful.
---
## File Structure
- Modify: `F:\PyCharm\DEMO-AGENT\templates\base.html`
- Modify: `F:\PyCharm\DEMO-AGENT\templates\scenarios\index.html`
- Modify: `F:\PyCharm\DEMO-AGENT\templates\documents\document_list.html`
- Modify: `F:\PyCharm\DEMO-AGENT\templates\documents\upload.html`
- Modify: `F:\PyCharm\DEMO-AGENT\templates\chat\index.html`
- Create: `F:\PyCharm\DEMO-AGENT\apps\platform_ui\__init__.py`
- Create: `F:\PyCharm\DEMO-AGENT\apps\platform_ui\apps.py`
- Create: `F:\PyCharm\DEMO-AGENT\apps\platform_ui\views.py`
- Create: `F:\PyCharm\DEMO-AGENT\apps\platform_ui\urls.py`
- Create: `F:\PyCharm\DEMO-AGENT\apps\platform_ui\services.py`
- Create: `F:\PyCharm\DEMO-AGENT\templates\platform_ui\knowledge_base.html`
- Create: `F:\PyCharm\DEMO-AGENT\templates\platform_ui\mcp_center.html`
- Create: `F:\PyCharm\DEMO-AGENT\templates\platform_ui\skill_studio.html`
- Create: `F:\PyCharm\DEMO-AGENT\templates\platform_ui\command_center.html`
- Modify: `F:\PyCharm\DEMO-AGENT\config\settings.py`
- Modify: `F:\PyCharm\DEMO-AGENT\config\urls.py`
- Test: `F:\PyCharm\DEMO-AGENT\tests\`
### Task 1: Register the new platform prototype app
**Files:**
- Create: `apps/platform_ui/__init__.py`
- Create: `apps/platform_ui/apps.py`
- Create: `apps/platform_ui/views.py`
- Create: `apps/platform_ui/urls.py`
- Modify: `config/settings.py`
- Modify: `config/urls.py`
- [ ] **Step 1: Add the Django app module skeleton**
```python
from django.apps import AppConfig
class PlatformUiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.platform_ui"
```
- [ ] **Step 2: Register the app in settings**
```python
INSTALLED_APPS = [
# ...
"apps.platform_ui",
]
```
- [ ] **Step 3: Add prototype page routes**
```python
urlpatterns = [
path("platform/", include("apps.platform_ui.urls")),
]
```
- [ ] **Step 4: Define view names for prototype pages**
```python
urlpatterns = [
path("knowledge-base/", views.knowledge_base, name="knowledge-base"),
path("mcp-center/", views.mcp_center, name="mcp-center"),
path("skills/", views.skill_studio, name="skills"),
path("command-center/", views.command_center, name="command-center"),
]
```
- [ ] **Step 5: Run framework validation**
Run: `python manage.py check`
Expected: PASS with no URL or app import errors
### Task 2: Create shared presentation data for the new prototype
**Files:**
- Create: `apps/platform_ui/services.py`
- Modify: `apps/platform_ui/views.py`
- [ ] **Step 1: Define one coherent demo dataset**
```python
def get_platform_demo_context():
return {
"batch": {...},
"knowledge_sources": [...],
"mcp_connectors": [...],
"skills": [...],
"workflow_steps": [...],
}
```
- [ ] **Step 2: Keep each page view thin**
```python
def knowledge_base(request):
context = get_platform_demo_context()
return render(request, "platform_ui/knowledge_base.html", context)
```
- [ ] **Step 3: Run framework validation**
Run: `python manage.py check`
Expected: PASS with importable views and service helpers
### Task 3: Replace the global visual system and navigation shell
**Files:**
- Modify: `templates/base.html`
- [ ] **Step 1: Rewrite the global shell**
```html
<body>
<div class="app-shell">
<aside class="sidebar">...</aside>
<main class="main-shell">
<header class="topbar">...</header>
{% block content %}{% endblock %}
</main>
</div>
</body>
```
- [ ] **Step 2: Define the new design tokens and shared components**
```css
:root {
--bg: #eef3f7;
--surface: #f8fbfd;
--panel: #ffffff;
--ink: #102033;
--accent: #1e5eff;
--signal: #d77a2b;
}
```
- [ ] **Step 3: Add global helpers for panels, metric cards, timelines, tables, pills, and section headers**
```css
.panel { ... }
.metric-card { ... }
.section-heading { ... }
.timeline-step { ... }
.data-table { ... }
```
- [ ] **Step 4: Render shared navigation links to all prototype surfaces**
```html
<a href="{% url 'scenarios:index' %}">任务总览</a>
<a href="{% url 'platform_ui:knowledge-base' %}">知识库配置</a>
<a href="{% url 'documents:list' %}">文件中心</a>
<a href="{% url 'platform_ui:command-center' %}">工作台大屏</a>
```
- [ ] **Step 5: Open a few pages manually**
Run: `python manage.py runserver`
Expected: the shell renders and every nav item resolves
### Task 4: Redesign the homepage as the business-closure entry point
**Files:**
- Modify: `templates/scenarios/index.html`
- Modify: `apps/scenarios/views.py` if extra presentation fields are needed
- [ ] **Step 1: Add a structured homepage context if needed**
```python
return render(
request,
"scenarios/index.html",
{
"scenarios": list_scenarios(),
"scenario_issues": list_scenario_issues(),
"hero_metrics": [...],
"workflow_overview": [...],
},
)
```
- [ ] **Step 2: Replace the page body with hero, metrics, workflow strip, risk board, and quick-entry modules**
```html
<section class="hero-band">...</section>
<section class="metrics-grid">...</section>
<section class="workflow-strip">...</section>
<section class="two-column-board">...</section>
```
- [ ] **Step 3: Verify the homepage**
Run: `python manage.py runserver`
Expected: `/` shows the new dashboard-like homepage
### Task 5: Redesign the document pages around parsing and slicing workflow
**Files:**
- Modify: `templates/documents/document_list.html`
- Modify: `templates/documents/upload.html`
- Optionally modify: `apps/documents/views.py`
- [ ] **Step 1: Add any lightweight display-only summary fields in the view if needed**
```python
return render(
request,
"documents/document_list.html",
{
"documents": documents,
"processing_summary": {...},
"exception_items": [...],
},
)
```
- [ ] **Step 2: Rebuild the list page into upload stats, pipeline board, anomaly box, and structured directory table**
```html
<section class="metrics-grid">...</section>
<section class="tri-column">...</section>
<section class="panel">
<table class="data-table">...</table>
</section>
```
- [ ] **Step 3: Rebuild the upload page into a guided import experience**
```html
<section class="dropzone-panel">...</section>
<section class="checklist-panel">...</section>
```
- [ ] **Step 4: Verify both pages**
Run: `python manage.py runserver`
Expected: `/documents/` and `/documents/upload/` match the new prototype style
### Task 6: Redesign the chat page as a controlled audit workspace
**Files:**
- Modify: `templates/chat/index.html`
- Optionally modify: `apps/chat/views.py`
- [ ] **Step 1: Add presentation-only helper blocks if the existing context is too sparse**
```python
return render(
request,
"chat/index.html",
{
...
"task_modes": [...],
"result_highlights": [...],
},
)
```
- [ ] **Step 2: Replace the template with a three-zone workspace**
```html
<section class="workspace-grid">
<div class="left-rail">...</div>
<div class="conversation-stage">...</div>
<div class="result-rail">...</div>
</section>
```
- [ ] **Step 3: Preserve the real form submission path while upgrading the visual output areas**
```html
<form method="post">...</form>
{% if result %} ... {% endif %}
```
- [ ] **Step 4: Verify one scenario page**
Run: `python manage.py runserver`
Expected: `/chat/<scenario_id>/` still submits and now renders as a workbench
### Task 7: Implement the governance and platform pages
**Files:**
- Create: `templates/platform_ui/knowledge_base.html`
- Create: `templates/platform_ui/mcp_center.html`
- Create: `templates/platform_ui/skill_studio.html`
- Create: `templates/platform_ui/command_center.html`
- Modify: `apps/platform_ui/views.py`
- Modify: `apps/platform_ui/services.py`
- [ ] **Step 1: Build the knowledge base page**
```html
<section class="three-column-board">...</section>
<section class="panel">...</section>
```
- [ ] **Step 2: Build the MCP center page**
```html
<section class="connector-grid">...</section>
<section class="import-flow">...</section>
```
- [ ] **Step 3: Build the Skill studio page**
```html
<section class="editor-layout">...</section>
<section class="version-board">...</section>
```
- [ ] **Step 4: Build the leadership command center page**
```html
<section class="command-stage">...</section>
<section class="risk-heatmap">...</section>
<section class="evidence-matrix">...</section>
```
- [ ] **Step 5: Verify page routing**
Run: `python manage.py check`
Expected: PASS and all `/platform/...` routes resolve
### Task 8: Regression, polish, and documentation sync
**Files:**
- Modify: `README.md` if navigation or page descriptions need updating
- Modify: `AGENTS.md` only if current implementation status needs sync
- [ ] **Step 1: Run core validation**
Run: `python manage.py check`
Expected: PASS
- [ ] **Step 2: Run targeted tests if template/view assumptions changed**
Run: `pytest`
Expected: PASS or clearly identified existing failures unrelated to the prototype
- [ ] **Step 3: Review the new screens for consistent terminology**
Check: page titles, batch name, product name, risk labels, and workflow terms all match the new design spec
- [ ] **Step 4: Update high-level documentation if needed**
```markdown
## 当前原型页面
- 任务总览
- 知识库配置
- 文件中心
- 审核工作台
- MCP 中心
- Skill Studio
- 工作台大屏
```
## Self-Review
- Spec coverage: the plan covers homepage, knowledge base, document center, chat workspace, MCP, Skill studio, and leadership dashboard.
- Placeholder scan: no TODO/TBD markers remain.
- Type consistency: route names, app names, and page concepts are consistent across tasks.

View File

@@ -0,0 +1,185 @@
# 注册审核智能体平台产品原型设计
## 目标
本次原型围绕“试剂盒临床注册文件准备与审核智能体平台”重新设计一套高保真、可点击、适合复试演示的 Web 产品界面。设计不沿用当前前端视觉而是以新版需求分析为准突出资料治理、法规知识底座、Agent 审核闭环、平台治理能力和可解释工作台。
## 设计范围
本次原型聚焦一条完整演示主线:
1. 首页进入批次级任务总览。
2. 查看法规知识库与结构化规则配置。
3. 上传并解析申报资料,查看切片、页数、章节点归类和异常状态。
4. 进入 AI Agent 审核工作台执行完整性检查、字段抽取和一致性核查。
5. 查看外部 MCP 能力接入情况。
6. 查看和编辑 Skill 编排能力。
7. 进入领导演示型工作台大屏查看 Agent 工作流程、解释依据和风险态势。
## 设计原则
### 1. 业务闭环优先
页面组织必须服务“资料准备到审核闭环”,而不是把系统包装成泛化 Agent 工具箱。
### 2. 高层能看懂,操作员也能讲清
首页与大屏承担讲故事职责知识库、文件中心、Agent 工作台承担业务可信度职责MCP 与 Skill 页面承担平台能力说明职责。
### 3. 可解释性强于炫技
任何页面都应尽量展示来源、阶段、规则命中、风险等级、人工复核状态,而不是堆叠模型术语。
### 4. 信息密度高但不压抑
整体风格走“专业监管科技工作台”,采用明亮底色、深色信息层、铜橙风险强调、清晰分栏和大面积结构化留白。
## 视觉方向
### 色彩
- 主背景采用浅米白与冷灰蓝混合渐变,强调专业与稳定。
- 主强调色采用深青蓝,承担导航、激活态、关键信息。
- 风险强调采用铜橙与深红,用于高风险、缺失、待处理。
- 成功状态采用低饱和绿色,用于已完成、已入库、规则已生效。
### 排版
- 页级标题使用强对比的中文大标题,突出“阶段感”。
- 模块级标题使用中等字号与清晰副标题。
- 指标数字使用紧凑等宽风格,强调可视化汇报感。
### 组件风格
- 顶部为平台导航与当前批次状态条。
- 页面内部以大面板、信息条、时间线、矩阵卡片、指标舱和结构树为主。
- 尽量避免传统后台里大量相同卡片堆叠,强化带状布局、流程图感和分析板感。
## 页面设计
### 1. 首页 / 任务总览
**目标:**
让评委在 10 秒内理解系统价值和当前批次进展。
**核心内容:**
- 批次概览 Hero当前申报批次、产品名、流程阶段、批次状态。
- 四个关键指标:资料齐套率、法规命中率、字段抽取完成度、高风险项数量。
- 演示主流程带:资料进入、规则配置、解析入库、审核执行、结果输出。
- 今日待办与高风险问题板。
- 快速入口:知识库、文件中心、审核工作台、大屏。
### 2. 知识库搭建与配置页
**目标:**
把“双层知识底座”讲透。
**核心内容:**
- 左侧法规规则树:按章、条、要求项、模板字段展示。
- 中部知识源列表:法规依据、业务资料、模板库、公告附件包。
- 右侧切片与生效配置:切片策略、召回阈值、适用流程、最近更新时间。
- 底部人工校订记录与知识更新入口。
### 3. 文件上传、切片与解析中心
**目标:**
展示 Documents 模块是“资料治理中心”。
**核心内容:**
- 顶部上传拖拽区与批次选择。
- 左侧批量导入队列:文件名、类型、页数、识别状态。
- 中部处理流水:保存原件、提取文本、识别表格、页数统计、章节点归类、切片入库。
- 右侧异常箱:疑似扫描件、归类待确认、页数低可信度、切片失败。
- 下方目录总览表:章节点、资料名称、状态、模板命中情况。
### 4. AI Agent 审核工作台
**目标:**
把自由问答升级为受控审核任务工作台。
**核心内容:**
- 顶部任务切换:目录汇总、完整性检查、字段抽取、一致性核查、综合风险报告。
- 左侧自然语言输入与资料范围选择。
- 中间 Agent 对话流与执行阶段条。
- 右侧结构化结果舱:结论摘要、字段表、缺失项、冲突项、风险建议。
- 下方证据区:法规条款引用、文档片段、来源页码、人工复核提示。
### 5. 外部 MCP 导入页
**目标:**
说明平台可扩展但不喧宾夺主。
**核心内容:**
- MCP 连接卡:法规源、飞书通知、模板服务、文档转换、企业数据源。
- 接入状态、鉴权方式、最近同步时间。
- 输入输出能力摘要。
- 一个简单的导入向导区块。
### 6. Skill 编辑与使用页
**目标:**
展示 Agent 可配置、可维护、可复用。
**核心内容:**
- Skill 列表完整性检查、字段抽取、一致性核查、Word 回填、飞书通知。
- 中部编辑区:角色说明、工具绑定、输入输出约束、启停配置。
- 右侧运行预览:最近测试结果、命中工具、失败原因。
- 底部版本历史和发布状态。
### 7. Agent 工作台大屏
**目标:**
作为演示高潮页,向领导/评委解释“这一轮审核发生了什么”。
**核心内容:**
- 顶部大标题与批次状态。
- 左侧流程总览:资料进入、规则装载、字段池建立、一致性比对、风险汇总。
- 中央主舞台Agent 工作流时间轴,展示每一步的输入、动作、输出和解释。
- 右侧风险热力与关键告警。
- 底部证据矩阵:法规依据、命中文档、责任人、待补动作。
## 跳转与演示动线
推荐演示顺序:
1. 首页说明平台定位。
2. 进入知识库页说明法规与规则如何维护。
3. 进入文件中心说明资料如何入库、切片、归类。
4. 进入审核工作台发起一次完整性或一致性检查。
5. 进入 Skill 页解释为何 Agent 行为可控。
6. 进入 MCP 页说明可接飞书与外部能力。
7. 最后切到大屏汇总整轮审核过程。
## 数据策略
本轮原型以高保真演示为优先,允许使用页面级演示数据,但必须满足:
- 术语与字段名称来自新版需求分析。
- 状态设计贴近真实业务流程。
- 页面间批次名称、产品名称、风险项和规则口径保持一致。
## 实现策略
基于当前 Django 模板工程直接重构前端:
1. 保留现有 Django 应用与路由边界。
2. 新增平台页应用承接知识库、MCP、Skill、大屏页面。
3. 重做 `base.html` 及所有主要模板的布局和样式。
4. 使用共享演示数据构造页面内容,保证视觉统一和讲解一致。
## 测试与验证
原型完成后至少验证:
1. Django 路由可访问。
2. 首页、知识库、文件中心、审核工作台、MCP、Skill、大屏页面均可打开。
3. 页面导航互通。
4. `python manage.py check` 通过。
5. 如无模板语法问题,关键页面可在本地服务下正常渲染。

View File

@@ -0,0 +1,62 @@
**试剂盒临床注册文件准备与审核智能体搭建**
**一、背景**
卡尤迪生物研发团队在推进NMPA国家药品监督管理局注册申报时需准备大量合规性文件包括产品技术要求、说明书、检测报告、临床评估资料等。
公司计划组建AI Agent新团队目标为"试剂盒NMPA注册文件准备与审核智能体",实现文件目录自动汇总、法规完整性检查、关键信息自动提取与填写、缺失文件预警、文档一致性核查,提升注册效率并降低合规风险。
**二、任务目标**
请你作为 AI Agent 工程师候选人,设计并实现(或详细描述)一个智能体,能够:
1. 自动汇总注册申报文件夹中的所有文件及页数
2. 对照 NMPA 法规要求核查文件完整性并预警缺失
3. 提取产品关键信息并自动填写至申报文件
4. 核查文档结构与信息一致性
5. 输出合规风险预警与处理建议
**三、具体要求如下**
**1. 自动汇总文件夹文件目录与页数。**
文件目录参考附件。
**2. 按照NMPA现行法规要求核查文件完整性。**
- 对照NMPA法规检查所需文件是否齐全如注册申报资料基本要求、产品技术要求、注册检验报告等
- 自动识别缺失文件并通知责任人
- 参考法规来源网站:
<https://www.cmde.org.cn/xwdt/zxyw/20210930163300622.html、>
<https://www.nmpa.gov.cn/>
**3. 从产品文件中提取关键信息并自动填写至目标文件。**
- 自动提取:产品名称、检测靶标、适用范围、储存条件、性能指标等核心信息
- 将提取信息自动填入注册申报表格或对照清单
**4. 核查文档结构、信息一致性与章节规范性。**
- 检测章节是否完整(如分析灵敏度、特异性、重复性等必检项目)
- 不同文档间同一信息是否一致(如产品名称、规格型号等)
- 格式是否符合NMPA要求的规范章节结构
**5. 提供合规风险预警与处理建议。**
例如:"文件X缺少临床评估报告请补充"或"产品Y说明书与检测报告中的适用范围描述不一致请核对"
**附加要求【在复试时陈述,需结合 Demo 演示】**
**1. 架构搭建思路(基于 Demo 版)**
- 展示Demo运行结果文件目录汇总表、法规完整性报告、信息提取对照表、异常预警列表
- 结合你实现的Demo说明智能体的整体工作流文件扫描 → 目录汇总 → 法规匹配 → 信息提取 → 一致性核查 → 风险预警)
- 展示Demo中实际调用的关键工具/库(如 pdfplumber / PyMuPDF、正则表达式、规则引擎、向量检索等并分析选用理由
- 简述Demo中如何体现文件完整性检测、信息一致性核查、法规条款匹配等难点规则的处理
**2. 基于 Demo 版的迭代规划**
- 说明当前Demo实现了哪些核心功能哪些是模拟数据/简化逻辑
- 下一版本最想增加的一个功能以及需要投入的技术资源(如 NMPA 官网 API 对接、文件版本管理、多语言支持等),并说明为什么优先做它

Binary file not shown.

View File

@@ -1,71 +0,0 @@
# 模块详细设计文档索引
## 1. 设计文档说明
本目录存放 Universal Agent Demo Framework V1 的设计文档。需求文档回答“要做什么”,设计文档回答“怎么实现、边界在哪里、如何验证”。
文档命名统一使用中文编号,便于复试讲解和按顺序阅读。
## 2. 模块设计文档列表
| 顺序 | 文档 | 说明 |
|---|---|---|
| 0 | `0.设计文档索引.md` | 当前索引 |
| 1 | `1.智能体总体设计.md` | 智能核心总体链路、配置、输出和 Adapter |
| 2 | `2.功能流程设计.md` | 复试准备、演示、上传、入库、对话和审计流程 |
| 3 | `3.数据库设计.md` | Django 数据模型、字段、索引和初始化策略 |
| 4 | `4.页面与路由设计.md` | 页面结构、URL、跳转和异常状态 |
| 5 | `5.部署设计.md` | 本地、Docker、环境变量和持久化 |
模块详细设计位于 `模块设计/`
| 模块 | 文档 |
|---|---|
| 配置 | `模块设计/1.配置模块详细设计.md` |
| 场景 | `模块设计/2.场景模块详细设计.md` |
| 文档 | `模块设计/3.文档模块详细设计.md` |
| 对话 | `模块设计/4.对话模块详细设计.md` |
| 审计 | `模块设计/5.审计模块详细设计.md` |
| 智能核心 | `模块设计/6.智能核心模块详细设计.md` |
## 3. 模块依赖关系
```text
config
|-- apps.scenarios
|-- apps.documents
|-- apps.chat
|-- apps.audit
apps.scenarios
|-- reads configs/*.yaml
apps.documents
|-- depends on apps.scenarios
|-- calls agent_core.rag.ingest
apps.chat
|-- depends on apps.scenarios
|-- calls agent_core.orchestrator
|-- calls apps.audit.services
apps.audit
|-- stores AgentResult snapshots
agent_core
|-- consumes scenario config
|-- uses RAG, tools, LLM provider and structured output parser
```
## 4. 推荐阅读顺序
1. `docs/需求分析/1.V1总需求文档.md`
2. `docs/需求分析/2.模块需求索引.md`
3. `docs/设计文档/1.智能体总体设计.md`
4. `docs/设计文档/2.功能流程设计.md`
5. `docs/设计文档/3.数据库设计.md`
6. `docs/设计文档/4.页面与路由设计.md`
7. `docs/设计文档/5.部署设计.md`
8. `docs/设计文档/模块设计/*.md`
后续编码时,每个模块应先对照对应需求文档和详细设计,再实现模型、服务、视图和测试。

View File

@@ -1,211 +0,0 @@
# 智能体总体设计文档
## 1. 设计目标
Agent 设计的核心目标是支持未知复试题的快速适配。
系统不针对单一业务写死,而是通过场景配置、知识库、工具和输出模板组合出不同业务 Agent。
```text
业务 Agent = 场景配置 + 知识库 + 工具集 + 输出模板 + 审计日志 + 模型适配器
```
## 2. Agent 类型
V1 预置 5 类 Agent 场景:
| Agent ID | 名称 | 适用场景 |
|---|---|---|
| `knowledge_qa` | 知识库问答助手 | SOP、制度、客服知识库 |
| `document_review` | 文档审核助手 | 合同、制度、SOP、材料审核 |
| `ticket_assistant` | 工单处理助手 | 客服、售后、运维工单 |
| `quality_analysis` | 质量异常分析助手 | 生产、质检、缺陷分析 |
| `risk_audit` | 风险审核助手 | 财务、采购、报销、合同风险 |
## 3. Agent 执行链路
```text
用户输入
加载场景配置
判断是否启用 RAG
检索知识库片段
加载可用工具
构造 Prompt
调用大模型
解析工具调用和结构化输出
生成 AgentResult
写入审计日志
页面展示
```
## 4. 场景配置结构
场景配置使用 YAMLV1 以配置文件作为场景唯一事实来源,后台管理不作为场景配置入口。
```yaml
id: quality_analysis
name: 质量异常分析助手
description: 用于分析生产质量异常、检索 SOP、生成处理建议
agent:
role: 质量管理专家
goal: 根据用户问题、知识库和工具结果,输出可执行的质量分析报告
system_prompt: ""
instructions:
- 回答必须基于知识库或工具结果
- 不确定时必须说明缺失信息
- 涉及质量风险时给出风险等级
rag:
enabled: true
collection: quality_docs
top_k: 5
tools:
- query_demo_records
- calculate_rate
output:
type: quality_report
audit:
enabled: true
log_retrieval: true
log_tool_calls: true
```
## 5. Prompt 组成
Prompt 建议由以下部分组成:
```text
系统角色
任务目标
行为约束
输出格式要求
知识库检索内容
工具调用结果
用户问题
```
V1 不追求复杂 Prompt 框架,优先保证可读、可改、可解释。
## 6. RAG 策略
RAG 在 V1 中负责给 Agent 提供题目材料和业务知识。
入库流程:
```text
上传文件
抽取文本
文本切分
生成 embedding
写入 Chroma
```
检索流程:
```text
用户问题
按 scenario_id 和可选 document_ids 过滤
向量检索 top_k
返回片段内容、来源和分数
```
## 7. 工具调用策略
工具用于补足大模型不能直接可靠完成的业务动作。
V1 内置工具:
| 工具 | 用途 |
|---|---|
| `calculate_rate` | 计算比例、缺陷率、通过率 |
| `query_demo_records` | 查询模拟业务数据 |
| `check_required_fields` | 检查必填项 |
| `generate_action_items` | 生成行动项 |
工具返回格式:
```json
{
"tool_name": "calculate_rate",
"success": true,
"arguments": {},
"result": {},
"error": ""
}
```
## 8. 结构化输出
V1 支持以下输出类型:
- `general_answer`
- `document_review_report`
- `ticket_response`
- `quality_report`
- `risk_audit_report`
结构化输出优先使用 JSON。
解析失败时:
- 保留模型原始输出。
- 返回解析错误。
- 页面展示原始回答。
- 审计日志记录失败原因。
## 9. AgentResult
Agent Core 统一返回:
```json
{
"answer": "",
"structured_output": {},
"references": [],
"tool_calls": [],
"raw_output": "",
"model_name": "",
"latency_ms": 0,
"status": "success",
"error": ""
}
```
## 10. Adapter 策略
V1 默认使用自研轻量 Orchestrator通过 OpenAI 兼容接口接入 LLM 与 Embedding可自主选择 OpenAI、硅基流动等兼容服务。
后续可以扩展:
- OpenAI Agents SDK Adapter。
- Dify API Adapter。
- LangGraph Adapter。
所有 Adapter 应保持统一接口:
```text
run_agent(scenario_config, user_input, options=None) -> AgentResult
```
这样可以保证 Django 业务层不受底层 Agent 编排实现影响。

View File

@@ -1,169 +0,0 @@
# V1 功能设计文档
## 1. 功能设计目标
V1 的功能设计目标是让复试展示者在本地快速完成一个可讲解、可演示、可改题的 Agent Demo。系统不追求复杂平台能力而是优先保证以下闭环稳定
- 场景配置可选择。
- 文档可上传并入库。
- 用户可在场景下发起对话。
- Agent 可返回结构化结果、引用来源和工具调用记录。
- 每次成功或失败的对话都有审计记录。
- 本地和 Docker 均可启动。
## 2. 用户角色
V1 仅设计一个用户角色Demo 操作者。
该角色负责启动系统、选择场景、上传材料、触发入库、发起对话、查看输出和审计日志。系统不在 V1 中区分管理员、审核员、普通用户等权限角色。
## 3. 核心业务流程
```text
启动系统
查看 5 个预置场景
选择场景
上传题目材料
触发知识库入库
发起 Agent 对话
查看结构化输出、引用和工具调用
查看审计日志
```
任一环节失败时,页面应给出明确提示,并尽量保留用户已完成的上下文。
## 4. 场景选择流程
1. 首页调用 `apps.scenarios.services.list_scenarios()`
2. 服务从 `configs/` 读取 YAML 场景配置。
3. 校验必填字段、工具名称和输出类型。
4. 页面展示场景名称、描述、适用题型、启用状态。
5. 用户点击进入 `/chat/<scenario_id>/`
异常处理:
- 配置目录不存在:展示空状态和配置目录提示。
- 单个配置非法:不阻断其他配置,页面展示该配置错误。
- 场景不存在:跳转或渲染错误页,提示检查场景 ID。
## 5. 文件上传流程
1. 用户进入 `/documents/upload/`
2. 页面加载可用场景下拉框。
3. 用户选择场景并上传 `.txt``.md``.pdf``.docx` 文件。
4. Documents 模块校验文件类型和大小。
5. 保存文件到 `UPLOAD_ROOT/<scenario_id>/`
6. 写入 `UploadedDocument` 记录,状态为 `uploaded`
7. 返回文件列表页并展示上传结果。
V1 文件上传默认手动入库,避免上传大文件时页面阻塞过久。
## 6. 文档入库流程
1. 用户在文件列表点击“入库”。
2. Documents 模块读取文件并抽取文本。
3. 调用 `agent_core.rag.ingest.ingest_document()`
4. Agent Core 按固定长度切分文本。
5. 写入本地 Chroma collection。
6. 入库成功:更新状态为 `indexed`
7. 入库失败:更新状态为 `failed`,保存错误信息。
文本为空、文件丢失、向量库不可写都应进入失败状态,不能让页面报 500。
## 7. Agent 对话流程
```text
用户提交问题
Chat 表单校验
Scenarios 加载场景配置
Agent Core 执行 run_agent()
RAG 按场景和可选文档范围检索知识片段
工具系统执行可用工具
LLM Provider 生成结果
结构化输出解析
Audit 写入日志
Chat 页面展示结果
```
Chat 模块只负责请求处理和页面展示,不直接写 RAG、工具和模型调用细节。
## 8. RAG 检索流程
1. Orchestrator 读取场景配置中的 `rag.enabled``collection``top_k`
2. 若启用 RAG则调用 `agent_core.rag.retriever.retrieve()`
3. 检索必须按 `scenario_id` 过滤,避免跨场景污染。
4. 如果用户在对话页选择了文档,则同时按 `document_ids` 过滤;未选择时使用当前场景全部已入库文档。
5. 返回片段内容、来源文件、chunk ID、分数。
6. 片段进入 Prompt同时随 AgentResult 返回给页面和审计日志。
检索失败时AgentResult 应记录错误或警告;若业务允许,可继续使用非 RAG 上下文回答。
## 9. 工具调用流程
1. 场景配置声明可用工具名称。
2. Orchestrator 从 Tool Registry 查询工具。
3. 对不可用工具记录失败,不中断整个流程。
4. 内置工具按统一参数和返回结构执行。
5. 工具结果进入 Prompt 或结构化输出上下文。
6. 所有工具调用写入 AgentResult 和审计日志。
V1 先采用“配置声明 + Orchestrator 决策”的轻量策略,不实现复杂多轮工具调用协议。
## 10. 审计日志流程
1. Chat 模块在 Agent Core 返回后调用 `apps.audit.services.create_audit_log()`
2. 成功结果记录输入、输出、引用、工具调用、模型名和耗时。
3. 失败结果也记录场景、输入、错误信息和已产生的中间结果。
4. 日志中不得保存 `LLM_API_KEY`、环境变量完整内容或上传文件绝对敏感路径。
5. 审计列表展示摘要,详情页展示完整 JSON 片段。
## 11. 复试改题流程
1. 判断题目最接近的模板。
2. 复制 `configs/` 中相近 YAML。
3. 修改场景名称、角色、目标、指令和输出类型。
4. 上传题目文档并入库。
5. 如题目需要计算或查询,新增一个内置工具并在场景中声明。
6. 用 2 到 3 个问题验证输出和审计链路。
7. 演示时重点展示配置、知识库、工具调用、结构化结果和审计日志。
## 12. 异常处理流程
| 异常 | 处理方式 |
|---|---|
| 场景配置缺失 | 页面展示错误,保留返回首页入口 |
| 场景字段非法 | 标记非法配置,不影响其他场景 |
| 上传文件类型不支持 | 表单错误提示 |
| 文件读取失败 | 文档状态改为 `failed` |
| RAG 入库失败 | 记录错误信息并允许重试 |
| LLM 配置缺失 | AgentResult 返回失败,审计日志记录失败 |
| 工具调用失败 | 记录工具失败,流程尽量继续 |
| 结构化解析失败 | 展示原始输出并记录解析错误 |
## 13. V1 功能验收标准
- 首页可以展示 5 个预置场景。
- 场景配置来自 YAML 文件。
- 可以上传 `.txt``.md``.pdf``.docx` 文件。
- 文件可触发入库,并显示 `uploaded``indexed``failed` 状态。
- 可以进入任一场景对话页并提交问题。
- AgentResult 至少包含回答、结构化输出、引用、工具调用、耗时和状态。
- 成功和失败对话都能生成审计日志。
- 审计详情可以解释一次 Agent 输出的输入、依据和过程。
- 本地启动和 Docker 启动路径清晰可执行。

View File

@@ -1,144 +0,0 @@
# V1 数据库设计文档
## 1. 数据库设计目标
V1 数据库设计优先服务本地演示、讲解清晰和快速改题。数据模型只覆盖文件、对话、审计和简单示例业务数据,不引入复杂权限、多租户或工作流状态机。
## 2. 数据库选型
默认使用 SQLite数据库文件位于 `data/db.sqlite3`。SQLite 适合复试现场单机运行,便于 Docker 挂载和备份。
后续如需多人协作或更正式部署,可通过 Django settings 切换到 PostgreSQL但 V1 不强制实现。
## 3. 表结构总览
| 表 | Django Model | 模块 | 说明 |
|---|---|---|---|
| uploaded_document | `UploadedDocument` | Documents | 上传文件元数据和入库状态 |
| agent_audit_log | `AgentAuditLog` | Audit | Agent 执行审计快照 |
| demo_business_record | `DemoBusinessRecord` | Agent Core / Tools | 内置工具可查询的模拟业务数据 |
| chat_session | `ChatSession` | Chat | 可选,对话会话 |
| chat_message | `ChatMessage` | Chat | 可选,对话消息 |
## 4. UploadedDocument 表设计
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BigAutoField | PK | 主键 |
| scenario_id | CharField(100) | indexed | 关联场景 ID |
| original_name | CharField(255) | required | 原始文件名 |
| file | FileField | required | 文件相对路径 |
| file_type | CharField(20) | required | `txt``md``pdf``docx` 等 |
| size | PositiveIntegerField | default 0 | 字节数 |
| status | CharField(20) | indexed | `uploaded``indexed``failed` |
| error_message | TextField | blank | 入库失败原因 |
| created_at | DateTimeField | auto_now_add | 上传时间 |
| updated_at | DateTimeField | auto_now | 更新时间 |
状态流转:
```text
uploaded -> indexed
uploaded -> failed
failed -> indexed
failed -> failed
```
重新入库时应按文档维度覆盖或清理旧 chunk避免同一文件重复出现在向量检索结果中。文档选择范围由 Chat 表单本次提交的 `document_ids` 传入 Agent CoreV1 不需要为该选择单独建表。
## 5. AgentAuditLog 表设计
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BigAutoField | PK | 主键 |
| scenario_id | CharField(100) | indexed | 场景 ID |
| scenario_name | CharField(200) | blank | 场景名称快照 |
| user_input | TextField | required | 用户输入 |
| retrieved_chunks | JSONField | default list | RAG 引用片段 |
| tool_calls | JSONField | default list | 工具调用记录 |
| structured_output | JSONField | default dict | 结构化输出 |
| final_answer | TextField | blank | 最终回答 |
| raw_output | TextField | blank | 模型原始输出 |
| model_name | CharField(100) | blank | 模型名称 |
| latency_ms | PositiveIntegerField | default 0 | 执行耗时 |
| status | CharField(20) | indexed | `success``failed` |
| error_message | TextField | blank | 错误信息 |
| created_at | DateTimeField | auto_now_add, indexed | 创建时间 |
审计日志保存的是执行快照,不依赖场景配置后续是否被修改。
## 6. DemoBusinessRecord 表设计
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BigAutoField | PK | 主键 |
| scenario_id | CharField(100) | indexed | 适用场景 |
| record_type | CharField(100) | indexed | 记录类型,如 defect、ticket、invoice |
| title | CharField(255) | required | 标题 |
| payload | JSONField | default dict | 模拟业务数据 |
| created_at | DateTimeField | auto_now_add | 创建时间 |
该表为 V1 必需表,用于 `query_demo_records` 工具避免工具只能返回硬编码数据。Django Admin 可以管理该表的数据,场景 YAML 仍不在 Admin 中编辑。
## 7. ChatSession 表设计
V1 可先不实现会话持久化。如果实现,字段建议如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BigAutoField | 主键 |
| scenario_id | CharField(100) | 场景 ID |
| title | CharField(255) | 会话标题 |
| created_at | DateTimeField | 创建时间 |
| updated_at | DateTimeField | 更新时间 |
## 8. ChatMessage 表设计
V1 可通过审计日志满足演示追踪,不强制实现消息表。如果实现,字段建议如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BigAutoField | 主键 |
| session | ForeignKey(ChatSession) | 所属会话 |
| role | CharField(20) | `user``assistant``system` |
| content | TextField | 消息内容 |
| audit_log | ForeignKey(AgentAuditLog, null=True) | 关联审计 |
| created_at | DateTimeField | 创建时间 |
## 9. 表关系设计
```text
Scenario YAML
|-- scenario_id
|-- UploadedDocument.scenario_id
|-- AgentAuditLog.scenario_id
|-- DemoBusinessRecord.scenario_id
|-- ChatSession.scenario_id
ChatSession 1 -- N ChatMessage
ChatMessage 0/1 -- 1 AgentAuditLog
```
场景配置 V1 存在 YAML 中,不建 `Scenario` 数据表。这样更方便复试现场复制和修改配置文件。
## 10. 索引设计
- `UploadedDocument(scenario_id, status)`:用于按场景查看文件和入库状态。
- `AgentAuditLog(scenario_id, created_at)`:用于按场景查看最近日志。
- `AgentAuditLog(status, created_at)`:用于排查失败日志。
- `DemoBusinessRecord(scenario_id, record_type)`:用于工具查询模拟数据。
## 11. 数据初始化策略
- 场景初始化:读取 `configs/*.yaml`,不写数据库。
- 示例业务数据:可提供 Django management command 初始化 `DemoBusinessRecord`
- 超级用户本地演示可手动创建Docker 可通过说明引导创建。
- 上传文件和 Chroma 数据:存放在 `data/` 下,通过 Docker volume 持久化。
## 12. 后续扩展方向
- 增加 `Scenario` 表,实现后台编辑场景。
- 增加 `ToolCallLog` 独立表,用于复杂工具审计。
- 使用 PostgreSQL JSONB 优化 JSON 查询。
- 增加用户和权限模型。
- 增加文档 chunk 元数据表,便于从数据库追踪向量库内容。

View File

@@ -1,179 +0,0 @@
# V1 页面与路由设计文档
## 1. 页面设计目标
V1 页面使用 Django Templates优先保证清晰、稳定、可讲解。页面应围绕复试演示的主路径组织选择场景、上传文档、入库、对话、查看审计。
## 2. 页面列表
| 页面 | 路径 | 模块 | 说明 |
|---|---|---|---|
| 首页/场景列表 | `/` | Scenarios | 展示 5 个预置场景 |
| Agent 对话页 | `/chat/<scenario_id>/` | Chat | 提交问题并展示结果 |
| 文件列表页 | `/documents/` | Documents | 查看上传文件和入库状态 |
| 文件上传页 | `/documents/upload/` | Documents | 上传题目材料 |
| 文档入库动作 | `/documents/<id>/index/` | Documents | POST 触发入库 |
| 审计日志列表 | `/audit/` | Audit | 查看对话记录 |
| 审计日志详情 | `/audit/<log_id>/` | Audit | 查看单次执行详情 |
| Django Admin | `/admin/` | Config | 后台管理 |
## 3. 路由总览
```text
config.urls
|-- "" -> apps.scenarios.urls
|-- "chat/" -> apps.chat.urls
|-- "documents/" -> apps.documents.urls
|-- "audit/" -> apps.audit.urls
|-- "admin/" -> django.contrib.admin
```
各模块只暴露自己的 URL避免把业务路由集中写在 `config.urls` 中。
## 4. 首页与场景列表页
路径:`/`
展示内容:
- 系统名称和简短定位。
- 5 个场景卡片或列表。
- 场景名称、描述、适用题型、启用状态。
- “进入对话”按钮。
- 文件管理和审计日志入口。
错误状态:
- 没有可用场景:展示配置目录提示。
- 配置读取失败:展示失败原因和文件名。
## 5. Agent 对话页
路径:`/chat/<scenario_id>/`
页面区域:
- 场景摘要名称、角色、目标、RAG 状态、工具列表。
- 文档范围:当前场景下状态为 `indexed` 的文档多选框;未选择时默认使用全部已入库文档。
- 输入区:一个 textarea 和提交按钮。
- 结果区:自然语言回答和结构化输出。
- 引用区source、chunk_id、score、content。
- 工具区tool_name、success、arguments、result、error。
- 审计入口:当前对话生成日志后展示详情链接。
POST 成功后仍渲染同一页面,保留用户问题和 AgentResult。
## 6. 文件上传页
路径:`/documents/upload/`
页面元素:
- 场景选择下拉框。
- 文件选择控件。
- 支持类型提示。
- 上传按钮。
- 错误或成功提示。
表单接受 `.txt``.md``.pdf``.docx`。PDF 仅要求纯文本抽取DOCX 仅要求段落和普通文本抽取。
## 7. 文件列表页
路径:`/documents/`
展示字段:
- 原始文件名。
- 所属场景。
- 文件类型。
- 文件大小。
- 入库状态。
- 上传时间。
- 入库按钮。
- 失败原因。
状态为 `indexed` 时可以显示“重新入库”,重新入库需要覆盖或清理该文档旧 chunk。
## 8. 审计日志列表页
路径:`/audit/`
展示字段:
- 日志 ID。
- 场景名称。
- 用户输入摘要。
- 状态。
- 模型名称。
- 执行耗时。
- 创建时间。
- 详情入口。
默认按 `created_at desc` 排序。
## 9. 审计日志详情页
路径:`/audit/<log_id>/`
展示内容:
- 场景信息。
- 用户输入。
- 最终回答。
- 结构化输出 JSON。
- RAG 引用列表。
- 工具调用列表。
- 模型名称和耗时。
- 错误信息。
JSON 内容可以先用 `<pre>` 展示,优先保证可读。
## 10. Django Admin 页面
Admin 注册:
- `UploadedDocument`
- `AgentAuditLog`
- `DemoBusinessRecord`
V1 不要求在 Admin 中编辑 YAML 场景,场景仍以配置文件为准。
## 11. 页面跳转关系
```text
首页
|-- 进入对话页
|-- 文件列表页
|-- 审计日志列表页
文件列表页
|-- 文件上传页
|-- 触发入库后回到文件列表页
对话页
|-- 提交后留在当前对话页
|-- 查看当前审计详情
审计列表页
|-- 审计详情页
```
## 12. 页面异常状态
| 页面 | 异常 | 展示方式 |
|---|---|---|
| 首页 | 场景配置为空 | 空状态和配置目录说明 |
| 对话页 | 场景不存在 | 明确提示并提供返回首页 |
| 对话页 | Agent 执行失败 | 展示错误、保留输入、写入失败审计 |
| 上传页 | 文件类型错误 | 表单错误 |
| 文件列表 | 入库失败 | 状态为 failed 并显示原因 |
| 审计详情 | 日志不存在 | 404 或友好错误页 |
## 13. V1 页面验收标准
- 主要页面可通过浏览器访问。
- 页面之间跳转路径完整。
- POST 表单使用 CSRF 保护。
- 所有用户可见错误都有中文提示。
- Agent 对话结果可以同时看到回答、引用、工具和审计入口。
- 页面不依赖 React/Vue。

View File

@@ -1,111 +0,0 @@
# V1 部署设计文档
## 1. 部署设计目标
V1 部署目标是降低复试现场环境风险。系统应支持本地 Python 方式启动,也支持 Docker Compose 一键启动。默认不依赖外部数据库、Redis 或任务队列。
## 2. 本地运行方式
建议命令:
```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver
```
本地运行使用 SQLite、`data/uploads``data/chroma`
当前本地方式会在启动时自动读取根目录 `.env`,因此 `runserver``pytest` 和日常脚本可以共享同一套配置。
## 3. Docker 运行方式
建议命令:
```bash
docker compose up --build
```
V1 Docker Compose 只需要一个 Django Web 服务。Chroma 使用本地持久化目录,不额外启动独立服务。
## 4. 环境变量设计
| 变量 | 默认值 | 说明 |
|---|---|---|
| `DJANGO_SECRET_KEY` | `dev-secret-key` | 开发密钥 |
| `DJANGO_DEBUG` | `true` | 是否开启调试 |
| `DJANGO_ALLOWED_HOSTS` | `*` | 允许主机 |
| `LLM_API_KEY` | 空 | 大模型 API Key |
| `LLM_BASE_URL` | `https://api.openai.com/v1` | OpenAI 兼容接口地址,可接入 OpenAI、硅基流动等兼容服务 |
| `LLM_MODEL` | `gpt-4.1-mini` | 默认模型 |
| `EMBEDDING_API_KEY` | 空 | Embedding API Key为空时可复用 `LLM_API_KEY` |
| `EMBEDDING_BASE_URL` | 空 | Embedding OpenAI 兼容接口地址;为空时可复用 `LLM_BASE_URL` |
| `EMBEDDING_MODEL` | `text-embedding-3-small` | 默认 Embedding 模型 |
| `SCENARIO_CONFIG_DIR` | `configs` | 场景配置目录 |
| `UPLOAD_ROOT` | `data/uploads` | 上传目录 |
| `CHROMA_PATH` | `data/chroma` | 向量库目录 |
`.env.example` 应提供这些变量的样例,不写真实密钥。
当前实现说明:
- 本地 Python 方式启动时,会先加载根目录 `.env`,再读取进程环境中的覆盖值。
- Docker Compose 方式可通过 `env_file` 向容器注入环境变量;当前仓库默认读取 `.env`
- 因此本地运行和容器运行可以默认共用一份 `.env`,但演示前仍应确认密钥和模型参数是否正确。
## 5. 目录挂载设计
Docker 需要持久化以下目录:
```text
./data/db.sqlite3
./data/uploads
./data/chroma
./configs
```
`configs` 挂载后可以在不重建镜像的情况下修改场景配置。
## 6. SQLite 数据持久化
SQLite 文件放在 `data/db.sqlite3`。Docker 中应将 `data/` 作为 volume 挂载,避免容器重建后数据丢失。
## 7. Chroma 数据持久化
Chroma 数据放在 `data/chroma`。RAG 入库后,重启容器不应丢失向量数据。
## 8. 上传文件持久化
上传文件放在 `data/uploads/<scenario_id>/`。数据库只保存相对路径或 Django FileField 路径。
## 9. 启动命令设计
Docker 容器启动时建议执行:
```bash
python manage.py migrate
python manage.py runserver 0.0.0.0:8000
```
V1 可以先用开发服务器满足演示。后续正式部署可切换到 Gunicorn。
## 10. 常见部署问题
| 问题 | 处理 |
|---|---|
| 端口 8000 被占用 | 修改 compose 端口映射 |
| API Key 缺失 | 页面提示 LLM 或 Embedding 配置缺失 |
| Chroma 目录无权限 | 检查 `data/chroma` 挂载权限 |
| 上传目录不存在 | settings 或启动脚本创建目录 |
| 场景配置读取失败 | 检查 `configs/*.yaml` 格式 |
| Docker 构建慢 | 提前构建镜像或使用本地 Python 方式演示 |
## 11. 后续部署扩展
- 使用 Gunicorn + WhiteNoise。
- 增加 PostgreSQL 服务。
- 增加 Redis 和 Celery 做异步入库。
- 增加 Nginx 反向代理。
- 增加健康检查接口。

View File

@@ -1,112 +0,0 @@
# 配置模块详细设计
## 1. 模块目标
Config 模块负责 Django 项目的启动配置和总装配。它不承载业务逻辑,只为其他模块提供稳定运行环境。
目标:
- 项目本地和 Docker 均可启动。
- 环境变量可覆盖关键配置。
- App、模板、静态资源、上传文件和数据库路径统一配置。
- URL 总入口清晰,模块路由各自维护。
## 2. 职责边界
负责:
- `settings.py``urls.py``wsgi.py``asgi.py`
- 环境变量读取和默认值。
- SQLite、静态文件、媒体文件、Chroma、场景配置目录。
- Django Admin 和模块 URL 装配。
不负责:
- 不读取场景 YAML 业务内容。
- 不调用 Agent Core。
- 不处理上传文件文本抽取。
- 不写审计日志。
## 3. 配置项设计
| 配置 | Django setting | 默认值 |
|---|---|---|
| `DJANGO_SECRET_KEY` | `SECRET_KEY` | `dev-secret-key` |
| `DJANGO_DEBUG` | `DEBUG` | `true` |
| `DJANGO_ALLOWED_HOSTS` | `ALLOWED_HOSTS` | `["*"]` |
| `UPLOAD_ROOT` | `MEDIA_ROOT` | `BASE_DIR / "data" / "uploads"` |
| `SCENARIO_CONFIG_DIR` | `SCENARIO_CONFIG_DIR` | `BASE_DIR / "configs"` |
| `CHROMA_PATH` | `CHROMA_PATH` | `BASE_DIR / "data" / "chroma"` |
| `LLM_API_KEY` | `LLM_API_KEY` | 空 |
| `LLM_BASE_URL` | `LLM_BASE_URL` | `https://api.openai.com/v1` |
| `LLM_MODEL` | `LLM_MODEL` | `gpt-4.1-mini` |
| `EMBEDDING_API_KEY` | `EMBEDDING_API_KEY` | 空,默认可复用 `LLM_API_KEY` |
| `EMBEDDING_BASE_URL` | `EMBEDDING_BASE_URL` | 空,默认可复用 `LLM_BASE_URL` |
| `EMBEDDING_MODEL` | `EMBEDDING_MODEL` | `text-embedding-3-small` |
## 4. 目录路径设计
启动前或初始化时应确保:
```text
data/
uploads/
chroma/
configs/
static/
templates/
```
V1 可以在 `settings.py` 中定义路径,在 management command 或启动脚本中创建目录。生产代码不应在每次请求中反复创建目录。
## 5. URL 总路由设计
`config.urls`
```python
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("apps.scenarios.urls")),
path("chat/", include("apps.chat.urls")),
path("documents/", include("apps.documents.urls")),
path("audit/", include("apps.audit.urls")),
]
```
开发模式下追加 `static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)`,用于访问上传文件。
## 6. 静态资源与上传文件设计
- `STATIC_URL = "static/"`
- `STATICFILES_DIRS = [BASE_DIR / "static"]`
- `MEDIA_URL = "media/"`
- `MEDIA_ROOT = UPLOAD_ROOT`
上传文件路径由 Documents 模块按场景组织Config 只提供根目录。
## 7. 环境变量读取设计
V1 可使用标准库 `os.environ.get()`,不强制引入复杂配置库。
布尔值规则:
```text
"1", "true", "yes", "on" -> True
其他 -> False
```
`DJANGO_ALLOWED_HOSTS` 使用逗号分隔,空值时默认 `["*"]`
当前实现约束:
- 本地直接运行 Django 命令时,会先尝试解析根目录 `.env` 文件,再读取进程环境中的覆盖值。
- Docker Compose 方式可以通过 `env_file` 传入同一批变量;当前仓库默认读取 `.env`
- `.env.example` 只保留占位符示例,不保存真实 API Key。
## 8. 验收标准
- `python manage.py check` 通过。
- `python manage.py migrate` 可执行。
- `/``/admin/` 路由可访问。
- `MEDIA_ROOT``CHROMA_PATH``SCENARIO_CONFIG_DIR` 在 settings 中可被其他模块引用。
- LLM 与 Embedding 配置只从 settings 或环境变量读取,不散落在业务代码中。

View File

@@ -1,134 +0,0 @@
# 场景模块详细设计
## 1. 模块目标
Scenarios 模块是业务 Agent 的入口,负责读取和展示场景配置,并向 Chat、Documents、Agent Core 提供场景上下文。
## 2. 职责边界
负责:
-`configs/*.yaml` 读取场景。
- 校验场景必填字段。
- 展示场景列表和场景摘要。
- 提供 `list_scenarios()``get_scenario()` 等服务。
不负责:
- 不执行 Agent。
- 不做 RAG 检索。
- 不调用工具和大模型。
- 不保存审计日志。
## 3. 场景配置结构
必填结构:
```yaml
id: knowledge_qa
name: 知识库问答助手
description: 用于 SOP、制度和内部知识库问答
applicable_questions:
- SOP 问答
- 制度问答
agent:
role: 知识库问答专家
goal: 基于知识库回答用户问题
system_prompt: ""
instructions:
- 回答必须基于检索内容
rag:
enabled: true
collection: knowledge_qa
top_k: 5
tools:
- generate_action_items
output:
type: general_answer
audit:
enabled: true
```
`agent.system_prompt` 为可选字段。配置了非空值时Agent Core 优先使用该字段作为系统提示词;为空或缺失时,由 `role``goal``instructions` 组合生成系统提示词。
`applicable_questions` 作为页面展示字段,若缺失可显示为空列表。
## 4. 场景加载流程
1. 读取 `settings.SCENARIO_CONFIG_DIR`
2. 遍历 `.yaml``.yml` 文件。
3. 使用 YAML parser 转为 dict。
4. 调用 `validate_scenario()`
5. 转换为 `ScenarioConfig` dataclass 或普通 dict。
6. 按文件名或配置顺序返回。
为了便于复试修改V1 不需要强缓存;若加缓存,应提供清理方式或在 DEBUG 下禁用缓存。
## 5. 场景校验规则
必填字段:
- `id`
- `name`
- `description`
- `agent.role`
- `agent.goal`
- `agent.instructions`
- `rag.enabled`
- `tools`
- `output.type`
- `audit.enabled`
校验失败时返回包含文件名、字段路径、错误原因的结果。列表页可以跳过非法场景并展示错误摘要。
## 6. 页面设计
首页路径:`/`
展示:
- 场景名称。
- 场景描述。
- 适用题型。
- RAG 是否启用。
- 工具数量。
- 进入对话按钮。
可选详情页:`/scenarios/<scenario_id>/`。V1 可以把详情合并到 Chat 页面。
## 7. 服务函数设计
```python
def list_scenarios() -> list[ScenarioConfig]:
"""读取配置目录中的合法场景,非法场景以错误摘要返回给页面。"""
def get_scenario(scenario_id: str) -> ScenarioConfig:
"""按场景 ID 返回完整配置,找不到时抛出 ScenarioNotFound。"""
def validate_scenario(config: dict) -> ValidationResult:
"""校验必填字段、字段类型、工具名称和输出类型。"""
```
`get_scenario()` 找不到时抛出业务异常,例如 `ScenarioNotFound`,由 View 转成中文错误提示。
## 8. 异常处理
| 异常 | 处理 |
|---|---|
| 配置目录不存在 | 返回空列表和错误提示 |
| YAML 语法错误 | 标记该文件无效 |
| ID 重复 | 保留第一个,报告重复错误 |
| 必填字段缺失 | 标记该场景无效 |
| 工具不存在 | 场景仍可展示,但 Chat 执行时记录工具错误 |
## 9. 验收标准
- 首页至少展示 5 个场景。
- 场景配置来自 `configs/` 文件。
- 非法配置有明确错误,不导致首页 500。
- Chat 可通过 `scenario_id` 获取完整配置。

View File

@@ -1,127 +0,0 @@
# 文档模块详细设计
## 1. 模块目标
Documents 模块让用户把复试题材料快速变成 Agent 可检索的知识库。V1 必须支持 `.txt``.md``.pdf``.docx`,保证常见复试材料可以进入 RAG。
## 2. 职责边界
负责:
- 文件上传表单和页面。
- 文件保存与元数据记录。
- 读取文本内容。
- 调用 Agent Core RAG 入库。
- 更新入库状态。
不负责:
- 不实现向量检索算法。
- 不生成模型回答。
- 不直接写审计日志。
## 3. 数据模型设计
模型:`UploadedDocument`
字段见 `docs/设计文档/3.数据库设计.md`
常量:
```python
STATUS_UPLOADED = "uploaded"
STATUS_INDEXED = "indexed"
STATUS_FAILED = "failed"
SUPPORTED_EXTENSIONS = {".txt", ".md", ".pdf", ".docx"}
```
文件保存路径建议:
```text
uploads/<scenario_id>/<YYYYMMDD>/<uuid>_<original_name>
```
## 4. 文件上传流程
1. GET `/documents/upload/` 渲染上传表单。
2. POST 校验 `scenario_id` 和文件。
3. 调用 Scenarios 服务确认场景存在。
4. 校验扩展名和文件大小。
5. 保存文件。
6. 创建 `UploadedDocument(status="uploaded")`
7. 跳转文件列表页并展示成功提示。
## 5. 文本抽取流程
抽取函数:
```python
def extract_text(document: UploadedDocument) -> str:
"""按文件类型抽取可入库纯文本,失败时抛出可展示的业务异常。"""
```
规则:
- `.txt`:优先 UTF-8失败时尝试系统默认编码。
- `.md`UTF-8 读取,保留标题、列表和正文。
- `.pdf`:抽取纯文本,不要求 OCR、表格还原和复杂版式理解。
- `.docx`:抽取段落、标题和普通表格文本,不要求完整保留 Word 样式。
- 空文本视为失败。
- 文件不存在视为失败。
XLSX 暂不作为 V1 必须项,可作为后续结构化业务数据导入能力。
## 6. RAG 入库触发流程
POST `/documents/<id>/index/`
1. 获取 `UploadedDocument`
2. 调用 `extract_text()`
3. 调用 `agent_core.rag.ingest.ingest_document()`,传入 `document_id``scenario_id`、文件名和抽取文本。
4. 成功后更新 `status="indexed"`,清空 `error_message`
5. 失败后更新 `status="failed"`,写入 `error_message`
6. 重定向回文件列表页。
入库动作必须使用 POST避免 GET 触发写操作。
已入库或失败文档允许重新入库。重新入库前需要按 `document_id` 清理或覆盖旧 chunk避免重复检索。
## 7. 页面设计
文件列表页展示:
- 文件名。
- 场景 ID。
- 文件类型。
- 文件大小。
- 状态。
- 上传时间。
- 入库按钮。
- 错误信息。
上传页展示:
- 场景下拉框。
- 文件控件。
- 支持类型提示。
- 表单错误。
## 8. 异常处理
| 异常 | 处理 |
|---|---|
| 场景不存在 | 表单错误 |
| 文件为空 | 表单错误 |
| 扩展名不支持 | 表单错误 |
| 文件保存失败 | 页面提示失败 |
| 文本为空 | 状态 failed |
| RAG 入库失败 | 状态 failed 并保存原因 |
## 9. 验收标准
- 可以上传 `.txt``.md``.pdf``.docx`
- 文件列表可看到记录。
- 文件可按场景关联。
- 入库成功状态变为 `indexed`
- 入库失败状态变为 `failed` 且可查看原因。
- 入库失败或已入库文档可重新入库。

View File

@@ -1,118 +0,0 @@
# 对话模块详细设计
## 1. 模块目标
Chat 模块负责复试演示中的主交互:用户选择场景后提交问题,系统展示 Agent 输出、引用、工具调用和审计入口。
## 2. 职责边界
负责:
- 对话页 GET/POST。
- 用户输入表单校验。
- 获取场景配置。
- 调用 Agent Core。
- 调用 Audit 服务写日志。
- 渲染 AgentResult。
不负责:
- 不直接读取 YAML。
- 不直接调用 LLM。
- 不直接执行 RAG 和工具。
- 不实现复杂多轮会话状态。
## 3. 页面设计
路径:`/chat/<scenario_id>/`
GET
- 加载场景配置。
- 展示场景摘要。
- 加载当前场景下状态为 `indexed` 的文档列表。
- 展示空表单。
POST
- 校验输入。
- 执行 Agent。
- 写审计。
- 展示结果和审计链接。
## 4. 表单设计
字段:
| 字段 | 类型 | 规则 |
|---|---|---|
| `message` | textarea | 必填,最大 4000 字 |
| `document_ids` | 多选 | 可选,只能选择当前场景下已入库文档 |
错误提示:
- 空输入:`请输入要咨询的问题。`
- 超长输入:`问题过长,请控制在 4000 字以内。`
- 文档不属于当前场景或未入库:`请选择当前场景下已入库的文档。`
## 5. Agent Core 调用流程
```python
scenario = get_scenario(scenario_id)
result = run_agent(
scenario_config=scenario,
user_input=form.cleaned_data["message"],
options={"document_ids": form.cleaned_data.get("document_ids", [])}
)
```
Chat 只依赖 Agent Core 的统一返回对象,不关心内部是否使用 RAG、工具或真实模型。
未选择文档时,`document_ids` 传空列表或不传,由 Agent Core 默认使用当前场景全部已入库文档。
## 6. 结果展示设计
优先级:
1. 如果 `structured_output` 不为空,展示结构化 JSON 或字段化结果。
2. 展示 `answer`
3. 展示 `references`
4. 展示 `tool_calls`
5. 展示 `latency_ms``model_name``status`
6. 如果有 `error`,展示中文错误提示。
结构化解析失败时,页面仍展示 `raw_output``answer`
## 7. 审计日志写入流程
Agent Core 返回后调用:
```python
audit_log = create_audit_log(
scenario_id=scenario.id,
scenario_name=scenario.name,
user_input=message,
agent_result=result,
)
```
如果 Agent Core 抛异常Chat 应构造失败结果并继续写失败审计。
## 8. 异常处理
| 异常 | 处理 |
|---|---|
| 场景不存在 | 显示错误并返回首页入口 |
| 表单无效 | 留在页面并显示表单错误 |
| Agent Core 抛异常 | 构造 failed AgentResult写审计 |
| 审计写入失败 | 页面提示审计失败,但展示 Agent 输出 |
| LLM 配置缺失 | 展示模型配置缺失 |
## 9. 验收标准
- 从首页可进入对话页。
- 可提交问题并渲染 AgentResult。
- 可选择本次对话使用的文档范围;未选择时默认使用当前场景全部已入库文档。
- 失败时有中文提示。
- 成功和失败都尽量写入审计。
- View 中没有 RAG、工具、LLM 的细节实现。

View File

@@ -1,121 +0,0 @@
# 审计模块详细设计
## 1. 模块目标
Audit 模块记录 Agent 执行过程,使演示者能够解释一次输出的来源、工具调用和模型结果。它是系统从“普通问答页面”变成“可追踪业务 Agent”的关键。
## 2. 职责边界
负责:
- `AgentAuditLog` 模型。
- 审计写入服务。
- 审计列表页。
- 审计详情页。
- 敏感信息过滤。
不负责:
- 不执行 Agent。
- 不执行 RAG。
- 不执行工具。
- 不调用模型。
## 3. 数据模型设计
模型:`AgentAuditLog`
字段见 `docs/设计文档/3.数据库设计.md`
JSON 字段默认值必须使用函数,例如 `default=list``default=dict`,避免多实例共享同一对象。
## 4. 日志写入流程
服务函数:
```python
def create_audit_log(
scenario_id: str,
scenario_name: str,
user_input: str,
agent_result: AgentResult,
) -> AgentAuditLog:
"""将 AgentResult 映射为 AgentAuditLog并在保存前做敏感信息脱敏。"""
```
写入映射:
- `agent_result.references` -> `retrieved_chunks`
- `agent_result.tool_calls` -> `tool_calls`
- `agent_result.structured_output` -> `structured_output`
- `agent_result.answer` -> `final_answer`
- `agent_result.raw_output` -> `raw_output`
- `agent_result.model_name` -> `model_name`
- `agent_result.latency_ms` -> `latency_ms`
- `agent_result.status` -> `status`
- `agent_result.error` -> `error_message`
## 5. 日志列表页设计
路径:`/audit/`
查询:
- 默认按创建时间倒序。
- V1 可不做分页,若日志较多再加 Django Paginator。
展示:
- ID。
- 场景名称。
- 用户输入前 80 字。
- 状态。
- 模型名。
- 耗时。
- 创建时间。
- 详情链接。
## 6. 日志详情页设计
路径:`/audit/<log_id>/`
展示:
- 基础信息。
- 用户输入。
- 最终回答。
- 结构化输出。
- RAG 检索片段。
- 工具调用。
- 原始输出。
- 错误信息。
JSON 可用格式化后的 `<pre>` 展示。
## 7. 敏感信息处理
不得保存:
- `LLM_API_KEY`
- 完整环境变量 dump
- 用户机器上的敏感绝对路径
- Docker secret 或 token
如错误信息来自异常对象,应在保存前做简单脱敏,至少替换 API Key 值。
## 8. 异常处理
| 异常 | 处理 |
|---|---|
| AgentResult 字段缺失 | 使用默认空值 |
| JSON 不可序列化 | 转为字符串或空对象 |
| 日志不存在 | 返回 404 |
| 写入失败 | 抛给 Chat由 Chat 展示审计失败提示 |
## 9. 验收标准
- 每次对话成功后有审计日志。
- Agent 失败也有失败日志。
- 列表页可查看日志摘要。
- 详情页可查看输入、输出、引用和工具调用。
- 日志不包含 API Key。

View File

@@ -1,259 +0,0 @@
# 智能核心模块详细设计
## 1. 模块目标
Agent Core 提供独立于 Django View 的智能编排能力。它消费场景配置,执行 RAG、工具、模型调用和结构化解析最终返回统一 AgentResult。
## 2. 职责边界
负责:
- Agent 编排。
- 场景配置对象消费。
- RAG 入库和检索。
- 工具注册与执行。
- LLM Provider 与 Embedding Provider。
- 结构化输出解析。
- AgentResult 定义。
不负责:
- 不渲染页面。
- 不处理 Django 表单。
- 不保存 Django Model。
- 不管理登录权限。
## 3. 子模块划分
```text
agent_core/
orchestrator.py
scenario_loader.py
llm_provider.py
tool_registry.py
structured_output.py
rag/
ingest.py
retriever.py
tools/
builtin_tools.py
schemas/
outputs.py
```
`scenario_loader.py` 可作为非 Django 环境下加载配置的工具Django 场景展示仍由 `apps.scenarios` 负责。
## 4. Orchestrator 设计
入口:
```python
def run_agent(scenario_config, user_input: str, options: dict | None = None) -> AgentResult:
"""执行一次 Agent 编排options 可包含 document_ids 等运行期约束。"""
```
流程:
1. 记录开始时间。
2. 根据 `rag.enabled``scenario_id` 和可选 `document_ids` 检索引用。
3. 根据 `tools` 执行或准备工具结果。
4. 构造 messages。
5. 调用 LLM Provider。
6. 解析结构化输出。
7. 计算耗时。
8. 返回 `AgentResult(status="success")`
9. 捕获可恢复异常并返回 `status="failed"`
V1 在缺少 LLM 或 Embedding 配置时必须返回清晰失败结果。测试代码可以使用 mock provider但 V1 验收链路必须通过真实 OpenAI 兼容 LLM、Embedding 和 Chroma。
## 5. Scenario Loader 设计
Agent Core 的 Scenario Loader 用于脚本、测试或后续独立服务场景。它不依赖 Django View可以复用 Scenarios 模块的字段规范。
接口:
```python
load_scenario(path: str) -> dict
load_scenarios(directory: str) -> list[dict]
```
## 6. RAG 设计
入库接口:
```python
def ingest_document(
document_id: int,
scenario_id: str,
source_file: str,
text: str,
collection: str,
) -> IngestResult:
"""切分文档、生成 embedding并写入 Chroma。重新入库时覆盖同一 document_id 的旧 chunk。"""
```
检索接口:
```python
def retrieve(
scenario_id: str,
query: str,
collection: str,
top_k: int = 5,
document_ids: list[int] | None = None,
) -> list[ReferenceChunk]:
"""按场景和可选文档范围执行向量检索,返回可审计引用片段。"""
```
切分策略:
- 默认 chunk size 800 到 1000 字。
- overlap 100 到 150 字。
- metadata 包含 `scenario_id``document_id``source_file``chunk_id`
RAG 入库和检索必须使用 Embedding Provider 与 Chroma。单元测试桩或开发阶段临时验证方案不属于 V1 验收设计。
## 7. Tool Registry 设计
工具注册:
```python
registry.register("calculate_rate", calculate_rate)
registry.get("calculate_rate")
registry.run("calculate_rate", **kwargs)
```
工具结果统一:
```json
{
"tool_name": "calculate_rate",
"success": true,
"arguments": {},
"result": {},
"error": ""
}
```
内置工具:
- `calculate_rate`
- `query_demo_records`
- `check_required_fields`
- `generate_action_items`
工具函数不得直接读取 API Key 或执行无审计的外部副作用。
## 8. LLM Provider 设计
接口:
```python
class LLMProvider:
def generate(self, messages: list[dict], response_format: dict | None = None) -> LLMResponse:
"""调用 OpenAI 兼容 Chat Completions 接口并返回统一响应对象。"""
```
配置来源:
- `LLM_API_KEY`
- `LLM_BASE_URL`
- `LLM_MODEL`
Provider 对外隐藏供应商差异Orchestrator 只处理 `LLMResponse.content``LLMResponse.model_name` 和错误信息。供应商可自主选择 OpenAI、硅基流动等 OpenAI 兼容服务。
Embedding Provider 接口:
```python
class EmbeddingProvider:
def embed_texts(self, texts: list[str]) -> list[list[float]]:
"""调用 OpenAI 兼容 Embeddings 接口,返回与输入文本一一对应的向量。"""
```
配置来源:
- `EMBEDDING_API_KEY`
- `EMBEDDING_BASE_URL`
- `EMBEDDING_MODEL`
`EMBEDDING_API_KEY``EMBEDDING_BASE_URL` 为空时,可以复用 `LLM_API_KEY``LLM_BASE_URL`
## 9. Structured Output 设计
接口:
```python
def parse_structured_output(raw_output: str, output_type: str) -> ParseResult:
"""优先解析 JSON并根据输出类型返回结构化结果或解析错误。"""
```
策略:
- 优先解析 JSON。
- 根据 `output_type` 做字段补齐或轻校验。
- 失败时返回 `success=False`,保留 `raw_output`
- 不因结构化解析失败导致整个 Agent 流程崩溃。
## 10. AgentResult 设计
建议 dataclass
```python
@dataclass
class AgentResult:
answer: str
structured_output: dict
references: list
tool_calls: list
raw_output: str
model_name: str
latency_ms: int
status: str
error: str = ""
```
所有字段必须有默认值或构造时明确传入,保证 Audit 模块写入稳定。
## 11. Adapter 扩展设计
统一接口:
```python
class AgentEngine:
def run_agent(self, scenario_config, user_input: str, options: dict | None = None) -> AgentResult:
"""保持与顶层 run_agent 函数一致的输入输出合约。"""
```
V1 实现:
- `LightweightOrchestrator`
后续扩展:
- `DifyAdapter`
- `OpenAIAgentsAdapter`
- `LangGraphAdapter`
Adapter 只能替换编排实现,不能改变 Django 层依赖的 AgentResult 合约。
## 12. 异常处理
| 异常 | 处理 |
|---|---|
| RAG 检索失败 | 记录错误,允许继续或返回 failed |
| 工具不存在 | 记录失败工具调用 |
| 工具执行异常 | 捕获并返回失败工具结果 |
| LLM 配置缺失 | 返回 failed AgentResult |
| LLM 调用失败 | 返回 failed AgentResult |
| JSON 解析失败 | 返回 success 但带解析错误,展示 raw output |
## 13. 验收标准
- Chat 可以调用 `run_agent()`
- 返回对象字段稳定完整。
- RAG 按 `scenario_id` 隔离。
- RAG 支持按 `document_ids` 限定本次对话的文档范围。
- 工具调用结果格式统一。
- LLM 与 Embedding 配置从环境变量读取。
- 结构化解析失败不导致页面崩溃。
- Agent Core 不依赖 Django View。

View File

@@ -0,0 +1,248 @@
# 需求重构总览与待确认事项
## 1. 文档目的
本轮需求分析不再沿用“通用 AI Agent Demo 框架”的抽象表述,而是基于 `docs/` 目录下已经提供的真实笔试材料,重构为一个面向 **NMPA 境内第三类体外诊断试剂注册申报资料准备与审核** 的业务系统需求。
本文档作为总览,主要说明三件事:
1. 本次重构后的业务目标是什么。
2. 后续模块化需求分析采用什么拆分方式。
3. 当前原始材料中有哪些必须尽快向需求方确认的地方。
## 2. 原始材料所反映的真实业务目标
根据 `docs/【模拟题二】试剂盒临床注册文件准备与审核Agent/【模拟题二】试剂盒临床注册文件准备与审核Agent.md``docs/目标产品说明书.docx``docs/附件 4 体外诊断试剂注册申报资料要求及说明.doc``docs/原始材料/关于公布体外诊断试剂注册申报资料要求和批准证明文件格式的公告/` 下公告附件,以及 `docs/第1章 监管信息/` 下样例文件,可以确认本题的核心目标不是“泛化问答 Agent”而是一个围绕注册申报资料整理、核查、回填、比对和预警的垂直 Agent 系统。
系统需要覆盖的业务闭环至少包括:
1. 扫描申报文件夹,形成资料目录、文件清单、页数统计和章节点归属。
2. 基于法规要求和申报目录模板,判断资料是否齐全、是否放对位置、是否缺少关键附件。
3. 从说明书、申请表、产品列表、声明文件等材料中提取关键信息,形成统一字段池。
4. 利用统一字段池回填申请表、对照清单、章节目录或其他待生成文件。
5. 对跨文档的名称、规格、适用范围、靶标、机构、日期、标准清单等信息做一致性检查。
6. 输出可讲解、可演示、可追踪的风险预警和处理建议。
## 3. 建议的系统定位
### 3.1 不再定位为“通用题型适配器”
当前项目 README 中强调“拿到任何题都能快速适配”,这适合作为框架背景,但已经不适合作为本题需求文档主线。对于本次笔试题,系统更适合定位为:
> 试剂盒临床注册文件准备与审核智能体平台
它仍然保留配置化和可扩展能力,但产品叙事必须切换为“注册申报资料准备助手”,否则会削弱题目贴合度。
### 3.2 建议保留的底层架构
尽管业务定位变化较大,现有的架构边界仍然是合理的:
- `config`:系统配置、环境变量、路径、部署入口。
- `apps.scenarios`:场景定义与任务入口,但需要从“多题型模板”转向“注册申报子任务配置”。
- `apps.documents`:上传、解析、入库、目录构建、资料状态管理。
- `apps.chat`:面向操作人员的交互入口,展示审核结果与回填结果。
- `apps.audit`:记录每次审核、抽取、回填、预警的执行痕迹。
- `agent_core`法规规则、抽取逻辑、结构化输出、工具编排、RAG 检索。
因此,本次需求分析采用“保留模块边界,重写模块职责”的方式推进,而不是推翻现有项目结构。
## 4. 本轮需求分析文档拆分方式
后续需求分析将按照现有项目主模块输出,不做过细拆分,避免把一个可演示系统拆成过多碎文件:
1. `1.config模块需求分析.md`
2. `2.scenarios模块需求分析.md`
3. `3.documents模块需求分析.md`
4. `4.chat模块需求分析.md`
5. `5.audit模块需求分析.md`
6. `6.agent_core模块需求分析.md`
这样的拆分有三个好处:
1. 与当前代码目录一致,后续重构时容易对照落地。
2. 每个模块既能写清职责,也能覆盖该模块承担的真实业务流程。
3. 复试讲解时可以从“页面层、资料层、编排层、合规层”自然展开。
## 5. 从原始材料中已经识别出的关键业务特征
### 5.1 本题的审核对象是“注册申报资料包”,不是单篇文档
题面要求明确包含“自动汇总注册申报文件夹中的所有文件及页数”“对照法规要求核查文件完整性”,这说明系统输入是一个资料集合,而不是只上传一个 PDF 后做问答。
因此,系统设计必须支持“文件夹视角”的审核。
### 5.2 本题非常强调“章节点”和“法规目录”
`附件 4 体外诊断试剂注册申报资料要求及说明` 以及 `关于公布体外诊断试剂注册申报资料要求和批准证明文件格式的公告` 附件可看出,资料并不是简单的“有或没有”,而是存在固定层级结构和明确的附件格式要求,至少包含:
- 第 1 章 监管信息
- 第 2 章 综述资料
- 第 3 章 非临床资料
- 第 4 章 临床评价资料
- 第 5 章 产品说明书和标签样稿
- 第 6 章 质量管理体系文件
这意味着审核规则必须识别“资料缺失”“章节点错误”“章节目录与实际文件不一致”等不同问题类型。
同时,新增公告材料还表明系统不能只停留在“目录齐套性”这一层,还应预留以下法规校验层级:
1. 注册申报资料要求及说明。
2. 医疗器械注册申报资料和批准证明文件格式要求。
3. 体外诊断试剂安全和性能基本原则清单。
4. 注册证、变更注册(备案)文件等批准证明文件格式。
### 5.3 本题非常强调跨文档字段一致性
现有样例材料中已经出现多个明显的潜在冲突点,例如:
- 题面说明书是“新型冠状病毒 2019-nCoV 核酸检测试剂盒”。
- 监管信息目录、申请表、产品列表则是“呼吸道合胞病毒、肺炎支原体核酸检测试剂盒”。
结合当前已确认口径,本项目目标是建立“通用的试剂盒临床注册文件准备与审核智能体”,因此这些样例差异不要求在需求阶段统一到某一个具体产品,而是要求系统具备:
1. 按项目、批次、文档范围界定审核对象的能力。
2. 对同一审核对象内文档执行严格一致性校验的能力。
3. 对疑似混档、跨产品资料混入给出风险提示的能力。
因此,需求分析必须把“跨文档字段冲突检测”写成核心能力,而不是附属功能。
### 5.4 本题不仅需要审核,还需要回填与生成
题面第三项写得很明确:从产品文件中提取关键信息并自动填写至目标文件。因此系统不是只出一份报告,还要支持“结构化字段输出 + 对目标文件字段回填”。
### 5.5 本题存在历史申报与监管沟通情境
`CH1.9 产品申报前沟通的说明.doc` 体现出:
- 当前申报可能是二次申报。
- 历史受理号、撤回信息、临床机构调整、总结报告重形成都需要保留痕迹。
因此,系统应考虑“申报轮次”“历史沟通记录”“版本来源说明”等能力,而不应只把资料看成静态附件。
## 6. 建议的演示主线
为了后续 Demo 更贴合复试陈述,建议整个系统的业务主线固定为:
1. 导入一批注册资料。
2. 系统自动形成申报目录与页数清单。
3. 系统根据法规目录检查缺失项、错放项、待补项。
4. 系统抽取产品核心信息并形成字段总表。
5. 系统检查说明书、申请表、产品列表、声明文件之间的一致性。
6. 系统输出风险清单、建议动作以及需要人工确认的问题。
如果演示时间允许,建议再增加一个“法规依据展示”环节:
7. 系统展示当前完整性结论所依据的公告附件、资料要求和模板来源。
## 7. 已确认事项
以下内容已根据最新沟通结果确认,并已同步进入后续模块需求:
### 7.1 产品目标范围
- 系统目标是建立“通用的试剂盒临床注册文件准备与审核智能体”。
- 因此不需要把当前样例材料统一解释为同一个具体产品的唯一资料包。
- 实际审核时,应由系统或用户先界定当前参与核查的项目批次或文档范围。
### 7.2 飞书接入方向
- 系统需要支持飞书接入,并纳入本次 Demo 的明确交付范围。
- 目标交互包括:在飞书内完成任务选择、结果查看,以及通过飞书机器人 `@` 对应责任人或责任角色。
- 本次 Demo 需要支持手动配置责任人及飞书账号。
- 需要支持群聊机器人作为飞书入口形态之一。
### 7.3 一致性核查口径
- 当前阶段按完全一致处理。
- 不采用“语义相近即可视为一致”的放宽规则。
- 因此同一字段只要文案不一致,就应判为冲突或至少进入人工复核。
### 7.4 风险分级与准入规则
- 风险按高 / 中 / 低三级分类。
- 存在必须拦截提交的情形。
- 风险需要综合分析并分别打分。
- 只要存在任一高风险项,本次审核结果就不允许通过。
### 7.5 第 2 至第 6 章样本与模板口径
- 结合补充的公告附件包,可以确认第 2 至第 6 章对应的法规要求、章节点结构和资料模板口径已经存在。
- 这些材料可以作为系统进行完整性检查、章节归类、字段回填和生成审核结论的“监管模板 / 结构模板”。
- 但它们并不等于每一章都有完整可直接复用的企业填充样本,因此需求上应表述为“可作为法规模板与结构模板”,而不是“已经具备全量业务样本”。
### 7.6 输出文档形式
- 必须支持输出新的 Word 文档。
- 输出结果需要支持导出下载。
- 对目标模板的格式、标题层级、表格样式、盖章位和版式必须达到可直接报送级版式。
- 允许将用户后续编写的新模板纳入模板库,后续回填应以模板库中的标准模板为准。
### 7.7 法规主规则源与 RAG 口径
- 当前版本默认以 `关于公布体外诊断试剂注册申报资料要求和批准证明文件格式的公告` 附件包为主规则来源。
- `附件 4` 视为同源补充材料,不另立异源规则体系。
- 现版本不必把“法规可在线更新”做成显式能力。
- 法规材料需要按“章 -> 条 -> 要求项 -> 模板字段”四级结构维护。
- 需同步建设结构化规则文件,用于完整性校验、格式校验和模板字段映射,避免完全依赖检索文本。
- 法规材料仍建议切片入库到 RAG用于证据检索、条款引用和审计追溯。
- 合规判断逻辑仍应坚持“结构化规则优先RAG 辅助解释”,不能把 RAG 当成唯一规则引擎。
- 系统需要提供后端管理页面,保留人工校订入口和知识库更新入口。
### 7.8 飞书技术接入口径
- 飞书接入可参考飞书开放平台提供的 AI Agent 接入路线,而不是自定义一个模糊的消息转发方案。
- 首版需求应明确两条接入路径:
1. 飞书机器人 / Channel SDK 作为用户入口。
2. 飞书 CLI / OpenAPI MCP 作为 Agent 访问飞书消息、文档、日历、多维表格等能力的工具接入方式。
- 本次 Demo 需实现群聊机器人触发,并支持在飞书内完成任务选择、结果查看和责任人通知。
- 若需要责任人 `@`、回传摘要、文档评论回复或群消息触发,应在后续设计中按飞书应用权限、事件订阅和可见范围来实现。
## 8. 重新整理后的待确认事项
以下问题是在前序确认基础上重新收敛后的剩余待确认项。它们不影响当前需求分析继续推进,但会影响后续设计取舍和开发优先级。
### 8.1 V1 演示深度是否要求真正覆盖全六章流程
原因:
- 当前样例文件主要集中在第 1 章监管信息。
- 题面与法规附件实际要求覆盖六大章。
需确认的问题:
1. Demo 是否允许以第 1 章样本做主演示,同时以法规模板覆盖第 2 至第 6 章的完整性校验口径。
2. 若后续要把第 2 至第 6 章做成更强的“内容级抽取与回填演示”,是否还会补充企业真实样本。
### 8.2 仍建议补充确认的业务细节
以下问题已经有明确主方向,但仍建议在进入详细设计前再确认实现细节:
1. Word 导出是否除 `docx` 外还要同步输出 PDF 归档件。
2. 用户新写模板进入模板库后,模板版本、生效范围和审批流程是否需要管理。
3. 责任人配置是仅按章节点维护,还是同时支持按任务类型、项目角色双维度维护。
4. 后端知识库更新入口是否只允许管理员使用,还是允许业务审核人员参与人工校订。
## 9. 本轮需求分析采用的默认假设
在需求方未进一步确认前,后续模块文档将先基于以下假设展开:
1. 系统以“注册申报资料审核与准备”作为唯一主线,不再强调通用多题型产品定位。
2. Demo 首版可先覆盖监管信息章,并为全章扩展预留结构。
3. 一致性检查前需要先确定当前审核范围,不能默认把所有原始材料视为同一产品资料包。
4. 法规完整性检查首版以本地结构化规则和本地法规材料为准,不强依赖联网抓取法规。
5. 法规材料会同步按“章 -> 条 -> 要求项 -> 模板字段”四级结构切片入 RAG并同步维护结构化规则文件规则判断以结构化规则优先RAG 负责检索、引用和解释。
6. 回填能力必须达到“生成新的 Word 文档并支持导出,且版式可直接报送”的交付标准。
7. 一致性判断当前默认按完全一致执行。
8. 风险判断采用高 / 中 / 低三级,且任一高风险项直接判定不通过。
9. 飞书接入属于本次 Demo 明确范围,需支持在飞书内完成任务选择、结果查看和责任人通知,并支持群聊机器人入口。
10. V1 的法规任务边界先聚焦“注册申报”,变更备案和延续注册在规则架构上预留扩展位,但不作为当前主验收范围。
11. 系统需要提供后端管理页面,支持人工校订、模板管理、责任人维护和知识库更新。
## 10. 结论
本次需求重构的关键,不是再补几条场景配置,而是把项目从“通用 Demo 基座”转向“IVD 注册申报资料审核平台”。只要模块需求始终围绕以下四件事展开,后续设计和实现就会比较稳:
1. 文件夹级资料治理。
2. 法规目录级完整性校验。
3. 跨文档字段抽取与一致性核查。
4. 可追踪的风险预警与回填辅助。

View File

@@ -1,583 +1,175 @@
# Universal Agent Demo Framework V1 需求文档
# V1 需求文档
## 1. 项目背景
## 1. 文档目的
项目用于复试展示。复试题目暂时未知但大概率围绕企业生产、质量、客服、财务、SOP、文档审核、工单处理等场景
文档作为当前项目 V1 阶段的总需求索引文档,用于统一说明本轮笔试题对应的产品定位、目标用户、核心业务闭环、模块拆分方式和后续阅读路径
项目目标不是提前猜中某一个具体业务题,而是先搭建一个通用 AI Agent Demo 底座。拿到复试题目后,可以通过修改场景配置、上传知识库、补充少量业务工具,快速生成一个可演示的业务 Agent 系统。
核心理念:
与历史“通用 AI Agent Demo 框架”定位不同,本轮 V1 需求以 `docs/` 目录中的真实题面与资料样本为准,系统目标已经切换为:
```text
业务 Agent = 场景配置 + 知识库 + 工具集 + 输出模板 + 审计日志 + 模型适配器
```
> 试剂盒临床注册文件准备与审核智能体平台
## 2. 项目目标
V1 版本目标是实现一个可运行、可演示、可快速改题的基础平台。
系统需要支持:
- 通过配置快速创建不同业务 Agent。
- 支持上传文档并构建 RAG 知识库。
- 支持根据场景调用内置业务工具。
- 支持结构化输出,方便展示报告、风险点、建议动作等结果。
- 支持审计日志,记录用户输入、检索内容、工具调用和模型输出。
- 支持 Docker 一键启动,降低复试现场环境风险。
- 支持快速替换大模型 API。
## 3. 非目标
V1 不追求完整企业级平台能力,以下内容暂不作为第一版重点:
- 复杂权限系统。
- 多租户管理。
- 完整工作流引擎。
- 复杂多 Agent 协作。
- 前后端分离架构。
- 深度集成 Dify。
- 生产级高并发优化。
- 完整在线文档协同编辑。
## 2. 产品定位
## 4. 技术方案
本系统面向 **NMPA 境内第三类体外诊断试剂注册申报资料准备与审核** 场景,服务于需要整理、核查、抽取、回填和追踪注册资料的业务人员。
### 4.1 总体架构
V1 使用 Django 单体应用承载企业系统外壳Agent Core 作为独立 Python 模块承载智能编排能力。
```text
Django Monolith
|
|-- Web UI
| |-- 场景选择
| |-- Agent 对话
| |-- 文件上传
| |-- 结构化结果展示
| |-- 审计日志查看
|
|-- Django Admin
| |-- 上传文件管理
| |-- 审计日志管理
| |-- 示例业务数据管理
|
|-- Agent Core
| |-- 场景配置加载
| |-- RAG 检索
| |-- 工具注册与调用
| |-- 大模型适配
| |-- 结构化输出解析
|
|-- Storage
|-- SQLite
|-- Chroma
|-- Uploaded Files
```
### 4.2 技术栈
| 模块 | 技术 | 说明 |
|---|---|---|
| Web 框架 | Django | 负责页面、模型、后台、文件上传和业务管理 |
| 页面渲染 | Django Templates + Bootstrap | 降低前端复杂度,快速完成 Demo |
| 数据库 | SQLite | V1 默认数据库,适合本地演示 |
| 向量库 | Chroma | 本地 RAG 知识库 |
| Agent Core | 自研轻量 Orchestrator | 保证可控、易讲解、易改题 |
| LLM 接入 | OpenAI API 兼容接口 | 方便切换 OpenAI、硅基流动等兼容服务、国产模型或本地代理 |
| Embedding 接入 | OpenAI API 兼容接口 | 用于文档向量化,供应商可自主选择 |
| 部署 | Docker + Docker Compose | 支持一键启动 |
## 5. 用户角色
V1 只设计一个主要用户角色:
### Demo 操作者
通常是复试时的展示者,负责选择场景、上传材料、输入问题、查看 Agent 输出和审计记录。
暂不区分管理员、业务人员、审核人员等复杂角色。
## 6. 核心使用流程
### 6.1 复试前准备流程
系统主体围绕注册申报审核场景展开,但能力目标是沉淀为“通用的试剂盒临床注册文件准备与审核智能体”,而不是只绑定某一个具体试剂盒产品。
1. 启动系统。
2. 选择或复制一个已有场景模板。
3. 根据题目修改场景配置。
4. 上传题目相关文档。
5. 如有必要,补充一个业务工具函数。
6. 运行一次测试对话。
7. 使用审计日志确认 RAG、工具调用和输出链路正常。
### 6.2 复试演示流程
1. 打开系统首页。
2. 展示系统支持多个业务场景。
3. 选择当前题目对应的 Agent。
4. 上传或选择知识库文档。
5. 输入业务问题。
6. 展示 Agent 的结构化输出。
7. 展示引用来源、工具调用和审计日志。
8. 说明同一平台可通过配置切换到其他业务场景。
## 7. 场景模板
V1 预置 5 类通用场景模板,用于覆盖大多数复试题型。
| 模板 ID | 模板名称 | 适用题型 |
|---|---|---|
| knowledge_qa | 知识库问答助手 | SOP、制度、客服知识库、内部文档问答 |
| document_review | 文档审核助手 | 合同审核、制度审核、SOP 审核、材料合规检查 |
| ticket_assistant | 工单处理助手 | 客服工单、售后工单、运维工单 |
| quality_analysis | 质量异常分析助手 | 生产质量、缺陷分析、原因定位 |
| risk_audit | 风险审核助手 | 财务审核、采购审核、报销审核、合同风险 |
## 8. 场景配置需求
场景应通过 YAML 或 JSON 文件定义,避免把业务逻辑写死在代码中。
配置内容包括:
- 场景 ID。
- 场景名称。
- 场景描述。
- Agent 角色。
- Agent 任务目标。
- 系统提示词 可选。
- 是否启用 RAG。
- RAG 检索参数。
- 可用工具列表。
- 输出模板类型。
- 审计策略。
示例:
```yaml
id: quality_analysis
name: 质量异常分析助手
description: 用于分析生产质量异常、检索 SOP、生成处理建议
agent:
role: 质量管理专家
goal: 根据用户问题、知识库和工具结果,输出可执行的质量分析报告
system_prompt: 你是质量管理专家,需要基于知识库和工具结果输出结构化质量分析报告
instructions:
- 回答必须基于知识库或工具结果
- 不确定时必须说明缺失信息
- 涉及质量风险时给出风险等级
系统不再以“适配任意业务题”的通用 Demo 作为对外主叙事,而是聚焦以下业务价值:
rag:
enabled: true
collection: quality_docs
top_k: 5
1. 自动汇总注册资料目录与页数。
2. 对照法规要求检查资料完整性。
3. 抽取产品关键信息并形成统一字段池。
4. 支持目标文件字段回填准备。
5. 核查跨文档信息一致性与章节规范性。
6. 输出合规风险预警和处理建议。
tools:
- query_demo_records
- calculate_rate
## 3. 原始材料依据
output:
type: quality_report
当前需求分析主要基于以下材料整理:
audit:
enabled: true
log_retrieval: true
log_tool_calls: true
```
1. `docs/【模拟题二】试剂盒临床注册文件准备与审核Agent/【模拟题二】试剂盒临床注册文件准备与审核Agent.md`
2. `docs/目标产品说明书.docx`
3. `docs/附件 4 体外诊断试剂注册申报资料要求及说明.doc`
4. `docs/第1章 监管信息/` 下的监管目录、申请表、产品列表、声明与沟通记录样例
5. `docs/原始材料/关于公布体外诊断试剂注册申报资料要求和批准证明文件格式的公告/` 下的公告附件包
## 9. 功能需求
其中新增公告附件包使法规依据不再只是单篇“资料要求说明”,而是扩展为一组正式规则来源,包括:
### 9.1 首页
1. 体外诊断试剂注册申报资料要求及说明
2. 医疗器械注册申报资料和批准证明文件格式要求(体外诊断试剂)
3. 体外诊断试剂安全和性能基本原则清单
4. 中华人民共和国医疗器械注册证(体外诊断试剂)格式
5. 体外诊断试剂变更备案 / 变更注册申报资料要求及说明
6. 体外诊断试剂延续注册申报资料要求及说明
首页需要展示系统定位和可用场景列表
当前 V1 默认以“公告附件包”作为主规则来源,并将 `附件 4 体外诊断试剂注册申报资料要求及说明` 视作同源补充材料,而不是独立的第二套规则来源
页面能力:
## 4. V1 范围
- 查看所有 Agent 场景
- 进入某个场景的对话页。
- 查看最近审计日志入口。
- 查看文件上传入口。
V1 聚焦“可运行、可讲解、可演示”的注册资料审核闭环,不追求一次性做成完整商业平台
### 9.2 场景选择
### 4.1 V1 必须覆盖
系统需要支持从预置模板中选择业务场景。
1. 资料上传与管理
2. 文件目录与页数汇总
3. 法规完整性检查
4. 产品关键信息抽取
5. 跨文档一致性核查
6. 风险预警输出
7. 审计留痕
8. 本地可运行与 Docker 演示启动
V1 从 YAML 配置文件读取场景。后台管理只负责上传文件、审计日志和示例业务数据管理,不作为场景配置入口。
其中第 3 项“法规完整性检查”在 V1 中建议明确分为三层:
最低要求:
1. 资料齐套性检查
2. 章节点和结构合规性检查
3. 批准证明文件格式与输出映射检查
- 展示场景名称。
- 展示场景描述。
- 展示场景适用题型。
- 点击后进入对应 Agent 对话页。
### 4.2 V1 可接受的简化
### 9.3 Agent 对话
1. 首版可优先覆盖第 1 章监管信息,并为全章扩展预留结构。
2. 首版即要求具备“生成新的 Word 文档并支持导出”的能力,且输出版式必须达到可直接报送级别。
3. 首版法规校验可以本地规则为主,不强依赖联网抓取最新法规。
4. 首版需要支持飞书内完成任务选择、结果查看和责任人通知,并支持群聊机器人入口及手动维护责任人 / 飞书账号映射。
5. 首版法规任务边界以“注册申报”主流程为核心,变更备案和延续注册暂作为规则扩展方向。
Agent 对话页是核心演示页面。
## 5. 业务闭环
页面需要包含
建议按以下业务闭环理解整套系统
- 当前场景名称
- 用户输入框
- 文件上下文选择,可多选当前场景已入库文档;不选时默认使用当前场景全部已入库文档
- Agent 输出区域
- 结构化结果展示区域
- 引用片段展示区域
- 工具调用展示区域。
1. 导入注册申报资料
2. 识别文档、统计页数、构建目录
3. 依据法规目录进行完整性核查
4. 从说明书、申请表、产品列表等材料中抽取统一字段
5. 对同名字段进行跨文档一致性比对
6. 形成风险清单、回填结果和审计记录
Agent 执行流程
在规则执行层,建议采用“双层知识底座”
1. 接收用户问题
2. 加载当前场景配置
3. 如果启用 RAG则检索相关知识片段。
4. 根据场景判断是否调用工具。
5. 调用大模型生成结果。
6. 解析为结构化输出。
7. 写入审计日志。
8. 返回页面展示。
1. 结构化规则文件负责完整性判断、强一致比对和风险映射
2. 公告附件原文切片入 RAG负责条款引用、证据检索和解释说明
### 9.4 文件上传
其中法规知识维护方式应固定为:
系统需要支持上传题目材料和知识库文档
1. 按“章 -> 条 -> 要求项 -> 模板字段”四级结构维护
2. 同步建设结构化规则文件,避免让完整性校验完全依赖检索文本。
3. 提供后台管理页面,支持人工校订和知识库更新。
V1 支持的文件类型
在法规维度上,建议把完整流程理解为
- TXT
- Markdown
- PDF
- DOCX
- XLSX 可作为后续增强
1. 识别当前审核任务属于“注册申报”主流程。
2. 匹配对应的资料要求与章节点模板。
3. 检查资料齐套性与章节结构。
4. 对需要回填或输出的批准证明文件格式做字段映射准备。
文件上传后需要保存:
## 6. 模块拆分
- 原始文件名。
- 文件路径。
- 文件类型。
- 上传时间。
- 关联场景。
- 是否已入库。
V1 需求分析按项目现有主模块拆分,不做过度细分:
### 9.5 RAG 知识库
1. [1.config模块需求分析.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\1.config模块需求分析.md)
2. [2.scenarios模块需求分析.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\2.scenarios模块需求分析.md)
3. [3.documents模块需求分析.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\3.documents模块需求分析.md)
4. [4.chat模块需求分析.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\4.chat模块需求分析.md)
5. [5.audit模块需求分析.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\5.audit模块需求分析.md)
6. [6.agent_core模块需求分析.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\6.agent_core模块需求分析.md)
系统需要支持将上传文档写入向量库,并在 Agent 对话时检索。
另附一份待确认事项文档,供与需求方沟通时直接使用:
V1 RAG 流程:
- [0.需求重构总览与待确认事项.md](F:\PyCharm\DEMO-AGENT\docs\需求分析\0.需求重构总览与待确认事项.md)
1. 读取上传文件文本。
2. 按固定长度切分文本。
3. 生成 embedding。
4. 写入 Chroma collection。
5. 对话时根据用户问题检索 top_k 片段。
6. 将片段作为上下文传给 Agent。
7. 在结果中展示引用来源。
## 7. 当前识别出的关键业务特征
### 9.6 工具调用
### 7.1 审核对象是“资料包”
系统需要提供一个工具注册机制
本题输入对象是整套注册申报资料,不是单篇文档问答
V1 内置工具建议包括:
### 7.2 审核标准是“法规目录 + 资料内容”
| 工具名 | 用途 |
|---|---|
| calculate_rate | 计算比例、缺陷率、通过率等指标 |
| query_demo_records | 查询模拟业务数据 |
| check_required_fields | 检查文档或表单必填项 |
| generate_action_items | 根据问题生成行动项 |
系统既要看是否有文件,也要看是否放对章节点、内容是否对应。
工具调用需要记录到审计日志中。
### 7.3 系统必须具备“冲突识别”
### 9.7 结构化输出
当前样例中已经存在不同产品资料混入的迹象。这不要求系统默认把全部材料视为同一个产品,而是要求系统具备以下能力:
不同场景需要不同输出模板
1. 支持按项目批次或文档范围界定审核对象
2. 对被划入同一审核对象的资料执行严格一致性检查。
3. 对混档、错归类和跨产品资料混入给出风险提示。
V1 至少支持以下输出类型:
### 7.4 系统必须具备“可解释性”
#### 通用问答输出
所有缺失判断、字段抽取和风险预警都应尽量有证据、有来源、有审计记录。
- answer
- references
- confidence
### 7.5 系统必须具备“法规分层引用”
#### 文档审核输出
结合新增公告附件包,系统应能区分并引用不同层级的规则来源,而不是把所有法规依据混成一条说明:
- summary
- issues
- risk_level
- suggestions
- missing_items
- references
1. 资料要求类依据
2. 格式要求类依据
3. 安全和性能基本原则类依据
4. 批准证明文件格式类依据
#### 工单处理输出
### 7.6 系统需要具备“多入口访问能力”
- reply
- category
- priority
- suggested_action
- need_human_review
V1 除 Web 工作台外,还需要实际支持飞书入口能力,使审核任务可以从浏览器工作台扩展到飞书会话和飞书群聊机器人场景。
#### 质量分析输出
### 7.7 系统需要具备“后台治理能力”
- summary
- possible_causes
- evidence
- risk_level
- suggested_actions
- references
除前台审核能力外V1 还需要提供后台管理能力,用于维护规则包、模板库、责任人映射和知识库更新入口。
### 9.8 审计日志
## 8. 后续文档与实现衔接建议
系统需要记录每次 Agent 执行过程。
后续若继续推进设计与开发,建议按如下顺序展开:
审计字段:
1. 先确认待确认事项中的产品范围、回填目标和法规范围。
2. 基于模块需求文档输出设计文档。
3.`config -> scenarios -> documents -> agent_core -> chat -> audit` 顺序推进重构。
4. 同步更新 README、AGENTS 和场景配置命名。
- 日志 ID。
- 场景 ID。
- 用户输入。
- 检索片段。
- 工具调用记录。
- 模型名称。
- 结构化输出。
- 原始输出。
- 执行耗时。
- 创建时间。
## 9. 结论
审计日志页面需要支持
当前 V1 需求已经从“通用 Agent Demo 基座”重构为“注册申报资料审核系统”。后续所有设计、实现和讲解,建议都围绕以下四个关键词展开
- 查看日志列表。
- 查看单条日志详情。
- 展示检索内容。
- 展示工具调用。
- 展示最终输出。
### 9.9 模型适配
系统需要通过统一接口调用大模型,避免模型 API 写死。
V1 模型适配器需要支持:
- 从环境变量读取 API Key。
- 从环境变量读取 Base URL。
- 从环境变量读取 Model Name。
- 支持 OpenAI API 兼容格式,可接入 OpenAI、硅基流动等兼容供应商。
- 支持独立配置 Embedding 模型,用于 RAG 入库和检索。
环境变量示例:
```env
DJANGO_SECRET_KEY=replace-with-a-local-secret-key
DJANGO_DEBUG=true
DJANGO_ALLOWED_HOSTS=*
LLM_API_KEY=your_api_key
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4.1-mini
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=text-embedding-3-small
SCENARIO_CONFIG_DIR=configs
UPLOAD_ROOT=data/uploads
CHROMA_PATH=data/chroma
```
补充说明:
- `EMBEDDING_API_KEY` 为空时可复用 `LLM_API_KEY`
- `EMBEDDING_BASE_URL` 为空时可复用 `LLM_BASE_URL`
- `.env.example` 仅作为模板,不允许放真实密钥。
- 当前 V1 代码会在 settings 初始化时自动读取根目录 `.env`,本地运行与 `pytest` 可复用同一套配置;当前 Docker Compose 配置也通过 `env_file` 读取 `.env`
## 10. Dify 集成策略
V1 不把 Dify 作为核心依赖。
原因:
- 复试现场需要最大程度保证可控。
- 自研 Agent Core 更方便解释架构设计。
- 题目未知时,直接依赖外部平台会增加部署和调试风险。
- Django + Agent Core 已能覆盖第一版演示需求。
系统预留 Agent Engine Adapter 概念,后续可接入 Dify、OpenAI Agents SDK 或其他企业 AI 平台。
V1 默认引擎:
```text
Lightweight Orchestrator
```
后续可扩展:
```text
Dify API Adapter
OpenAI Agents SDK Adapter
LangGraph Adapter
```
## 11. Docker 部署需求
系统需要支持 Docker Compose 一键启动。
基础命令:
```bash
docker compose up --build
```
V1 容器内容:
- Django Web 服务。
- SQLite 数据文件挂载。
- Chroma 数据目录挂载。
- 上传文件目录挂载。
V1 暂不强制引入 PostgreSQL。如果后续需要更正式的部署效果可以在 Docker Compose 中增加 PostgreSQL 服务。
## 12. 推荐项目结构
```text
universal-agent-demo/
manage.py
requirements.txt
Dockerfile
docker-compose.yml
.env.example
README.md
config/
settings.py
urls.py
wsgi.py
asgi.py
apps/
scenarios/
models.py
admin.py
services.py
chat/
views.py
urls.py
forms.py
documents/
models.py
views.py
services.py
audit/
models.py
admin.py
services.py
agent_core/
orchestrator.py
scenario_loader.py
llm_provider.py
tool_registry.py
structured_output.py
rag/
ingest.py
retriever.py
tools/
builtin_tools.py
schemas/
outputs.py
configs/
knowledge_qa.yaml
document_review.yaml
ticket_assistant.yaml
quality_analysis.yaml
risk_audit.yaml
data/
uploads/
chroma/
db.sqlite3
templates/
base.html
home.html
chat/index.html
documents/upload.html
audit/logs.html
static/
css/
js/
```
## 13. 模块需求文档
V1 按 6 个核心模块拆分,具体模块需求见:
| 模块 | 文档 |
|---|---|
| 配置 | `docs/需求分析/3.配置模块需求.md` |
| 场景 | `docs/需求分析/4.场景模块需求.md` |
| 文档 | `docs/需求分析/5.文档模块需求.md` |
| 对话 | `docs/需求分析/6.对话模块需求.md` |
| 审计 | `docs/需求分析/7.审计模块需求.md` |
| 智能核心 | `docs/需求分析/8.智能核心模块需求.md` |
模块总览见:
```text
docs/需求分析/2.模块需求索引.md
```
## 14. V1 验收标准
V1 完成后,需要满足以下验收标准:
- 可以通过 Docker Compose 启动系统。
- 首页可以看到至少 5 个预置场景。
- 可以进入某个场景进行 Agent 对话。
- 可以上传 TXT、Markdown、PDF 或 DOCX 文件。
- 可以将上传文件写入本地知识库。
- Agent 回答时可以使用知识库检索结果。
- 至少支持 2 个内置工具调用。
- Agent 输出可以以结构化方式展示。
- 每次对话都会生成审计日志。
- 审计日志中可以查看用户问题、检索内容、工具调用和最终输出。
- 可以通过环境变量切换大模型 API 地址和模型名。
## 15. 复试改题策略
拿到题目后,优先按以下步骤适配:
1. 判断题目属于哪类模板。
2. 复制最接近的 YAML 场景配置。
3. 修改 Agent 角色、任务目标和输出模板。
4. 上传题目给出的文档或样例数据。
5. 如果题目需要业务计算,则新增一个工具函数。
6. 用 2 到 3 个测试问题验证效果。
7. 演示时重点展示配置、知识库、工具调用、结构化输出和审计日志。
题型映射:
| 题目类型 | 优先模板 |
|---|---|
| SOP 问答 | knowledge_qa |
| 制度问答 | knowledge_qa |
| 文档审核 | document_review |
| 客服处理 | ticket_assistant |
| 质量异常分析 | quality_analysis |
| 财务审核 | risk_audit |
| 采购审核 | risk_audit |
| 合同风险分析 | document_review 或 risk_audit |
## 16. 后续迭代方向
V1 完成后,可以根据时间增加以下能力:
- 支持 Excel 数据分析工具。
- 支持后台页面编辑场景配置,并同步生成或更新 YAML。
- 支持流式输出。
- 支持 OpenAI Agents SDK Adapter。
- 支持 Dify API Adapter。
- 支持 PostgreSQL 部署模式。
- 支持简单登录认证。
- 支持演示数据一键初始化。
1. 文件夹级资料治理
2. 法规目录级完整性校验
3. 统一字段池与跨文档一致性检查
4. 可追溯的风险预警与审计留痕

View File

@@ -0,0 +1,337 @@
# Config 模块需求分析
## 1. 模块定位
`config` 模块负责整个注册申报资料审核系统的运行底座。它不直接承担业务审核、信息抽取或报告生成,但它决定了系统能否以稳定、可控、可讲解的方式启动、读取资料、装配规则、连接模型、管理上传目录并支撑 Docker 演示。
在当前题目下,`config` 不应再被理解为一个普通 Django 配置目录,而应被视为“注册资料审核平台的环境装配层”。
## 2. 模块目标
本模块需要实现以下目标:
1. 让系统能够在本地开发、测试、复试演示和 Docker 演示环境中稳定启动。
2. 为法规资料、上传文件、解析结果、向量库和审计数据提供清晰的路径规范。
3. 将与模型、RAG、规则包、文件解析、导出目录相关的参数统一配置化。
4. 保证测试环境可离线执行,不因为本地真实模型密钥存在而影响回归测试。
5. 为后续从“通用 Demo”切换到“注册申报垂直系统”的配置迁移提供兼容空间。
## 3. 业务背景下的配置需求
### 3.1 本题不是单纯的聊天页项目
题面要求系统处理的是“整包注册资料”,这意味着配置层必须面向文件密集型、规则密集型场景设计。与普通问答系统相比,本题配置上至少多出以下关注点:
- 上传目录不仅要按场景分,还要考虑按项目批次、申报轮次、资料章节分层。
- 规则来源不止一个 YAML可能包括法规目录模板、字段抽取模板、一致性校验规则、风险分级规则以及公告附件原文所对应的结构化法规包。
- 文档解析链路中可能同时使用 `pdfplumber``PyMuPDF`、Word 解析库、OCR 预留能力,因此要有可切换的解析策略配置。
- 审计数据不能只保留“问答日志”,还要能关联具体资料批次和审核任务。
### 3.2 Demo 与真实业务之间要有明确边界
复试时允许 Demo 版做适度简化,但配置层必须明确什么是“演示默认值”,什么是“未来真实化扩展口”。否则系统很容易出现代码里写死演示路径、文档目录、模型名称、法规版本的情况,后续一改题就要整体返工。
## 4. 核心职责
`config` 模块建议承担以下职责:
### 4.1 系统运行配置
负责 Django 基础设置,包括:
- 应用注册
- 模板目录
- 静态资源目录
- 数据库连接
- URL 总入口
- 中间件
- 时区与语言
在本题中,这些基础配置的重点不是“全不全”,而是“是否足够清楚、便于解释”。
### 4.2 业务路径配置
需要统一管理以下路径:
- 原始上传文件根目录
- 法规原文资料目录
- 文本抽取中间结果目录
- 结构化抽取结果目录
- 向量库目录
- 规则文件目录
- 审核报告导出目录
- 审计附件目录
建议不要仅保留 `UPLOAD_ROOT``CHROMA_PATH` 两个路径,而是扩展出更贴合本题的目录配置。
### 4.3 模型与检索配置
负责管理:
- LLM 基础地址、模型名、超时、温度等参数
- Embedding 配置
- RAG 检索开关
- 向量库实现选择
- 本地规则优先 / LLM 辅助优先策略
本题的法规完整性核查、一致性检查,很多内容应以规则为主、模型为辅,因此配置层应支持这种策略切换。
同时,考虑到新增公告附件包中同时存在“注册申报”“变更备案 / 变更注册”“延续注册”等不同业务类型,配置层还应支持按任务类型切换规则集。
### 4.4 环境隔离与安全控制
负责确保:
- 本地 `.env` 中存在真实密钥时,测试仍默认使用 mock provider。
- 日志中不输出 API Key。
- Docker 演示环境能通过 `.env``env_file` 快速启动。
- 解析目录、导出目录不存在时能够自动创建。
## 5. 需要支撑的业务配置项
### 5.1 项目级配置项
建议保留或新增以下项目级环境变量:
- `PROJECT_MODE`
用于标识当前运行模式,如 `demo``review``offline-test`
- `SCENARIO_CONFIG_DIR`
当前场景配置根目录,但语义上应逐步过渡为“任务配置目录”。
- `UPLOAD_ROOT`
原始上传文件保存目录。
- `EXTRACTED_TEXT_ROOT`
文本抽取结果目录。
- `STRUCTURED_OUTPUT_ROOT`
结构化抽取结果目录。
- `REPORT_EXPORT_ROOT`
审核报告导出目录。
- `WORD_EXPORT_ROOT`
Word 回填结果与新生成文档导出目录。
- `WORD_TEMPLATE_ROOT`
可直接报送级 Word 模板库目录。
- `RULE_ADMIN_UPLOAD_ROOT`
规则包、切片文件和人工校订结果上传目录。
- `RULESET_DIR`
法规目录模板、字段规则、风险规则所在目录。
- `REG_SOURCE_DIR`
法规原文与公告附件所在目录。
- `CHROMA_PATH`
向量库目录。
### 5.2 模型级配置项
- `LLM_PROVIDER`
- `LLM_API_KEY`
- `LLM_BASE_URL`
- `LLM_MODEL`
- `LLM_TIMEOUT`
- `LLM_TEMPERATURE`
- `EMBEDDING_API_KEY`
- `EMBEDDING_BASE_URL`
- `EMBEDDING_MODEL`
建议增加:
- `LLM_ENABLE_FOR_RULE_CHECK`
用于控制法规完整性校验时是否允许 LLM 参与解释和兜底。
- `LLM_ENABLE_FOR_FIELD_EXTRACTION`
用于区分“规则抽取优先”还是“模型抽取优先”。
### 5.3 文档处理配置项
- `ALLOWED_UPLOAD_TYPES`
首版至少应支持 `pdf``docx``doc``md``txt`,必要时预留图片。
- `MAX_UPLOAD_SIZE_MB`
控制 Demo 环境下的上传体积。
- `ENABLE_OCR`
是否启用 OCR 兜底。
- `PAGE_COUNT_STRATEGY`
页数统计策略,如 PDF 直接取页数、Word 按分页符或估算策略。
- `DOCX_PARSE_STRATEGY`
例如“仅提取文本”“提取文本和表格”“保留章节层级”。
### 5.4 规则与版本配置项
- `REG_RULESET_VERSION`
当前启用的法规规则版本。
- `REG_TEMPLATE_VERSION`
当前启用的申报目录模板版本。
- `FIELD_SCHEMA_VERSION`
当前产品关键信息字段定义版本。
- `REG_WORKFLOW_TYPE`
当前审核任务所对应的法规流程类型,如 `registration``change``renewal`
- `REG_FORMAT_TEMPLATE_VERSION`
当前启用的批准证明文件格式模板版本。
- `FEISHU_APP_ID`
- `FEISHU_APP_SECRET`
- `FEISHU_BOT_NAME`
- `FEISHU_ENABLE_CHANNEL`
- `FEISHU_CALLBACK_URL`
- `FEISHU_CLI_ENABLED`
- `FEISHU_GROUP_BOT_ENABLED`
- `FEISHU_ACCOUNT_MAPPING_SOURCE`
用于支持飞书应用、机器人入口、事件回调和 CLI / MCP 工具接入。
这三个配置很关键,因为题面中的法规条目和样例材料未来可能变化,系统必须能讲清楚“按哪个版本在审”。
新增公告附件包后,这类版本配置的重要性进一步提高,因为系统不仅要说明“按哪个资料目录模板在审”,还要说明“按哪个公告附件包和哪个文件格式要求在审”。
## 6. 路径与目录结构需求
### 6.1 建议的目录设计
为贴合真实业务,建议在数据目录下形成类似结构:
```text
data/
uploads/
<project_id>/
raw/
normalized/
extracted/
<project_id>/
text/
tables/
metadata/
reports/
<project_id>/
chroma/
rules/
registration/
completeness/
format/
essential-principles/
extraction/
consistency/
risk/
knowledge/
templates/
admin/
rule_updates/
template_library/
responsibility_mapping/
```
这样的结构比“统一丢进某个场景目录”更适合注册资料业务,因为它天然支持:
- 同一产品多轮申报
- 同一项目多批资料
- 原始文件与处理中间结果分离
- 规则版本独立维护
- 注册申报、变更备案、延续注册可分开维护规则包
- Word 输出模板与导出结果独立管理
- 规则切片、人工校订结果和模板库可独立维护
### 6.2 路径命名要求
路径命名应尽量避免中文自由命名直接成为目录主键,建议在展示层保留中文,在系统层使用稳定 ID。例如
- `project_id`
- `submission_batch_id`
- `document_id`
- `ruleset_version`
这样可以避免 Windows 路径、导出路径和 Docker 挂载时出现兼容问题。
## 7. 与其他模块的协作边界
### 7.1 与 Documents 模块的边界
`config` 只负责定义上传目录、抽取目录、导出目录和允许格式,不负责具体解析逻辑。
### 7.2 与 Agent Core 的边界
`config` 负责提供模型参数、向量库参数、规则目录和功能开关,不负责具体提示词、抽取模板和审核规则实现。
### 7.3 与 Audit 模块的边界
`config` 负责定义审计数据落库所需的全局参数和日志脱敏策略,不负责审计内容写入。
## 8. 首版必须满足的功能性要求
### 8.1 启动要求
1. 本地 `python manage.py runserver` 可直接启动。
2. `python manage.py check` 无配置错误。
3. Docker Compose 可根据 `.env` 一键启动。
### 8.2 配置回退要求
1. 未配置 Embedding 专用地址时,允许复用 LLM 地址。
2. 未配置某些导出目录时,系统自动创建。
3. 未配置在线模型时,测试环境默认使用 mock provider。
### 8.3 错误提示要求
当配置错误时,不能只报 Python 异常栈,应尽量给出面向演示者可理解的提示,例如:
- “未配置法规规则目录,无法执行完整性检查”
- “上传目录不可写,请检查 UPLOAD_ROOT”
- “当前禁用了 LLM 抽取,系统将仅按规则执行”
## 9. 非功能要求
### 9.1 可演示性
配置项不能过度复杂。即便底层支持很多开关,复试演示时也应能用少量环境变量说明清楚系统如何启动。
### 9.2 可迁移性
应支持从当前“多场景 Demo”平滑迁移到“注册申报审核专用系统”因此配置命名和目录规划要尽量中性、可扩展。
### 9.3 可测试性
配置必须允许测试注入临时目录、临时规则目录和 Mock Provider确保文档解析、审计、页面测试都不依赖真实外部服务。
## 10. 当前代码基线下的重构建议
### 10.1 保留项
建议保留现有:
- Django 配置组织方式
- `.env` 自动加载方式
- LLM / Embedding 兼容式配置思路
- Docker Compose 基本启动方式
### 10.2 需要调整项
1. 将当前偏“通用 Demo”的命名改造成更贴近注册申报业务的配置语义。
2. 增加抽取结果目录、报告目录、规则目录等配置。
3. 增加法规规则版本与字段 schema 版本配置。
4.`.doc``.docx`、PDF 页数统计和解析策略提供显式配置位。
5. 增加法规原文目录、法规流程类型和文件格式模板版本配置。
6. 增加 Word 导出目录和飞书应用接入相关配置。
7. 增加模板库目录、规则管理目录和责任人映射配置。
## 11. 本模块验收标准
本模块需求完成后,应达到以下验收状态:
1. 一套 `.env` 即可完成本地和 Docker 演示环境启动。
2. 系统目录结构能清楚承载“原始资料、抽取结果、规则、报告、向量库”五类资产。
3. 模型调用、RAG、规则开关、文档处理策略均配置化不散落在业务代码中。
4. 测试环境在有真实 API Key 的机器上仍能稳定离线跑通。
5. 后续模块在引用路径、规则和模型参数时不再写死常量。

View File

@@ -0,0 +1,316 @@
# Scenarios 模块需求分析
## 1. 模块定位
`apps.scenarios` 模块在当前代码中承担“场景列表展示与配置读取”职责。在本题重构后,它不应继续被理解为“多类题型模板市场”,而应升级为:
> 注册申报资料审核任务配置中心
也就是说,系统仍然可以保留配置化入口,但配置的对象不再是“知识问答、工单助手、财务审核”等通用 Agent而是围绕注册申报业务拆分的若干子任务或工作模式。
## 2. 重构目标
本模块需要完成以下目标:
1. 把当前项目首页从“通用 Demo 场景列表”重构为“注册申报任务入口页”。
2. 用配置化方式定义不同审核任务的目标、输入、输出和可调用能力。
3. 支撑后续 Demo 演示时快速切换不同任务视角,而不是频繁改代码。
4. 保持与 `agent_core` 的边界清晰,即场景模块只定义任务,不实现任务逻辑。
结合新增公告材料,场景模块还应承担“法规流程类型选择器”的角色,避免把注册申报、变更备案、延续注册三类任务混成一个入口。
## 3. 为什么本题仍然需要场景模块
虽然本题更像一个垂直系统,但场景模块依然有价值,原因有三:
1. 题面本身包含多个不同子任务:目录汇总、完整性检查、信息提取、回填、一致性核查、风险预警。
2. 复试演示通常需要分步骤展示,若所有能力都硬塞在一个入口,会让页面和结果过于拥挤。
3. 后续需求方很可能会提出“只想先看资料齐套性”“只想看字段抽取”这类细分诉求,配置化任务入口比写死流程更灵活。
## 4. 建议的场景体系
### 4.1 不建议继续使用通用题型命名
现有 `knowledge_qa``ticket_assistant``risk_audit` 等命名与本题关联度太弱。首版建议替换为以下业务导向任务:
1. `registration_overview`
注册资料总览助手
2. `registration_completeness_check`
法规完整性核查助手
3. `registration_field_extraction`
产品关键信息抽取助手
4. `registration_consistency_review`
跨文档一致性核查助手
5. `registration_risk_report`
合规风险预警助手
首版仍建议以 `registration_*` 作为主任务族,但应在配置语义上预留后续扩展族,例如:
- `change_registration_*`
- `renewal_registration_*`
### 4.2 是否需要多个场景还是一个总场景
建议首版保留“一个主场景 + 若干子任务配置”的思路:
- 页面上可以展示多个任务入口。
- 底层可共享同一个项目资料池和统一字段池。
- Demo 演示时可以先点“完整性检查”,再点“一致性核查”,展示系统分工明确。
## 5. 场景配置应包含的内容
每个任务配置文件建议至少包含以下信息:
### 5.1 基础标识
- `id`
- `name`
- `description`
- `icon` 或展示标签
- `sort_order`
### 5.2 任务目标
- 当前任务处理什么问题
- 适用输入资料范围
- 输出结果形式
例如,完整性检查任务的目标可以表述为:
> 根据 NMPA 注册申报资料要求,对当前项目资料包进行章节点齐套性检查、缺失项识别和待补清单生成。
### 5.3 输入约束
- 是否依赖已上传文件
- 是否依赖已入库文本
- 是否需要指定资料章节点
- 是否允许只针对选中文档执行
### 5.4 规则和工具绑定
- 需要的规则集
- 允许调用的工具列表
- 是否启用 RAG
- 是否启用统一字段池
### 5.5 输出模板
- 结构化结果类型
- 页面展示字段
- 是否生成报告
- 是否触发审计日志
## 6. 页面层需求
### 6.1 首页需要表达的内容
首页不应只展示“这是几个 YAML 场景”,而应展示当前系统已支持的注册审核任务。建议每个任务卡片至少包含:
- 任务名称
- 任务目标
- 典型输入
- 典型输出
- 依赖资料条件
- 当前是否可执行
### 6.2 可执行状态判断
场景模块应能给出任务是否可执行的判定。例如:
- 未上传任何资料:完整性检查可执行,但只会提示无资料。
- 已上传未入库目录汇总可执行RAG 相关任务应提示需先入库。
- 规则文件缺失:完整性检查场景显示不可执行或部分能力不可用。
这类状态说明非常适合复试演示,因为它体现了系统不是“死调用大模型”,而是有明确任务前置条件。
## 7. 配置驱动的业务能力
### 7.1 目录汇总任务
该任务侧重:
- 扫描文件夹
- 统计文件数、页数、章节点
- 生成目录总览表
配置上需要指定:
- 可读取的资料范围
- 输出字段,如文件名、文件类型、所属章节、页数、状态
### 7.2 完整性核查任务
该任务侧重:
- 读取法规模板
- 比对当前资料是否齐套
- 识别缺失文件、错放文件、待人工确认项
结合公告附件包,该任务还应明确区分三层依据:
1. 注册申报资料要求及说明
2. 批准证明文件格式要求
3. 安全和性能基本原则清单
配置上需要指定:
- 对应法规规则版本
- 缺失项风险等级策略
- 默认输出清单字段
### 7.3 字段抽取任务
该任务侧重:
- 从说明书、申请表、产品列表等材料提取产品名称、靶标、适用范围、规格、储存条件、性能信息等
- 形成统一字段池
配置上需要指定:
- 目标字段 schema
- 字段来源优先级
- 是否允许 LLM 兜底抽取
### 7.4 一致性核查任务
该任务侧重:
- 比对统一字段池中的冲突项
- 输出冲突来源、冲突内容和处理建议
配置上需要指定:
- 必须完全一致的字段
- 默认按完全一致处理的字段
- 风险分级规则
### 7.5 风险预警任务
该任务侧重:
- 汇总前述各任务结果
- 形成综合风险清单
- 给出整改建议和优先级
配置上需要指定:
- 风险合并策略
- 高中低风险判定口径
- 责任归属字段
- 飞书通知目标字段
### 7.6 法规依据展示任务或结果区
基于新增公告材料,建议在任务配置中增加法规依据展示能力,至少能让系统说明:
1. 当前任务对应的是哪类法规流程。
2. 使用了哪份资料要求说明。
3. 使用了哪份格式要求或批准证明文件模板。
## 8. 与原始材料对应的任务设计要点
### 8.1 要能体现“资料结构化目录”的任务价值
`CH1.2 监管信息目录.docx` 已经给出一个非常适合 Demo 的目录样例,场景模块应支持把“监管目录核对”配置成单独任务,而不是只能在自由聊天中触发。
结合新增公告材料,这个任务还应能够说明当前目录核对所引用的是“资料要求说明”而不是任意自由目录。
### 8.2 要能体现“跨文档冲突识别”的任务价值
当前材料中的产品名称冲突非常典型,但结合最新确认,系统目标是通用试剂盒注册审核智能体,而不是只围绕某一个固定产品。因此,一致性核查任务应强调:
1. 先界定审核范围,再执行严格一致性检查。
2. 对同一审核范围内的字段按完全一致规则比对。
3. 对疑似混档资料单独输出风险提示。
### 8.3 要能体现“历史申报沟通说明”的任务价值
`CH1.9` 所反映的历史受理、撤回、替换临床机构等内容,适合在风险预警任务中作为“历史事项复核”子项展示。
### 8.4 要为飞书接入预留并实现任务入口语义
结合当前确认结果,场景模块建议为飞书访问统一维护任务标识和简短描述,便于后续:
1. 在飞书机器人中按任务 ID 触发。
2. 在飞书侧展示任务摘要和结果链接。
3. 将“通知责任人”与任务类型建立映射。
4. 支持群聊机器人中的任务选择与结果查看。
如果后续采用飞书开放平台的 Agent 接入路线,还应确保这些任务标识可以映射到飞书消息指令、评论触发或会话菜单项。
## 9. 场景模块与其他模块的边界
### 9.1 与 Chat 模块的边界
场景模块定义任务卡片和执行入口Chat 模块负责用户进入任务后的具体交互与结果展示。
### 9.2 与 Documents 模块的边界
场景模块不负责解析文档,只负责声明“当前任务依赖哪些资料和结构化资产”。
### 9.3 与 Agent Core 的边界
场景模块定义任务目标、输出模板、可用工具和规则集Agent Core 负责真正执行审核与抽取。
## 10. 建议的配置文件组织方式
建议将当前 `configs/` 目录从“题型模板”调整为“注册任务配置”,例如:
```text
configs/
registration_overview.yaml
registration_completeness_check.yaml
registration_field_extraction.yaml
registration_consistency_review.yaml
registration_risk_report.yaml
```
如果后续希望再进一步清晰化,也可以拆分为:
```text
configs/registration/
overview.yaml
completeness.yaml
extraction.yaml
consistency.yaml
risk_report.yaml
```
但首版不必为了目录优雅而过度重构。
## 11. 首版验收要求
本模块完成后,应达到以下效果:
1. 首页展示的任务名称、描述和本题业务强相关。
2. 每个任务的输入前提、输出类型和所依赖规则清晰可见。
3. 任务配置变更主要通过 YAML 完成,不需要频繁改 Python 代码。
4. 至少能清楚区分“目录汇总、完整性检查、字段抽取、一致性核查、风险预警”五类任务。
## 12. 当前代码基线下的重构建议
### 12.1 建议保留
- 通过配置文件驱动场景展示的思路
- 场景读取失败时对首页做容错提示的机制
### 12.2 建议替换
1. 将“适用题型”文案替换为“任务适用资料/适用阶段”。
2. 将当前通用场景命名替换为注册审核任务命名。
3. 增加任务前置条件和执行依赖展示。
4. 增加“是否依赖字段池”“是否依赖法规模板”等配置位。
## 13. 本模块在复试讲解中的价值
如果场景模块重构到位,复试时可以很自然地说明:
1. 为什么不是把所有能力塞进一个聊天机器人。
2. 为什么用配置驱动的任务入口更适合监管审核场景。
3. 如何在不推翻现有系统结构的情况下,快速从通用 Demo 切换到具体业务题。
这会直接增强整套系统的“工程化思维”观感。

View File

@@ -1,85 +0,0 @@
# 模块需求文档索引
本文档用于汇总 Universal Agent Demo Framework V1 的模块拆分和需求文档位置。
## 1. 模块拆分原则
V1 按 6 个核心模块拆分:
```text
config
apps.scenarios
apps.documents
apps.chat
apps.audit
agent_core
```
拆分原则:
- Django Apps 负责业务外壳。
- Agent Core 负责 AI 能力。
- RAG、工具调用、模型适配不直接写进 View。
- 第一版不做复杂权限、多租户和完整工作流。
- 模块数量保持克制,方便复试前快速改题。
## 2. 模块文档列表
| 模块 | 文档 | 说明 |
|---|---|---|
| 配置 | `3.配置模块需求.md` | Django 项目配置、环境变量、部署配置 |
| 场景 | `4.场景模块需求.md` | 场景模板、场景配置、场景列表 |
| 文档 | `5.文档模块需求.md` | 文件上传、文件管理、RAG 入库入口 |
| 对话 | `6.对话模块需求.md` | 对话页面、Agent 调用、结果展示 |
| 审计 | `7.审计模块需求.md` | 审计日志、检索记录、工具调用记录 |
| 智能核心 | `8.智能核心模块需求.md` | RAG、工具、模型调用、结构化输出、编排 |
## 3. 模块依赖关系
```text
apps.chat
|-- depends on apps.scenarios
|-- depends on apps.audit
|-- calls agent_core
apps.documents
|-- depends on apps.scenarios
|-- calls agent_core.rag.ingest
apps.audit
|-- stores result from apps.chat / agent_core
agent_core
|-- reads scenario config object
|-- uses Chroma
|-- uses LLM Provider
|-- uses Tool Registry
```
## 4. 推荐开发顺序
建议按以下顺序开发:
1. Config 模块:保证项目可启动。
2. Scenarios 模块:展示 5 个预置场景。
3. 智能核心最小闭环:输入问题,通过 OpenAI 兼容模型接口返回结构化结果。
4. Chat 模块:页面调用 Agent Core。
5. Audit 模块:记录每次对话。
6. Documents 模块:上传文档。
7. Agent Core RAG文档入库和检索。
8. Agent Core 工具系统:增加内置工具。
9. Docker一键启动。
## 5. V1 完成标准
模块文档全部完成后V1 的实现应满足:
- 系统可以启动。
- 首页可以看到 5 个场景。
- 可以进入场景对话。
- 可以上传文档。
- 可以触发 RAG 入库。
- Agent 可以返回结构化输出。
- 工具调用和引用来源可以展示。
- 每次对话都有审计日志。
- Docker Compose 可以一键启动。

View File

@@ -0,0 +1,376 @@
# Documents 模块需求分析
## 1. 模块定位
`apps.documents` 是本题最关键的业务入口之一。对于“试剂盒临床注册文件准备与审核”场景,它不只是一个上传附件页面,而是:
> 注册申报资料治理中心
该模块需要承接从资料接收、文件识别、内容抽取、章节点归类、页数统计、入库索引到状态反馈的完整过程。
## 2. 业务目标
本模块需要支撑以下真实业务目标:
1. 接收注册申报资料包中的各类文件。
2. 建立每份文件的结构化档案。
3. 自动形成目录汇总和页数统计结果。
4. 为法规完整性核查和一致性核查提供可靠的文档底座。
5. 为抽取、回填、审计和导出提供统一的文档主数据。
结合新增公告材料,本模块还应承担“法规原文资料资产管理”的基础职责,即把上传的业务资料与平台内置的法规依据材料区分管理。
## 3. 为什么 Documents 模块是本题核心
题面第一条就要求“自动汇总注册申报文件夹中的所有文件及页数”,第二条要求“对照 NMPA 法规要求核查文件完整性”。这两个要求都建立在一个前提上:
系统必须先“看懂当前资料包里到底有什么”。
因此Documents 模块不是配角,而是全流程的第一责任模块。
## 4. 核心职责
### 4.1 原始文件接收
支持上传和保存:
- PDF
- DOCX
- DOC
- MD
- TXT
必要时为后续 OCR 或图片扫描件预留扩展位。
除用户上传的申报资料外,系统还需要支持管理平台内置法规资料,例如:
- 注册申报资料要求及说明
- 批准证明文件格式要求
- 安全和性能基本原则清单
- 注册证 / 变更注册(备案)文件格式
### 4.2 文件基础信息管理
每份资料至少要记录:
- 文件 ID
- 原始文件名
- 文件类型
- 文件大小
- 上传时间
- 所属项目 / 批次
- 所属任务或场景
- 当前处理状态
对于法规资料,建议额外记录:
- 法规类型
- 法规流程类型
- 版本来源
- 是否为系统内置规则依据
### 4.3 页数统计与目录归属
系统要能为每份文件识别:
- 页数
- 章节归属
- 资料名称
- 是否匹配法规目录项
这部分是题面要求中的显式能力,不能只靠 Chat 页面临时回答。
### 4.4 文本与表格抽取
为后续规则比对和字段提取,需要抽取:
- 正文文本
- 标题层级
- 表格内容
- 可能的关键信息段落
例如 `目标产品说明书.docx` 中大量关键信息位于结构化段落和表格中,若只做粗暴全文提取,会显著影响抽取质量。
### 4.5 入库索引
对适合检索的内容建立索引,供 `agent_core` 的 RAG 或规则定位使用。
对法规原文资料,建议单独建立“法规知识索引”,切片时优先保留以下结构语义:
- 所属法规文档
- 适用流程类型
- 章 / 条 / 清单项编号
- 模板字段或格式要求类型
### 4.6 状态反馈与异常处理
文件处理流程要有明确状态,例如:
- 已上传
- 已解析
- 已入库
- 解析失败
- 待人工确认
不能只记录一个“成功 / 失败”。
## 5. 注册资料业务下的文件模型需求
### 5.1 现有上传模型的不足
当前 `UploadedDocument` 更像一个通用文档记录,适合简单 RAG Demo但对本题不够。至少还缺少以下业务字段
- `project_id``submission_batch_id`
- `chapter_code`
- `chapter_name`
- `document_code`
- `declared_document_name`
- `page_count`
- `source_version`
- `extraction_status`
- `index_status`
- `consistency_status`
- `completeness_match_status`
### 5.2 建议新增的业务语义字段
#### 5.2.1 章节点字段
需要支持类似:
- `CH1.2`
- `CH1.4`
- `CH1.5`
- `CH1.11.5`
这样系统才能把法规模板、样例目录和实际文件真正对齐。
#### 5.2.2 文档类别字段
例如:
- 监管信息
- 综述资料
- 非临床资料
- 临床评价资料
- 产品说明书和标签样稿
- 质量管理体系文件
#### 5.2.3 处理质量字段
建议记录:
- 是否成功提取文本
- 是否成功提取表格
- 是否页数统计可信
- 是否疑似扫描件
- 是否需要 OCR
这些字段会直接影响后续审核可信度。
#### 5.2.4 规则来源字段
建议增加:
- `source_role`
区分“业务申报资料”与“法规依据资料”。
- `workflow_type`
区分 `registration``change``renewal` 等流程类型。
- `format_template_type`
标记该文件是否属于批准证明文件格式模板。
## 6. 关键业务流程需求
### 6.1 文件上传流程
用户上传文件后,系统应完成:
1. 基础校验:格式、大小、文件名合法性。
2. 保存原始文件。
3. 创建文档记录。
4. 返回上传结果与下一步动作提示。
如果是批量导入,系统还应支持一次性上传多个资料。
### 6.2 文件识别与归类流程
上传后,系统应尽量自动识别文件属于哪个章节点。识别依据可以包括:
- 文件名中的章节点编码
- 标题中的资料名称
- 正文中出现的标准标题
- 用户手工选择的类别
如果自动识别不确定,应标记为“待人工确认”,而不是强行归类。
对于法规资料,还应进一步识别其所属层级,例如:
1. 资料要求说明
2. 格式要求说明
3. 安全和性能基本原则
4. 批准证明文件格式
### 6.3 页数统计流程
页数统计是本题显式要求,需支持:
- PDF 精确页数统计
- Word 文件页数估算或格式解析策略
- 目录页码与实际文件页数比对
即便首版不能对所有 Word 做精确页数恢复,也需要在需求上明确“统计可信度”和“估算标识”。
### 6.4 文本抽取与索引流程
系统应按文档类型采用不同策略:
- PDF优先文本解析必要时 OCR 兜底
- DOCX提取段落和表格
- DOC首版可使用兼容方式提取正文异常时提醒
- MD/TXT直接读取
抽取成功后,生成:
- 全文文本
- 标题/章节结构
- 表格结构
- 可供索引的切片
### 6.5 目录汇总输出流程
Documents 模块应能直接输出一份“资料目录总览”,字段建议包括:
- 文件名
- 资料名称
- 章节点
- 文件类型
- 页数
- 上传时间
- 处理状态
- 是否命中法规模板
这份目录总览既可作为页面列表,也可作为后续报告输入。
## 7. 与题面和样例资料的强关联需求
### 7.1 要能识别目录型文档
`CH1.2 监管信息目录.docx`,它本身不是普通资料正文,而是全章目录。系统需要把这类文件识别为“目录类文档”,并作为后续完整性比对的重要基准。
### 7.2 要能识别声明类文档
如:
- `CH1.11.1 符合标准的清单.docx`
- `CH1.11.5 真实性声明.docx`
- `CH1.11.6 符合性声明.docx`
这些文件看起来篇幅短但在法规齐套性里往往是必需项Documents 模块需要保留它们的业务属性,而不是简单按长度或内容量弱化其价值。
### 7.3 要能识别历史沟通说明类文档
`CH1.9 产品申报前沟通的说明.doc` 体现出历史申报背景和监管沟通信息,这类文件在合规审查中重要性很高,应单独分类标记。
### 7.4 要能区分业务资料与法规依据资料
结合新增公告材料Documents 模块应把以下两类材料明确分开管理:
1. 待审核的业务申报资料
2. 用于审核的法规依据与模板资料
否则后续在索引、引用和一致性检查时,容易把法规模板错误混入业务资料集合。
## 8. 列表页与上传页需求
### 8.1 文档列表页需求
文档列表页不应只是“文件上传记录”,而应成为资料治理面板。建议展示:
- 文件名
- 章节点
- 资料名称
- 页数
- 所属项目 / 批次
- 解析状态
- 入库状态
- 风险提示
- 最后更新时间
### 8.2 上传页需求
上传页应支持:
- 选择所属项目或申报批次
- 选择任务类型或章节点
- 上传单文件或多文件
- 上传后立即触发解析或稍后批量处理
如果首版只保留单文件上传,也应在需求中明确“后续需要支持批量导入资料包”。
## 9. 与其他模块的协作边界
### 9.1 与 Scenarios 模块
Scenarios 负责定义当前任务需要什么资料Documents 负责把资料真正落地并结构化。
### 9.2 与 Agent Core 模块
Agent Core 负责对文档内容做审核与抽取Documents 提供可靠的原始内容、切片和元数据。
### 9.3 与 Audit 模块
Documents 不负责审计结论,但应为审计提供文档 ID、处理过程和失败原因等基础事实。
## 10. 异常与边界情况
本模块必须考虑以下情况:
1. 文件存在但正文为空。
2. 文件格式伪装,例如后缀为 `.docx` 但内容异常。
3. Word / PDF 无法正常抽取文本。
4. 文件内容与文件名章节点不一致。
5. 同一资料重复上传多个版本。
6. 同一批次中混入其他产品资料。
第 6 点尤其重要,因为当前样例材料已经体现出不同产品信息混杂的问题。
此外,还应避免把法规依据资料误当成业务申报资料写入同一业务索引集合。
## 11. 首版建议的可交付结果
首版建议 Documents 模块至少能产出三类结果:
1. 文档主数据列表
2. 文档解析结果
3. 目录汇总表
其中目录汇总表是本题最关键的页面成果之一,建议作为可单独展示的功能输出。
## 12. 当前代码基线下的重构建议
### 12.1 可以保留的部分
- 上传记录模型的基本思路
- 文档列表与上传页的页面骨架
- 入库和失败提示机制
### 12.2 需要增强的部分
1. 从“文档上传记录”升级为“注册资料记录”。
2. 增加章节点、页数、资料名称、项目批次等字段。
3. 增加表格抽取和目录类文件识别。
4. 增加文档归类与页数统计能力。
5. 增加重复版本识别和疑似混档识别。
6. 增加法规资料类型识别与业务资料 / 法规资料隔离管理。
## 13. 验收标准
本模块验收时,应至少满足:
1. 能上传并管理题目中涉及的 Word、PDF、文本类资料。
2. 能为每份资料建立结构化记录。
3. 能输出文件目录与页数汇总结果。
4. 能为后续完整性核查和一致性核查提供可靠输入。
5. 出现解析失败时,页面有明确可理解的错误提示。

View File

@@ -1,115 +0,0 @@
# 配置模块需求文档
## 1. 模块定位
Config 模块是 Django 项目的基础配置模块,负责系统启动、路由装配、环境变量读取、静态资源、文件存储、数据库、日志和第三方组件配置。
该模块不承载业务逻辑,只负责让系统稳定启动,并为其他模块提供统一运行环境。
## 2. 模块目标
- 支持本地开发和 Docker 部署两种运行方式。
- 支持通过环境变量切换模型 API、Embedding API、调试模式和文件路径。
- 统一注册 Django Apps、模板目录、静态资源目录和上传目录。
- 提供系统级 URL 路由入口。
- 为后续扩展 PostgreSQL、Redis、Celery 等组件预留配置空间。
## 3. 职责边界
### 3.1 负责
- Django `settings.py` 配置。
- Django `urls.py` 总路由配置。
- WSGI / ASGI 启动配置。
- 环境变量读取。
- SQLite 默认数据库配置。
- 静态文件和上传文件配置。
- Chroma 本地持久化目录配置。
- LLM 与 Embedding 相关环境变量配置。
### 3.2 不负责
- 不处理具体 Agent 业务逻辑。
- 不解析场景 YAML。
- 不处理文件入库。
- 不直接调用大模型。
- 不保存审计日志。
## 4. 配置项需求
系统至少需要支持以下环境变量:
| 配置项 | 默认值 | 说明 |
|---|---|---|
| `DJANGO_SECRET_KEY` | `dev-secret-key` | Django 密钥 |
| `DJANGO_DEBUG` | `true` | 是否开启调试模式 |
| `DJANGO_ALLOWED_HOSTS` | `*` | 允许访问的主机 |
| `DATABASE_URL` | 空 | 预留配置V1 默认 SQLite不要求解析该配置 |
| `LLM_API_KEY` | 空 | 大模型 API Key |
| `LLM_BASE_URL` | `https://api.openai.com/v1` | OpenAI 兼容接口地址,可接入 OpenAI、硅基流动等兼容服务 |
| `LLM_MODEL` | `gpt-4.1-mini` | 默认模型名称 |
| `EMBEDDING_API_KEY` | 空 | Embedding API Key为空时可复用 `LLM_API_KEY` |
| `EMBEDDING_BASE_URL` | 空 | Embedding OpenAI 兼容接口地址;为空时可复用 `LLM_BASE_URL` |
| `EMBEDDING_MODEL` | `text-embedding-3-small` | 默认 Embedding 模型名称 |
| `CHROMA_PATH` | `data/chroma` | Chroma 持久化目录 |
| `UPLOAD_ROOT` | `data/uploads` | 上传文件目录 |
| `SCENARIO_CONFIG_DIR` | `configs` | 场景配置目录 |
补充要求:
- `.env.example` 仅作为模板文件,不得写入真实密钥。
- 本地直接执行 `python manage.py runserver` 时,应自动读取根目录 `.env`
- Docker 运行时可通过 `env_file` 或容器环境变量注入同一组配置;当前仓库默认由 Compose 读取 `.env`
## 5. 目录需求
系统启动时需要保证以下目录存在:
```text
data/
uploads/
chroma/
db.sqlite3
configs/
```
如果目录不存在V1 可以在初始化脚本或启动流程中创建。
## 6. 路由需求
总路由需要聚合以下模块路由:
| 路径 | 模块 | 用途 |
|---|---|---|
| `/` | `apps.scenarios` | 首页和场景列表 |
| `/chat/` | `apps.chat` | Agent 对话 |
| `/documents/` | `apps.documents` | 文件上传和文档管理 |
| `/audit/` | `apps.audit` | 审计日志查看 |
| `/admin/` | Django Admin | 后台管理 |
## 7. 启动需求
本地启动:
```bash
python manage.py migrate
python manage.py runserver
```
说明:上述命令执行前,应先准备好根目录 `.env`;当前 V1 代码会在启动时自动加载该文件。
Docker 启动:
```bash
docker compose up --build
```
## 8. 验收标准
- 项目可以通过 `python manage.py runserver` 启动。
- 项目可以通过 `docker compose up --build` 启动。
- `/admin/` 可以访问。
- 首页 `/` 可以访问。
- 环境变量可以覆盖默认 LLM 与 Embedding 配置。
- 上传目录和 Chroma 目录有明确配置。

View File

@@ -0,0 +1,306 @@
# Chat 模块需求分析
## 1. 模块定位
`apps.chat` 在当前项目中是用户输入问题并查看 Agent 返回结果的页面。对于本题,它依然是核心交互入口,但定位需要从“自由问答页面”升级为:
> 注册申报审核工作台
也就是说Chat 模块不只是让用户随便问一句话,而是要承接“选择任务、限定资料范围、发起审核、查看结构化结论、查看证据和建议”的完整操作流程。
## 2. 模块目标
本模块需要实现以下目标:
1. 为注册审核人员提供统一的任务执行入口。
2. 让用户能明确知道自己当前在执行哪类审核任务。
3. 让系统输出不仅有自然语言回答,还有结构化结论、引用证据、回填字段、风险建议。
4. 保证结果可追溯、可解释、可复核,而不是只给一个“大模型说了什么”。
5. 支持通过 Web 工作台与飞书入口两类方式访问智能体、触发审核和查看结果,并保证核心流程可在飞书内闭环完成。
## 3. 为什么 Chat 模块仍然必要
虽然本题也可以做成一组固定报表,但保留 Chat / 工作台交互有三个价值:
1. 复试演示更直观,容易展示 Agent 的编排能力。
2. 用户可以用自然语言提出临时核查要求,例如“只检查 CH1 监管信息”“比较说明书和申请表中的产品名称是否一致”。
3. Chat 页面可以作为多个任务的统一结果承载层,而不需要为每个任务都单独写一套复杂页面。
## 4. 交互定位建议
### 4.1 不建议保持纯聊天式体验
如果只保留一个输入框,让用户手工描述所有操作,体验会过于依赖 prompt不像一个业务系统。
建议采用“任务工作台 + 辅助对话”的模式,页面中同时提供:
- 当前任务名称
- 输入问题框
- 资料范围选择
- 建议提问模板
- 结构化结果区
- 证据引用区
- 风险列表区
- 审计入口
### 4.2 建议突出“任务上下文”
用户进入页面后,应明确看到:
- 当前任务是什么
- 当前使用了哪些资料
- 当前是否启用了法规规则
- 当前是否启用了字段池 / RAG / 工具
这对复试讲解非常重要,因为它能体现系统是“受控执行”而不是“随便问模型”。
## 5. 典型使用场景
### 5.1 发起完整性检查
用户输入类似:
- “检查当前上传资料是否满足第 1 章监管信息要求”
- “列出 CH1 缺失文件和风险等级”
系统返回:
- 已识别文件数
- 命中法规目录项
- 缺失项清单
- 错放项清单
- 处理建议
### 5.2 发起字段抽取
用户输入类似:
- “从说明书和产品列表抽取产品名称、规格、靶标、适用范围、储存条件”
系统返回:
- 统一字段表
- 字段来源文档
- 置信度或待确认状态
- 可回填目标字段
### 5.3 发起一致性核查
用户输入类似:
- “检查申请表、说明书、产品列表里的产品名称和规格是否一致”
系统返回:
- 一致字段
- 冲突字段
- 冲突来源
- 判定依据
- 建议处理动作
### 5.4 发起综合风险报告
用户输入类似:
- “生成本批申报资料的综合风险预警”
系统返回:
- 风险摘要
- 高优先级问题
- 待补文件
- 需人工确认事项
- 建议整改顺序
## 6. 输入层需求
### 6.1 用户输入类型
本模块应支持三类输入:
1. 自然语言问题
2. 结构化参数选择
3. 资料范围选择
### 6.2 结构化参数选择
建议用户在页面上可选:
- 审核任务类型
- 资料章节点范围
- 指定文档范围
- 输出模式
输出模式可包括:
- 简洁结论
- 结构化清单
- 回填字段视图
- 风险报告视图
### 6.3 建议提示词模板
页面上可给出快捷操作示例,例如:
- “汇总当前资料目录及页数”
- “检查 CH1 监管信息是否齐套”
- “抽取说明书中的核心产品信息”
- “检查说明书与申请表是否一致”
这样能降低演示时的自由输入风险。
### 6.4 飞书访问入口
飞书交互不应只是消息转发,而应支持在飞书内完成关键流程。交互设计上至少应纳入以下能力:
1. 通过飞书消息或群聊机器人入口触发某个审核任务。
2. 在飞书内完成任务选择、结果查看和责任人通知。
3. 支持手动维护后的责任人 / 飞书账号映射生效。
飞书开放平台接入路线中,群聊机器人属于 Demo 必备入口;文档评论区 `@bot` 或应用会话能力可作为后续扩展。
## 7. 输出层需求
### 7.1 自然语言结论
仍然需要保留总体回答,用于快速概括结果。
### 7.2 结构化结果
结构化结果是本题的重点,建议至少支持以下几类:
- 目录汇总结果
- 完整性检查结果
- 字段抽取结果
- 一致性核查结果
- 风险预警结果
### 7.3 引用证据
每个关键结论尽量附带来源,例如:
- 来源文档名
- 来源章节
- 引用片段
- 引用页码或位置
对于法规完整性核查,还应尽量附带命中的法规条目或模板条目。
### 7.4 回填结果展示
对于“自动填写至目标文件”的题面要求Chat 页面建议至少支持:
- 展示待回填字段
- 展示字段值
- 展示来源文档
- 展示是否存在冲突
- 展示是否已生成新的 Word 文档
- 展示导出入口
输出结果不仅要展示回填数据,还应明确展示“已按模板生成可直接报送版 Word”及其导出入口。
### 7.5 飞书端结果展示
飞书端至少应能展示:
- 当前任务名称
- 结果摘要
- 风险等级
- 关键缺失项或冲突项
- 责任人通知结果
- Web 详情页或导出文件链接
### 7.6 风险提示
风险输出不应混在普通回答里,建议单独展示:
- 风险等级
- 风险类型
- 涉及文档
- 问题描述
- 建议动作
- 是否需人工复核
## 8. 页面展示要求
### 8.1 结果要适合讲解
复试场景下,页面展示必须清楚,不适合只显示一个 JSON。建议将结果拆成几个清晰区块
- 执行摘要
- 结构化结果
- 证据引用
- 工具调用记录
- 风险预警
- 审计入口
### 8.2 异常提示要业务化
不能只提示“调用失败”。应该尽量说明:
- 当前无可用文档
- 资料未完成入库
- 未找到目标章节点资料
- 字段抽取结果存在冲突,需人工确认
- 法规规则未配置,无法执行完整性检查
### 8.3 支持“只看选中文档”
当前测试已覆盖按文档 ID 传递范围,这在本题里非常有用。因为注册审核人员往往只想检查某一章或某几个文件。
## 9. 结果可信度与人工复核
本题不应把系统塑造成“自动替代注册专员”的黑盒工具,因此 Chat 页面必须支持“需人工复核”的输出状态。
适合标记人工复核的情况包括:
1. 文档抽取失败或疑似扫描件。
2. 字段在不同文档中出现冲突。
3. 章节归类不确定。
4. 规则无法直接判断是否缺失。
5. 审核范围界定不清,无法确认哪些资料属于同一项目批次。
## 10. 与其他模块的边界
### 10.1 与 Scenarios 模块
Scenarios 定义任务入口Chat 承担任务执行界面。
### 10.2 与 Documents 模块
Documents 提供资料和元数据Chat 负责让用户选择资料并展示结果。
### 10.3 与 Agent Core 模块
Agent Core 生成审核结果Chat 只负责参数组织和结果呈现,不负责规则实现。
### 10.4 与 Audit 模块
Chat 是大多数审计记录的触发入口,应把每次关键执行与审计日志关联起来。
## 11. 当前代码基线下的重构建议
### 11.1 建议保留
- 用户输入表单和提交流程
- 结构化结果、引用片段、工具调用展示能力
- 审计入口跳转
- 选中文档范围传递机制
### 11.2 建议增强
1. 从“通用对话页”升级为“注册审核工作台”。
2. 增加任务上下文展示和建议操作模板。
3. 增加字段回填视图和风险清单视图。
4. 增加资料范围、章节点范围选择。
5. 增加人工复核标记的展示。
## 12. 验收标准
本模块验收时,应达到以下状态:
1. 用户能清楚知道当前执行的是哪项注册审核任务。
2. 结果输出同时包含自然语言总结和结构化内容。
3. 能查看引用证据、风险项和工具调用过程。
4. 能基于选中文档或章节点做定向审核。
5. 对失败、冲突和不确定情况给出清楚的人工复核提示。

View File

@@ -1,143 +0,0 @@
# 场景模块需求文档
## 1. 模块定位
Scenarios 模块负责管理业务 Agent 场景,是整个平台快速适配未知复试题的核心入口。
场景定义需要尽量配置化,避免把具体业务逻辑写死在 Django View 或 Agent Core 中。
## 2. 模块目标
- 读取预置场景配置。
- 展示可用业务 Agent 列表。
- 提供场景详情。
- 为 Chat 模块提供当前场景的完整配置。
- 以 YAML 配置文件作为 V1 场景唯一事实来源。
## 3. 职责边界
### 3.1 负责
- 场景模板定义。
- 场景配置文件读取。
- 场景元信息展示。
- 场景启用/禁用状态。
- 场景与文档、审计日志的关联关系。
### 3.2 不负责
- 不执行 Agent 对话。
- 不直接处理 RAG 检索。
- 不直接调用工具。
- 不直接调用大模型。
- 不解析结构化输出。
## 4. 场景模板需求
V1 预置 5 类场景模板:
| 模板 ID | 模板名称 | 适用题型 |
|---|---|---|
| `knowledge_qa` | 知识库问答助手 | SOP、制度、客服知识库、内部文档问答 |
| `document_review` | 文档审核助手 | 合同审核、制度审核、材料合规检查 |
| `ticket_assistant` | 工单处理助手 | 客服工单、售后工单、运维工单 |
| `quality_analysis` | 质量异常分析助手 | 生产质量、缺陷分析、原因定位 |
| `risk_audit` | 风险审核助手 | 财务审核、采购审核、报销审核、合同风险 |
## 5. 场景配置字段
场景配置文件使用 YAML。V1 的后台管理只管理上传文件、审计日志和示例业务数据等外围数据,不作为场景配置入口。
必填字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | string | 场景唯一标识 |
| `name` | string | 场景名称 |
| `description` | string | 场景说明 |
| `agent.role` | string | Agent 角色 |
| `agent.goal` | string | Agent 目标 |
| `agent.instructions` | list[string] | Agent 指令 |
| `agent.system_prompt` | string | 可选字段;配置后优先作为系统提示词 |
| `rag.enabled` | boolean | 是否启用 RAG |
| `tools` | list[string] | 可用工具列表 |
| `output.type` | string | 输出模板类型 |
| `audit.enabled` | boolean | 是否记录审计 |
示例:
```yaml
id: document_review
name: 文档审核助手
description: 检查合同、制度或 SOP 中的风险点和缺失项
agent:
role: 文档审核专家
goal: 根据审核规则和知识库内容输出结构化审核意见
system_prompt: ""
instructions:
- 只基于用户提供文档和知识库进行判断
- 不确定的问题必须标记为需人工复核
- 输出必须包含风险等级和修改建议
rag:
enabled: true
collection: document_review
top_k: 5
tools:
- check_required_fields
output:
type: document_review_report
audit:
enabled: true
```
## 6. 页面需求
### 6.1 场景列表页
路径:`/`
展示内容:
- 场景名称。
- 场景描述。
- 适用题型。
- 是否启用。
- 进入对话按钮。
### 6.2 场景详情页 可选
路径:`/scenarios/<scenario_id>/`
展示内容:
- Agent 角色。
- Agent 目标。
- RAG 是否启用。
- 可用工具列表。
- 输出模板类型。
V1 可以不做独立详情页,在对话页展示当前场景摘要即可。
## 7. 服务接口需求
Scenarios 模块至少需要提供以下服务函数:
```text
list_scenarios() -> list[ScenarioConfig]
get_scenario(scenario_id: str) -> ScenarioConfig
validate_scenario(config: dict) -> ValidationResult
```
## 8. 验收标准
- 首页可以展示 5 个预置场景。
- 点击场景可以进入对应对话页。
- 场景配置来自配置文件,而不是硬编码在 View 中。
- 后台管理不作为 V1 场景配置编辑入口。
- 缺失必填字段时能给出明确错误。
- Chat 模块可以根据 `scenario_id` 获取完整场景配置。

View File

@@ -0,0 +1,262 @@
# Audit 模块需求分析
## 1. 模块定位
`apps.audit` 在本题中绝不能只被理解为“对话历史”。对于注册申报资料准备与审核系统,它的定位应当是:
> 合规审查留痕与复核中心
因为本题输出的是“资料是否齐套、字段是否一致、哪里有合规风险”,这类结果天然需要留痕、可回溯、可解释。
## 2. 模块目标
本模块需要实现以下目标:
1. 对每一次资料审核、字段抽取、完整性检查和风险预警形成可查询记录。
2. 保留输入条件、处理范围、输出结果、证据来源和失败原因。
3. 为演示“系统不是黑盒”提供直接支撑。
4. 在不泄露敏感信息的前提下,支持问题追溯和结果复核。
5. 为 Web 端与飞书端的多入口执行留存统一审计链路。
## 3. 为什么本题对审计要求更高
在普通问答 Demo 中,审计模块往往只是锦上添花。但在本题里,审计本身就是业务可信度的一部分,原因包括:
1. 注册申报属于强监管场景,任何自动结论都应能追溯。
2. 题面明确要求输出风险预警和处理建议,这些建议后续可能影响资料补正方向。
3. 当前样例中已经出现跨文档冲突、二次申报、历史临床资料替换等复杂情境,没有审计留痕会很难解释系统为何得出某个结论。
## 4. 核心职责
### 4.1 执行留痕
记录每次任务执行的基本信息:
- 执行时间
- 操作人
- 任务类型
- 使用场景
- 输入问题
- 选中文档范围
- 申报批次
### 4.2 处理结果留痕
记录:
- 最终自然语言回答
- 结构化结果
- 风险清单
- 回填字段结果
- 缺失文件清单
### 4.3 证据留痕
记录:
- 引用文档
- 引用片段
- 命中的法规条目或目录项
- 使用的规则版本
- 工具调用过程
### 4.4 异常留痕
记录:
- 解析失败
- 入库失败
- 规则缺失
- 模型调用失败
- 输出冲突待人工确认
本题尤其需要保留失败和不确定状态,而不只是保存成功记录。
## 5. 审计对象定义
建议将审计对象扩展为以下几类:
1. 资料目录汇总任务
2. 完整性检查任务
3. 字段抽取任务
4. 一致性核查任务
5. 风险预警任务
6. 手工重试、重新入库、重新核查等操作
这样可以避免审计模块只能记录“聊天问答”,却看不到文件处理和重跑过程。
## 6. 审计记录字段需求
### 6.1 基础字段
- `audit_id`
- `task_type`
- `scenario_id`
- `project_id`
- `submission_batch_id`
- `created_at`
- `status`
### 6.2 输入字段
- 用户输入问题
- 执行参数
- 选中文档 ID 列表
- 章节点范围
- 规则版本
- 模型名称
- 触发入口类型Web / 飞书群聊 / 飞书评论 / 飞书应用会话)
- 飞书会话或消息标识
### 6.3 输出字段
- 最终摘要
- 结构化输出 JSON
- 风险等级
- 是否通过
- 是否存在人工复核项
- 缺失项数量
- 冲突项数量
### 6.4 证据字段
- 引用文档信息
- 引用片段
- 工具调用结果
- 命中规则项
- 风险评分明细
### 6.5 错误字段
- 错误类型
- 错误信息
- 失败阶段
- 是否可重试
## 7. 与题面强相关的审计需求
### 7.1 对完整性检查结果留痕
当系统判断“缺少临床评估报告”或“缺少某项监管声明”时,应能回查:
- 是依据哪一版规则判断的
- 当前已识别到哪些资料
- 哪些资料被判定未命中
### 7.2 对一致性冲突留痕
当系统判定:
> 说明书与申请表中的产品名称不一致
则审计中必须保留:
- 冲突字段名
- 冲突值
- 对应来源文档
- 判定时间
- 所属审核范围
当前项目目标是通用试剂盒注册审核智能体,因此审计还必须能够说明“这些文档为什么会被纳入同一轮一致性核查”,否则冲突结论容易失真。
### 7.3 对历史申报说明的审计价值
`CH1.9` 涉及历史受理号、撤回、临床数据替换等事项,若系统在风险报告中引用这部分内容,应在审计中保留相关证据链,方便后续说明“为什么标记为历史事项风险”。
## 8. 页面需求
### 8.1 审计列表页
列表页不应仅展示“问了什么问题”,还应体现业务摘要。建议展示:
- 执行时间
- 任务类型
- 项目 / 批次
- 状态
- 风险等级
- 缺失项数
- 冲突项数
- 是否需人工复核
### 8.2 审计详情页
详情页建议展示:
- 输入问题与参数
- 结果摘要
- 结构化结果
- 引用证据
- 工具调用
- 原始输出
- 错误信息
- 脱敏后的上下文信息
## 9. 脱敏与安全要求
### 9.1 不能写入敏感密钥
这一点与 AGENTS 约定一致,日志中不能保存:
- API Key
- 密钥类环境变量
- 不必要的鉴权头
### 9.2 业务敏感信息控制
虽然当前题目材料以产品注册资料为主,但后续真实环境中可能包含:
- 企业联系人
- 手机号 / 邮箱
- 临床机构信息
- 受理号
首版至少要具备“展示层脱敏”的设计意识。
### 9.3 原始输出保留边界
如果 LLM 原始输出中包含大量无效 prompt 内容或潜在敏感字段,应允许:
- 存摘要,不存完整原文
- 或仅对管理员展示原始输出
## 10. 与其他模块的边界
### 10.1 与 Chat 模块
Chat 是主要触发入口Audit 负责把执行结果沉淀为可追踪记录。
### 10.2 与 Documents 模块
Documents 提供文档处理事实Audit 负责记录这些事实如何被某次审核任务引用。
### 10.3 与 Agent Core 模块
Agent Core 负责产出结论与证据Audit 负责记录这些产出及其上下文。
## 11. 当前代码基线下的重构建议
### 11.1 建议保留
- 审计列表与详情页骨架
- 原始输出展示能力
- 敏感信息脱敏思路
- 成功与失败均记录的机制
### 11.2 建议增强
1. 将“对话日志”扩展为“任务执行审计”。
2. 增加项目批次、任务类型、章节点范围、规则版本等字段。
3. 增加缺失项数、冲突项数、人工复核标记等业务指标。
4. 增加法规命中项、字段来源和风险依据的留痕。
5. 增加飞书触发来源、回传状态和责任人通知记录。
## 12. 验收标准
本模块验收时,应达到以下状态:
1. 每次关键审核任务都能形成完整审计记录。
2. 审计详情足以解释“系统为什么得出这个结论”。
3. 成功、失败和待人工复核都可记录。
4. 页面层可快速筛选高风险或异常记录。
5. 敏感密钥不会进入审计内容。
6. 审计中能够明确体现“任一高风险即不通过”的最终判定依据。

View File

@@ -1,132 +0,0 @@
# 文档模块需求文档
## 1. 模块定位
Documents 模块负责文件上传、文件管理、文本抽取和知识库入库入口。
该模块是复试题快速适配的关键模块。拿到题目材料后,用户需要能快速上传文档,并让 Agent 在对话中使用这些文档。
## 2. 模块目标
- 支持上传题目材料和知识库文件。
- 保存文件元数据。
- 支持按场景关联文件。
- 提供文档入库入口。
- 为 Agent Core 的 RAG 模块提供文件内容。
## 3. 职责边界
### 3.1 负责
- 文件上传页面。
- 文件保存。
- 文件元数据记录。
- 文件与场景关联。
- 文本抽取入口。
- 触发 RAG 入库。
### 3.2 不负责
- 不负责具体向量检索算法。
- 不负责 embedding 生成细节。
- 不负责 Agent 对话编排。
- 不负责模型回答。
## 4. 支持文件类型
V1 必须支持:
| 类型 | 扩展名 | 说明 |
|---|---|---|
| 文本文档 | `.txt` | 第一优先级,最稳定 |
| Markdown | `.md` | 适合准备知识库和规则 |
| PDF | `.pdf` | 复试常见材料格式V1 抽取纯文本 |
| Word | `.docx` | 复试常见材料格式V1 抽取段落文本 |
后续增强:
| 类型 | 扩展名 | 说明 |
|---|---|---|
| Excel | `.xlsx` | 后续可作为业务数据源或结构化表格导入 |
## 5. 数据模型需求
建议模型:`UploadedDocument`
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | int | 主键 |
| `scenario_id` | string | 关联场景 ID |
| `original_name` | string | 原始文件名 |
| `file` | FileField | Django FileField 相对路径,不保存用户本机绝对路径 |
| `file_type` | string | 文件类型 |
| `size` | int | 文件大小 |
| `status` | string | `uploaded` / `indexed` / `failed` |
| `error_message` | text | 入库失败原因 |
| `created_at` | datetime | 上传时间 |
| `updated_at` | datetime | 更新时间 |
## 6. 页面需求
### 6.1 文件上传页
路径:`/documents/upload/`
页面元素:
- 场景选择下拉框。
- 文件选择按钮。
- 上传按钮。
- 支持类型提示。
- 上传结果提示。
### 6.2 文件列表页
路径:`/documents/`
展示内容:
- 文件名。
- 所属场景。
- 文件类型。
- 文件大小。
- 入库状态。
- 上传时间。
- 入库按钮。
## 7. RAG 入库流程
用户上传文件后,可以手动触发入库。
流程:
1. 用户上传文件。
2. 系统保存文件和元数据。
3. 用户点击入库按钮。
4. Documents 模块读取文件文本。
5. 调用 `agent_core.rag.ingest`
6. 入库成功后更新状态为 `indexed`
7. 入库失败后更新状态为 `failed` 并保存错误信息。
## 8. 文本抽取需求
V1 文本抽取策略:
- `.txt`:按 UTF-8 读取,失败时尝试系统默认编码。
- `.md`:按 UTF-8 读取,保留标题和正文。
- `.pdf`:抽取纯文本,不要求 OCR、表格还原和复杂版式理解。
- `.docx`:抽取段落、标题和普通表格文本,不要求完整保留 Word 样式。
入库失败后的文档允许重新触发入库。重新入库前需要清理或覆盖同一 `document_id` 对应的旧 chunk避免重复检索。
## 9. 验收标准
- 可以上传 `.txt``.md``.pdf``.docx` 文件。
- 上传后可以在文件列表看到记录。
- 文件可以关联到指定场景。
- 可以触发文件入库。
- 入库成功后状态变为 `indexed`
- 入库失败时页面能显示失败原因。
- 入库失败的文档可以重新入库。

View File

@@ -0,0 +1,537 @@
# Agent Core 模块需求分析
## 1. 模块定位
`agent_core` 是整套系统的能力中枢。在本题中,它不应再被描述为“一个通用 Prompt + RAG + Tool 的抽象核心”,而应被明确定位为:
> 注册申报资料审核编排引擎
它负责把法规规则、文档解析结果、字段抽取逻辑、一致性核查逻辑、风险输出模板和大模型能力组织成一个可执行的审核流程。
## 2. 模块总目标
本模块需要完成以下目标:
1. 基于题面要求完成文件目录汇总、完整性核查、字段抽取、回填准备、一致性检查和风险预警。
2. 形成规则优先、模型辅助的审核框架,而不是完全依赖自由生成。
3. 提供结构化、可追溯、可测试的输出。
4. 保持与 Django 页面层和数据层的边界清晰。
## 3. 为什么 Agent Core 是本题真正的“答题核心”
本题的难点不在“接个大模型接口”,而在于以下几点如何落到一个统一编排里:
1. 资料目录与法规目录如何比对。
2. 产品说明书、申请表、产品列表、声明文件之间如何抽取统一字段。
3. 不同字段的“一致”与“不一致”如何定义。
4. 风险预警如何从规则结果和模型解释中生成。
这些都属于 `agent_core` 的职责范围。
## 4. 核心能力拆分
建议将 `agent_core` 的能力拆成以下几个子域理解。
### 4.1 任务编排
根据不同任务入口,组织不同处理链路。例如:
- 目录汇总链路
- 完整性检查链路
- 字段抽取链路
- 一致性核查链路
- 综合风险链路
### 4.2 规则引擎
对以下事项优先使用规则处理:
- 章节点完整性
- 必交文件判断
- 文件归类
- 固定字段抽取
- 强一致字段比对
### 4.3 LLM 辅助推理
对以下事项由 LLM 作为辅助:
- 长段文本中的字段归纳
- 风险说明文案生成
- 处理建议生成
- 无法通过简单规则覆盖的异常解释
### 4.4 RAG 检索
用于在文档较长、规则或用户问题较细时,从已入库资料中定位证据片段,为回答和审计提供支撑。
对本题而言RAG 不仅要覆盖业务申报资料,也要覆盖公告附件包等法规原文资料。但它的职责应限定为:
1. 为规则判断提供证据定位。
2. 为结果解释提供法规引用。
3. 为审计留痕提供可追溯片段。
不能把 RAG 检索命中的段落直接等同于最终合规判断。
### 4.5 结构化输出
将每类任务输出为明确 schema而不是一段随意文本。
## 5. 按题面要求拆解的能力需求
## 5.1 文件目录汇总能力
### 目标
自动汇总注册申报文件夹中的所有文件及页数。
### 需要的输入
- Documents 模块提供的文档记录
- 文件页数
- 文档归类信息
### 处理逻辑
1. 遍历当前项目 / 批次所有资料。
2. 汇总文件名、章节点、页数、状态。
3. 识别目录类文档与普通文档。
4. 输出目录总表。
### 输出要求
结构化输出中至少包含:
- 文件清单
- 文件数量
- 总页数
- 已识别章节点
- 待确认文档
## 5.2 法规完整性核查能力
### 目标
对照 NMPA 法规要求,检查所需资料是否齐全,并识别缺失项。
### 规则依据
当前材料已明确可用依据包括:
- `附件 4 体外诊断试剂注册申报资料要求及说明`
- `CH1.2 监管信息目录`
- 题面中提及的 NMPA / CMDE 法规来源
- `关于公布体外诊断试剂注册申报资料要求和批准证明文件格式的公告` 附件包
结合新增公告附件包,法规规则来源建议分层管理:
1. 注册申报资料要求及说明
2. 医疗器械注册申报资料和批准证明文件格式要求(体外诊断试剂)
3. 体外诊断试剂安全和性能基本原则清单
4. 中华人民共和国医疗器械注册证(体外诊断试剂)格式
5. 变更备案 / 变更注册申报资料要求及说明
6. 延续注册申报资料要求及说明
### 处理逻辑
1. 装载法规目录模板。
2. 装载当前资料实际清单。
3. 以章节点和资料名称进行匹配。
4. 区分:
- 已提供
- 缺失
- 疑似已提供但命名不规范
- 需人工判断
5. 生成缺失项清单和建议动作。
### 关键难点
不是所有缺失都等价。需要区分:
- 监管强制项缺失
- 目录中声明有但实际文件找不到
- 文件存在但内容不符合该章节点用途
此外,还需要区分:
- 资料要求缺失
- 文件格式要求不满足
- 安全和性能基本原则映射不完整
### 输出要求
- 命中项列表
- 缺失项列表
- 风险等级
- 建议补充动作
- 规则依据
## 5.3 产品关键信息抽取能力
### 目标
从产品文件中提取关键信息并自动填写到目标文件或结构化结果中。
### 目标字段建议
至少包括题面点名字段:
- 产品名称
- 检测靶标
- 适用范围 / 预期用途
- 储存条件
- 性能指标
结合样例材料,建议进一步扩展:
- 包装规格
- 适用样本类型
- 适用仪器
- 分类编码
- 临床评价路径
- 申请人名称
- 生产地址
- 标准清单
- 申报日期
考虑到系统目标是“通用的试剂盒临床注册文件准备与审核智能体”,字段 schema 应优先沉淀通用注册字段,而不是只对某一具体产品定制。
### 字段来源优先级
需要明确来源优先级,例如:
1. 申请表
2. 产品说明书
3. 产品列表
4. 声明类文件
5. 其他说明材料
或根据字段类型分别设定优先级。
### 抽取逻辑
1. 规则抽取显式字段。
2. 表格抽取规格、组分、标准清单等。
3. 对长文本字段使用 LLM 归纳。
4. 将结果写入统一字段池。
5. 标记字段来源和置信状态。
### 输出要求
- 字段名
- 字段值
- 来源文档
- 来源片段
- 是否冲突
- 是否可直接回填
## 5.4 自动回填准备能力
### 目标
将抽取得到的信息填入目标文件或目标字段。
### 首版建议范围
首版即需要满足以下交付目标:
- 申请表字段回填数据集
- 对照清单字段回填数据集
- 页面可视化回填预览
- 新的 Word 文档生成与导出能力
- 基于模板库的高保真版式回填能力
### 处理逻辑
1. 根据目标模板定义字段映射。
2. 从统一字段池读取值。
3. 对冲突字段进行拦截或提示。
4. 生成回填预览结果。
### 后续扩展
Word 输出必须满足以下要求:
1. 支持将用户新增模板纳入模板库。
2. 回填时按模板字段映射写入指定模板。
3. 输出文档的标题层级、表格、页眉页脚、盖章位和整体版式需达到可直接报送级别。
结合新增公告附件包中的批准证明文件格式材料,回填能力的后续扩展方向应进一步明确为:
1. 按注册证 / 批准证明文件格式模板生成字段映射。
2. 按不同法规流程类型切换不同输出模板。
## 5.5 一致性核查能力
### 目标
核查不同文档间相同信息是否一致,检测章节结构和规范性问题。
### 强一致字段
建议首版按强一致处理的字段包括:
- 产品名称
- 申请人名称
- 规格型号 / 包装规格
- 分类编码
- 申报产品名称对应的章节点标题
结合最新确认,当前阶段不采用“语义一致即可通过”的宽松规则。对于被纳入同一审核范围的相同字段,默认按完全一致处理;如出现措辞差异,也应先判为冲突或待复核。
### 审核范围前置规则
一致性核查前必须先明确:
1. 哪些文档属于同一项目 / 批次 / 审核范围。
2. 哪些文档只是通用样本材料,不能直接混入同一轮一致性比对。
因此,一致性核查链路应包含“审核范围确认”这一步,而不是直接对全部文档做全量比较。
### 结构核查
除字段一致性外,还应检查:
- 说明书是否包含关键章节
- 目录页是否覆盖当前章节点
- 文档标题是否规范
- 是否存在不属于本产品的资料混入
### 典型异常示例
根据当前样例,系统应能识别:
- 若这些文档被划入同一审核范围则“2019-nCoV”与“呼吸道合胞病毒、肺炎支原体”构成明确冲突。
- 若这些文档本身被认定属于不同资料组,则系统应提示“存在跨产品样例混入,不应直接合并审核”。
### 输出要求
- 一致字段列表
- 冲突字段列表
- 冲突明细
- 风险等级
- 处理建议
## 5.6 合规风险预警能力
### 目标
把完整性检查、字段抽取和一致性核查的结果汇总成可执行的风险清单。
### 风险类型建议
- 缺失风险
- 混档风险
- 字段冲突风险
- 章节不规范风险
- 历史申报事项风险
- 资料真实性 / 版本一致性风险
- 法规适用情形错误风险
### 风险分级建议
首版可采用:
- 高风险
- 中风险
- 低风险
另行保留“待人工复核”状态,但它不是风险等级,而是处理状态。
### 风险准入规则
风险判定应采用综合分析机制,对至少以下维度分别评分:
1. 法规完整性
2. 跨文档字段一致性
3. 文档结构与章节规范性
4. 历史事项与版本风险
5. 法规流程适用性风险
综合规则如下:
1. 任一维度出现高风险项,则本次审核直接判定为不通过。
2. 无高风险但存在多个中风险项时,应给出“待整改后复核”的建议。
3. 低风险项可进入整改建议清单,但不单独阻断。
### 处理建议生成逻辑
规则部分负责给出基础动作,例如:
- “补充缺失文件”
- “核对产品名称”
- “重新确认临床资料版本”
LLM 负责把这些动作组织成自然语言建议,但不能改变底层规则结论。
## 6. 统一字段池设计需求
为支撑抽取、回填和一致性核查,建议在 `agent_core` 内形成统一字段池概念。
字段池至少记录:
- 字段名
- 标准化字段值
- 原始字段值
- 来源文档
- 来源位置
- 置信度
- 冲突状态
- 最终推荐值
这是本题从“简单聊天 Demo”走向“资料审核系统”的关键能力之一。
## 7. 规则体系需求
### 7.1 完整性规则
用于判断:
- 某章节点是否必交
- 当前资料是否命中
- 缺失是否构成高风险
这里应进一步拆为三个子层:
1. 资料要求层完整性规则
2. 结构目录层完整性规则
3. 格式模板层完整性规则
同时,这三层规则都应能映射回“章 -> 条 -> 要求项 -> 模板字段”四级知识结构。
### 7.2 抽取规则
用于:
- 标题识别
- 表格字段映射
- 固定格式声明提取
对于法规资料本身,还应支持抽取:
- 附件编号
- 法规流程类型
- 适用范围说明
- 批准证明文件格式字段
并为后台管理页面提供人工校订后的结构化回写入口。
### 7.3 一致性规则
用于定义:
- 哪些字段必须完全一致
- 如何判断冲突严重度
- 如何在执行前确认审核范围
### 7.4 风险映射规则
用于把缺失、冲突、不确定结果映射为风险级别、综合得分、是否通过和处理建议。
新增公告材料后,风险映射还应能够体现“适用情形错误”的风险,例如:
1. 把变更备案规则误用于首次注册申报
2. 把延续注册格式误用于注册申报输出
同时,若系统生成 Word 输出失败、模板字段无法落位或导出格式破坏严重,也应形成独立的交付风险提示。
## 8. 工具体系需求
题面附加要求提到需要展示实际调用的关键工具/库。因此 `agent_core.tools` 中应逐步沉淀出与本题强相关的工具,而不是只保留通用样例工具。
建议工具方向包括:
1. 文档页数统计工具
2. 章节点识别工具
3. 必交项检查工具
4. 字段抽取工具
5. 字段一致性比对工具
6. 风险汇总工具
7. 审核范围确认工具
8. 法规流程识别工具
9. 格式模板映射工具
10. Word 模板回填与导出工具
11. 飞书消息摘要生成与通知载荷组装工具
12. 责任人映射解析工具
13. 规则切片与结构化回写工具
这些工具都应通过 Tool Registry 注册,符合项目既有边界要求。
## 9. LLM Provider 需求
### 9.1 不允许业务代码散落模型调用
所有模型调用继续通过 Provider 统一处理。
### 9.2 模型在本题中的使用原则
本题应坚持:
1. 规则优先
2. 证据优先
3. 模型负责解释、补充和归纳
4. 模型不应凭空判断法规完整性
### 9.3 测试要求
所有核心编排逻辑应继续支持 Mock Provider以保证回归测试离线可跑。
## 10. 结构化输出 Schema 需求
建议至少定义以下输出类型:
1. `registration_overview_report`
2. `registration_completeness_report`
3. `registration_field_extraction_report`
4. `registration_consistency_report`
5. `registration_risk_report`
每种输出都应有稳定字段,便于页面展示与测试覆盖。
## 11. 与其他模块的边界
### 11.1 与 Documents 模块
Documents 负责提供资料事实Agent Core 负责把这些事实转化为审核结论。
### 11.2 与 Chat 模块
Chat 负责接收用户意图和展示结果Agent Core 负责执行任务链路。
### 11.3 与 Audit 模块
Audit 负责记录过程和结果Agent Core 负责产出可记录的结构化执行信息。
## 12. 当前代码基线下的重构建议
### 12.1 建议保留
- Prompt 编排机制
- 结构化结果对象
- Tool Registry
- RAG fallback / Chroma 双路径思路
- Mock Provider 测试策略
### 12.2 建议增强
1. 从通用场景输出转向注册审核专用输出 schema。
2. 增加法规完整性规则和目录模板匹配逻辑。
3. 增加统一字段池。
4. 增加一致性核查与风险汇总工具。
5. 将“回填准备结果”纳入正式输出结构。
6. 增加“是否通过”和“风险评分明细”输出字段。
7. 增加法规分层规则管理,以及注册申报 / 变更 / 延续三类流程的扩展边界。
8. 增加模板库驱动的高保真 Word 生成链路。
9. 增加后端管理入口所需的规则回写、人工校订和责任人映射能力。
## 13. 验收标准
本模块完成后,应至少满足:
1. 能支持目录汇总、完整性检查、字段抽取、一致性核查、风险预警五类核心任务。
2. 核心结论有结构化输出,不依赖随意文本。
3. 规则和模型分工清晰,法规判断不完全依赖大模型生成。
4. 输出能关联到具体文档和证据片段。
5. 测试环境下可以通过 Mock Provider 验证主要编排逻辑。
6. 法规原文可切片入 RAG但最终完整性与准入判断仍由规则链路主导。
7. Word 输出结果能够基于模板库生成可直接报送级版式文档。

View File

@@ -1,129 +0,0 @@
# 对话模块需求文档
## 1. 模块定位
Chat 模块负责 Agent 对话页面和用户交互,是复试演示时最核心的入口。
该模块接收用户问题,加载场景配置,调用 Agent Core 执行智能编排,并将结构化结果、引用来源、工具调用和审计信息展示给用户。
## 2. 模块目标
- 提供按场景进入的 Agent 对话页。
- 支持用户输入业务问题。
- 调用 Agent Core 执行完整 Agent 流程。
- 展示结构化输出。
- 展示 RAG 引用片段。
- 展示工具调用记录。
- 触发审计日志写入。
## 3. 职责边界
### 3.1 负责
- 对话页面渲染。
- 表单接收和校验。
- 当前场景上下文传递。
- 调用 Agent Core。
- 展示 Agent 返回结果。
### 3.2 不负责
- 不直接读取 YAML 场景文件。
- 不直接执行 RAG 检索。
- 不直接执行工具函数。
- 不直接调用大模型 API。
- 不直接写复杂审计细节。
## 4. 页面需求
### 4.1 Agent 对话页
路径:`/chat/<scenario_id>/`
页面区域:
- 当前场景摘要。
- 当前场景下已入库文档多选框。
- 用户问题输入框。
- 提交按钮。
- Agent 结构化输出区域。
- 引用来源区域。
- 工具调用区域。
- 执行耗时区域。
- 审计日志详情入口。
## 5. 表单需求
用户输入表单字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `message` | textarea | 是 | 用户业务问题 |
| `document_ids` | list[int] | 否 | 本次对话指定使用的已入库文档 |
校验规则:
- 输入不能为空。
- 输入长度建议不超过 4000 字。
- 如果场景不存在,需要返回明确错误。
- `document_ids` 只能包含当前场景下状态为 `indexed` 的文档。
- 未选择文档时,默认使用当前场景下全部已入库文档作为 RAG 范围。
## 6. Agent 执行流程
Chat 模块调用 Agent Core 的流程:
```text
用户提交问题
校验 scenario_id 和 message
获取场景配置
调用 `run_agent(scenario_config, user_input, options=None)`
获取 AgentResult
调用 Audit 模块记录日志
渲染结果页面
```
## 7. AgentResult 展示需求
Agent Core 返回结果建议包含:
| 字段 | 说明 |
|---|---|
| `answer` | 自然语言回答 |
| `structured_output` | 结构化结果 |
| `references` | RAG 引用来源 |
| `tool_calls` | 工具调用记录 |
| `raw_output` | 模型原始输出 |
| `latency_ms` | 执行耗时 |
| `error` | 错误信息 |
页面需要优先展示结构化结果。如果结构化解析失败,则展示自然语言回答和错误提示。
## 8. 错误处理需求
需要处理以下错误:
| 错误 | 页面行为 |
|---|---|
| 场景不存在 | 显示场景不存在 |
| 用户输入为空 | 显示表单错误 |
| LLM API Key 缺失 | 显示模型配置缺失 |
| RAG 检索失败 | 显示检索失败,但允许模型基于已有信息回答 |
| 工具调用失败 | 显示工具失败信息,并继续生成结果 |
| 结构化解析失败 | 展示原始回答,并提示结构化解析失败 |
## 9. 验收标准
- 可以从场景列表进入对话页。
- 可以提交问题并获得 Agent 输出。
- 页面能展示结构化结果。
- 页面能展示引用来源。
- 页面能展示工具调用记录。
- 执行失败时有可理解的错误提示。
- 每次对话都会产生审计日志。

View File

@@ -1,143 +0,0 @@
# Audit 模块需求文档
## 1. 模块定位
Audit 模块负责记录和展示 Agent 执行过程,是项目体现企业级能力的重要模块。
复试演示时,审计日志用于证明系统不是黑盒问答,而是可以追踪输入、检索、工具调用、模型输出和执行耗时。
## 2. 模块目标
- 记录每次 Agent 对话。
- 记录 RAG 检索片段。
- 记录工具调用详情。
- 记录模型输出和结构化结果。
- 提供审计日志列表和详情页。
- 支持按场景查看日志。
## 3. 职责边界
### 3.1 负责
- 审计日志数据模型。
- 日志写入服务。
- 日志列表页面。
- 日志详情页面。
- 工具调用记录展示。
- RAG 引用记录展示。
### 3.2 不负责
- 不执行 Agent。
- 不执行工具调用。
- 不执行 RAG 检索。
- 不参与模型生成。
- 不做复杂权限控制。
## 4. 数据模型需求
建议模型:`AgentAuditLog`
字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | int | 主键 |
| `scenario_id` | string | 场景 ID |
| `scenario_name` | string | 场景名称 |
| `user_input` | text | 用户输入 |
| `retrieved_chunks` | JSON | 检索片段 |
| `tool_calls` | JSON | 工具调用记录 |
| `structured_output` | JSON | 结构化输出 |
| `final_answer` | text | 最终回答 |
| `raw_output` | text | 模型原始输出 |
| `model_name` | string | 模型名称 |
| `latency_ms` | int | 执行耗时 |
| `status` | string | `success` / `failed` |
| `error_message` | text | 错误信息 |
| `created_at` | datetime | 创建时间 |
## 5. 日志写入需求
Audit 模块需要提供服务函数:
```text
create_audit_log(
scenario_id,
scenario_name,
user_input,
agent_result
) -> AgentAuditLog
```
写入规则:
- Agent 成功时,记录完整结果。
- Agent 失败时,也要记录用户输入、场景和错误信息。
- RAG 片段和工具调用使用 JSON 保存。
- 不记录 API Key 等敏感配置。
## 6. 页面需求
### 6.1 审计日志列表页
路径:`/audit/`
展示字段:
- 日志 ID。
- 场景名称。
- 用户输入摘要。
- 状态。
- 模型名称。
- 执行耗时。
- 创建时间。
- 详情入口。
### 6.2 审计日志详情页
路径:`/audit/<log_id>/`
展示内容:
- 用户输入。
- 最终回答。
- 结构化输出。
- RAG 检索片段。
- 工具调用记录。
- 模型名称。
- 执行耗时。
- 错误信息。
## 7. 检索片段展示需求
每个引用片段建议包含:
| 字段 | 说明 |
|---|---|
| `source` | 来源文件名 |
| `chunk_id` | 片段 ID |
| `content` | 片段内容 |
| `score` | 相似度分数 |
## 8. 工具调用展示需求
每次工具调用建议包含:
| 字段 | 说明 |
|---|---|
| `tool_name` | 工具名称 |
| `arguments` | 调用参数 |
| `result` | 工具结果 |
| `success` | 是否成功 |
| `error` | 错误信息 |
## 9. 验收标准
- 每次对话成功后都会生成审计日志。
- Agent 执行失败时也会生成失败日志。
- 审计列表可以查看所有日志。
- 审计详情可以查看用户输入、检索片段、工具调用和最终输出。
- 日志中不保存 API Key。
- 可以根据日志解释一次 Agent 输出的依据。

View File

@@ -1,225 +0,0 @@
# 智能核心模块需求文档
## 1. 模块定位
Agent Core 是系统的智能能力核心,负责根据场景配置完成 RAG 检索、工具调用、大模型调用和结构化输出。
该模块应保持独立于 Django View方便后续迁移为独立服务或接入 OpenAI Agents SDK、Dify 等外部编排引擎。
## 2. 模块目标
- 提供统一 Agent 执行入口。
- 根据场景配置组织 Prompt。
- 支持 RAG 检索。
- 支持工具注册与调用。
- 支持 OpenAI API 兼容的 LLM 与 Embedding 调用,可自主接入 OpenAI、硅基流动等兼容服务。
- 支持结构化输出解析。
- 返回可审计的 AgentResult。
## 3. 职责边界
### 3.1 负责
- Agent 编排。
- 场景配置对象消费。
- RAG 入库和检索核心逻辑。
- 工具注册和工具执行。
- LLM Provider 适配。
- 输出结构化解析。
- 生成 AgentResult。
### 3.2 不负责
- 不渲染页面。
- 不直接处理 Django 表单。
- 不直接保存 Django Model。
- 不管理用户登录。
- 不负责 Docker 部署。
## 4. 子模块划分
```text
agent_core/
orchestrator.py
scenario_loader.py
llm_provider.py
tool_registry.py
structured_output.py
rag/
ingest.py
retriever.py
tools/
builtin_tools.py
schemas/
outputs.py
```
## 5. Orchestrator 需求
`orchestrator.py` 提供统一入口:
```text
run_agent(
scenario_config,
user_input,
options=None
) -> AgentResult
```
执行流程:
1. 读取场景配置。
2. 根据配置判断是否启用 RAG。
3.`scenario_id` 和可选 `document_ids` 检索相关知识片段。
4. 根据配置加载可用工具。
5. 构造系统提示词。
6. 调用大模型。
7. 执行必要的工具调用。
8. 解析结构化输出。
9. 返回 AgentResult。
V1 可以使用轻量 Orchestrator不强制引入完整 Agent SDK。
## 6. RAG 需求
### 6.1 入库
`rag/ingest.py` 负责:
- 接收文档文本。
- 文本切分。
- 通过 OpenAI 兼容 Embedding Provider 生成 embedding。
- 写入 Chroma。
- 保存 metadata。
metadata 至少包含:
- `scenario_id`
- `document_id`
- `source_file`
- `chunk_id`
- `created_at`
### 6.2 检索
`rag/retriever.py` 负责:
- 根据用户问题检索相关片段。
- 支持按 `scenario_id` 过滤。
- 支持按本次对话选择的 `document_ids` 过滤;未选择时使用当前场景全部已入库文档。
- 返回 top_k 结果。
- 返回内容、来源和分数。
## 7. 工具系统需求
`tool_registry.py` 负责工具注册、查找和执行。
V1 内置工具:
| 工具名 | 说明 |
|---|---|
| `calculate_rate` | 计算通过率、缺陷率、占比等 |
| `query_demo_records` | 查询模拟业务数据 |
| `check_required_fields` | 检查必填项是否缺失 |
| `generate_action_items` | 生成行动项清单 |
工具执行结果需要统一格式:
```json
{
"tool_name": "calculate_rate",
"success": true,
"arguments": {},
"result": {},
"error": ""
}
```
## 8. LLM Provider 需求
`llm_provider.py` 负责模型调用。
V1 需要支持 OpenAI API 兼容 LLM 接口:
- `LLM_API_KEY`
- `LLM_BASE_URL`
- `LLM_MODEL`
接口需要隐藏不同模型供应商差异,对 Orchestrator 暴露统一方法:
```text
generate(messages, response_format=None) -> LLMResponse
```
Embedding 也通过 OpenAI 兼容接口接入:
- `EMBEDDING_API_KEY`
- `EMBEDDING_BASE_URL`
- `EMBEDDING_MODEL`
当 Embedding 专用 Key 或 Base URL 为空时,可以复用 LLM 的 Key 和 Base URL。RAG 入库和检索必须通过真实 embedding 与 Chroma 完成,模拟 embedding 或简单文本匹配只能作为开发阶段临时桩,不计入 V1 验收。
## 9. 结构化输出需求
`structured_output.py` 负责将模型输出转换为业务结构。
V1 输出类型:
- `general_answer`
- `document_review_report`
- `ticket_response`
- `quality_report`
- `risk_audit_report`
解析策略:
- 优先要求模型直接返回 JSON。
- JSON 解析成功则展示结构化结果。
- JSON 解析失败则保留原始输出,并返回解析错误。
## 10. AgentResult 需求
Agent Core 最终返回统一结果对象。
字段:
| 字段 | 说明 |
|---|---|
| `answer` | 最终自然语言回答 |
| `structured_output` | 结构化输出 |
| `references` | RAG 引用片段 |
| `tool_calls` | 工具调用记录 |
| `raw_output` | 模型原始输出 |
| `model_name` | 模型名称 |
| `latency_ms` | 执行耗时 |
| `status` | `success` / `failed` |
| `error` | 错误信息 |
## 11. Adapter 扩展需求
V1 默认使用 `LightweightOrchestrator`
后续可扩展:
- OpenAI Agents SDK Adapter。
- Dify API Adapter。
- LangGraph Adapter。
Adapter 需要保持同样输入输出:
```text
run_agent(scenario_config, user_input, options=None) -> AgentResult
```
## 12. 验收标准
- Chat 模块可以调用 Agent Core 获得统一 AgentResult。
- RAG 可以按场景检索知识片段。
- RAG 可以按本次对话选择的文档范围检索知识片段。
- 工具调用结果可以记录并返回。
- LLM 与 Embedding 配置可以通过环境变量切换。
- 结构化输出解析失败时不会导致整个流程崩溃。
- Agent Core 不依赖 Django View。

View File

@@ -0,0 +1,597 @@
# 业务确认问答清单
## 1. 文档目的
本文档用于和业务方进一步确认需求边界、交付口径和演示重点。
它和 `0.需求重构总览与待确认事项.md` 的区别是:
1. 总览文档更偏内部需求分析。
2. 本文档更偏“可直接拿去问业务”的沟通清单。
3. 每个问题尽量使用业务语言,避免陷入实现细节。
建议使用方式:
1. 按章节逐条和业务确认。
2. 每个问题尽量记录“明确答案 + 备注 + 示例材料来源”。
3. 若业务方回答不确定,可先记为“当前默认口径”。
4. 沟通完成后,再回填更新需求分析和设计文档。
---
## 2. 当前已确认口径摘要
以下内容目前已经有较明确的方向,和业务确认时可以先作为“我们的当前理解”说给对方听,请对方确认是否同意:
1. 系统目标是“通用的试剂盒临床注册文件准备与审核智能体”,不是只服务某一个固定产品。
2. 一致性核查当前按完全一致处理,不采用“语义差不多即可”的宽松口径。
3. 风险按高 / 中 / 低三级,任一高风险即不允许通过。
4. 输出结果必须支持生成新的 Word 文档,并达到可直接报送级版式。
5. 用户后续编写的新模板可以纳入模板库,后续按模板回填。
6. 规则来源以公告附件包为主,`附件 4` 视作同源补充材料。
7. 法规知识按“章 -> 条 -> 要求项 -> 模板字段”四级结构维护。
8. 飞书接入属于本次 Demo 范围,需要支持群聊机器人,并在飞书内完成任务选择、结果查看和责任人通知。
9. 系统需要提供后台管理页面,支持人工校订、知识库更新、模板管理和责任人维护。
---
## 3. 建议的沟通原则
和业务沟通时,建议优先确认四类问题:
1. 交付结果长什么样。
2. 哪些结论能自动判,哪些必须人工复核。
3. 哪些材料算输入,哪些材料算规则依据。
4. Demo 要演示到什么深度。
对于每个问题,建议都尽量追问到这三个层次:
1. 业务目标是什么。
2. 验收标准是什么。
3. 出现例外时怎么处理。
---
## 4. 业务确认问答清单
## 4.1 业务目标与使用场景
### Q1 当前系统最核心要解决的业务问题是什么?
建议提问方式:
> 如果这次 Demo 只能重点解决 1 到 2 个最关键问题,您最希望它解决什么?
建议记录答案:
- 主问题:
- 次问题:
- 不在本次范围的问题:
为什么要问:
- 这个问题决定首页主叙事和演示主线。
- 也决定后续是偏“审核”还是偏“生成回填”。
### Q2 这个系统的主要使用人是谁?
建议提问方式:
> 这个系统主要是给注册专员、法规人员、项目经理,还是管理层演示用?是否会有不同角色看到不同内容?
建议记录答案:
- 主要用户角色:
- 次要用户角色:
- 是否区分角色权限:
为什么要问:
- 决定页面表述是否偏专业操作型。
- 决定飞书通知和责任人配置逻辑。
### Q3 业务更看重“审核发现问题”还是“自动生成报送材料”?
建议提问方式:
> 在您看来,这套系统最有价值的是先帮您发现资料问题,还是直接帮您产出可报送材料?
建议记录答案:
- 更偏审核:
- 更偏生成:
- 两者权重:
为什么要问:
- 决定 Demo 讲解时主功能排序。
- 决定开发时优先打磨哪个闭环。
---
## 4.2 输入资料范围
### Q4 本次 Demo 是否允许以第 1 章样本作为主演示材料?
建议提问方式:
> 目前样例材料主要集中在第 1 章Demo 是否可以以第 1 章为主要演示对象,同时在规则上覆盖第 2 到第 6 章?
建议记录答案:
- 是否允许:
- 若不允许,需要补哪些章的样本:
为什么要问:
- 决定当前实现是否以“第 1 章内容级演示 + 全章规则级覆盖”为主。
### Q5 第 2 到第 6 章后续是否会补充真实业务样本?
建议提问方式:
> 如果希望 Demo 展示第 2 到第 6 章的抽取、比对和回填效果,后续是否会补充真实样本或脱敏样本?
建议记录答案:
- 是否补充:
- 预计补充哪些章:
- 预计补充时间:
为什么要问:
- 决定系统当前只做“结构合规”还是还能做“内容级审核”。
### Q6 当前样例中的跨产品材料混杂,是刻意测试异常,还是后续会统一资料包?
建议提问方式:
> 当前样例里存在不同产品名称的材料混在一起,这种情况是有意作为异常样例,还是后续会再整理成同一产品的一套资料?
建议记录答案:
- 属于刻意异常:
- 属于样例混杂:
- 后续是否会统一:
为什么要问:
- 决定一致性冲突是“核心演示点”还是“样本噪声”。
---
## 4.3 自动审核与人工复核边界
### Q7 哪些类型的问题可以由系统直接判定,哪些必须人工复核?
建议提问方式:
> 对您来说,哪些问题系统可以直接下结论,哪些问题必须保留给人工复核?
建议引导业务按下列类型回答:
1. 文件缺失
2. 章节点归类错误
3. 字段不一致
4. 模板格式不符合
5. 历史申报事项解释
建议记录答案:
- 可自动判定:
- 必须人工复核:
- 自动判定后是否仍需人工确认:
为什么要问:
- 决定结果页里哪些内容标“通过/不通过”,哪些标“待复核”。
### Q8 “完全一致”的范围是否适用于所有字段?
建议提问方式:
> 我们当前理解是:同一审核范围内,相同字段默认按完全一致处理。请确认是否所有关键字段都按这个标准执行,还是有少数字段允许格式差异但含义一致。
建议引导业务按字段类别回答:
1. 产品名称
2. 申请人名称
3. 规格型号
4. 适用范围
5. 储存条件
6. 样本类型
建议记录答案:
- 必须完全一致的字段:
- 允许人工判断等价的字段:
为什么要问:
- 虽然当前默认口径是完全一致,但和业务再确认一次更稳。
### Q9 风险评分结果里,“不通过”是否只看高风险,还是还要加总分门槛?
建议提问方式:
> 当前理解是“任一高风险即不通过”。除此之外,是否还需要总分门槛,比如中风险积累到一定程度也不通过?
建议记录答案:
- 是否仅以高风险阻断:
- 是否需要总分门槛:
- 是否需要“有条件通过 / 整改后复核”状态:
为什么要问:
- 决定风险报告的最终判定逻辑。
---
## 4.4 Word 生成与模板管理
### Q10 业务侧对“可直接报送级版式”的验收标准是什么?
建议提问方式:
> 对于导出的 Word您认为达到“可直接报送”的最低标准是什么哪些版式元素是绝对不能错的
建议引导业务从以下维度回答:
1. 标题层级
2. 段落格式
3. 表格样式
4. 页眉页脚
5. 页码
6. 盖章位
7. 附件格式
建议记录答案:
- 必须严格保留的版式元素:
- 可接受微调的版式元素:
- 不可接受的典型错误:
为什么要问:
- 这是 Word 导出能否被认为“可交付”的关键。
### Q11 是否需要同时导出 PDF 归档件?
建议提问方式:
> 除了 Word 报送稿,是否还需要系统同步导出 PDF 作为归档、流转或内部审核附件?
建议记录答案:
- 是否需要 PDF
- Word 和 PDF 哪个是主交付件:
为什么要问:
- 决定导出链路和验收文件类型。
### Q12 新模板进入模板库后,谁来确认它是正式模板?
建议提问方式:
> 如果后续由您这边提供新的模板,系统纳入模板库之前,是否需要指定某个角色进行确认、审批或启用?
建议记录答案:
- 模板提供方:
- 模板审核方:
- 模板启用规则:
为什么要问:
- 决定后台模板管理页是否需要版本和审批状态。
### Q13 模板是按什么维度区分的?
建议提问方式:
> 模板需要按哪些维度区分,例如注册申报 / 变更 / 延续、不同章节点、不同产品类型,还是不同企业内部模板版本?
建议记录答案:
- 按流程类型:
- 按章节点:
- 按产品类型:
- 按企业版本:
为什么要问:
- 决定模板库数据结构。
---
## 4.5 法规规则与知识库治理
### Q14 当前法规规则源是否只以现有公告附件包为准?
建议提问方式:
> 当前 Demo 是否可以只以现有公告附件包和附件 4 为规则依据,不额外纳入其他外部法规文件?
建议记录答案:
- 是否只用现有规则包:
- 是否还要补充其他规则源:
为什么要问:
- 决定知识库首版范围。
### Q15 后台知识库更新入口由谁使用?
建议提问方式:
> 后台的知识库更新和人工校订功能,预计是只有管理员可用,还是法规/注册业务人员也会参与维护?
建议记录答案:
- 仅管理员:
- 管理员 + 业务人员:
- 是否需要操作留痕:
为什么要问:
- 决定后台权限模型和审计范围。
### Q16 人工校订主要校订哪些内容?
建议提问方式:
> 您希望人工校订功能主要用于改哪些内容?是法规切片、字段映射、模板字段、责任人映射,还是审核结论备注?
建议记录答案:
- 允许校订的内容:
- 不允许业务侧直接改的内容:
为什么要问:
- 决定后台管理页具体有哪些编辑入口。
### Q17 知识库更新后,是否需要重新审核历史项目?
建议提问方式:
> 如果法规规则或模板更新了,历史项目是否需要按新规则重新检查,还是仅影响新项目?
建议记录答案:
- 仅影响新项目:
- 历史项目需要重跑:
- 是否保留旧版本结果:
为什么要问:
- 决定规则版本管理和历史审计口径。
---
## 4.6 飞书接入与责任人通知
### Q18 飞书群聊机器人里,业务最希望怎么触发任务?
建议提问方式:
> 在飞书群聊里,您更希望通过固定命令触发,还是通过菜单、卡片按钮、表单方式选择任务?
建议记录答案:
- 命令式:
- 卡片式:
- 菜单式:
- 其他偏好:
为什么要问:
- 决定飞书端交互体验复杂度。
### Q19 飞书里“结果查看”希望看到多详细?
建议提问方式:
> 在飞书里,您更希望看到简短摘要,还是希望直接看到缺失项、风险等级、责任人、下载链接这些完整信息?
建议记录答案:
- 仅摘要:
- 摘要 + 关键问题:
- 尽可能完整:
为什么要问:
- 决定飞书卡片内容设计。
### Q20 责任人是如何定义的?
建议提问方式:
> 责任人是按章节点、按资料类型、按项目角色,还是按项目具体人员来分配?
建议记录答案:
- 按章节点:
- 按资料类型:
- 按项目角色:
- 按具体人员:
为什么要问:
- 决定责任人配置表结构。
### Q21 责任人通知只要 `@人`,还是还要带整改动作?
建议提问方式:
> 飞书通知时,您是只希望 `@` 到对应责任人,还是希望同时带上问题摘要、整改建议、资料链接和截止时间?
建议记录答案:
- 只需 `@人`
- 需要问题摘要:
- 需要整改建议:
- 需要截止时间:
为什么要问:
- 决定飞书通知卡片结构。
### Q22 是否需要支持多人负责同一项问题?
建议提问方式:
> 同一章节点或同一风险项,是否可能对应多个责任人,例如主责人 + 复核人?
建议记录答案:
- 是否支持多人:
- 是否区分主责 / 协同 / 复核:
为什么要问:
- 决定责任人映射是否是一对一还是一对多。
---
## 4.7 审计、追溯与管理要求
### Q23 业务最关心审计记录里看到什么?
建议提问方式:
> 如果后续回看一次审核记录,您最希望一眼看到哪些信息?
建议引导业务回答:
1. 审核时间
2. 审核人
3. 使用的资料范围
4. 风险等级
5. 不通过原因
6. 导出文件
7. 飞书通知记录
建议记录答案:
- 必看信息:
- 次要信息:
为什么要问:
- 决定审计列表和详情页优先展示字段。
### Q24 是否需要区分“系统自动结论”和“人工修改后结论”?
建议提问方式:
> 如果系统先给出自动判断,后续又经过人工校订或人工确认,是否需要把这两个结果区分记录?
建议记录答案:
- 是否区分:
- 是否保留修改原因:
- 是否保留修改人:
为什么要问:
- 决定审计模型是否需要版本链。
---
## 4.8 Demo 演示与验收方式
### Q25 Demo 现场最希望看到哪条完整链路?
建议提问方式:
> 如果现场只演示一条最完整的链路,您最希望看到哪一种?
建议给业务三个选项帮助回答:
1. 导入资料 -> 自动检查缺失 -> 输出风险报告
2. 导入资料 -> 抽取字段 -> 自动生成 Word
3. 飞书群聊触发 -> 查看结果 -> 通知责任人
建议记录答案:
- 首选演示链路:
- 次选演示链路:
为什么要问:
- 决定 Demo 讲解脚本和开发优先级。
### Q26 本次 Demo 的“通过标准”是什么?
建议提问方式:
> 对您来说,这次 Demo 达到什么程度,就可以认为是比较成功的?
建议引导业务从以下角度回答:
1. 能看出业务理解到位
2. 能自动发现主要问题
3. 能自动生成较完整材料
4. 能在飞书里顺畅演示
5. 能展示后台治理能力
建议记录答案:
- 必须达到:
- 加分项:
- 本次不强求:
为什么要问:
- 这是项目收敛范围的总开关。
---
## 5. 建议的会议记录模板
建议你每次和业务沟通时,按下面格式记录:
```text
问题编号:
问题主题:
业务回答:
是否已明确:是 / 否
当前默认口径:
是否影响需求文档:是 / 否
是否影响设计文档:是 / 否
需要补充材料:
备注:
```
---
## 6. 沟通优先级建议
如果时间有限,建议优先确认以下 8 个问题:
1. Q4 Demo 是否允许以第 1 章作为主演示材料
2. Q6 当前混档样例是否是刻意异常
3. Q7 哪些结论可自动判、哪些必须人工复核
4. Q10 “可直接报送级版式”的验收标准
5. Q12 新模板进入模板库后的确认机制
6. Q18 飞书群聊机器人希望如何触发任务
7. Q20 责任人如何定义
8. Q26 本次 Demo 的通过标准
---
## 7. 结论
这份问答清单的目标,不是把所有问题一次性问完,而是帮助你优先确认真正影响范围和验收的关键信息。
如果后续拿到新的业务回答,建议同步更新:
1. `docs/需求分析/0.需求重构总览与待确认事项.md`
2. `docs/需求分析/1.V1总需求文档.md`
3. 对应模块需求分析文档
4. 后续新增的设计文档

View File

@@ -3,54 +3,71 @@
{% block title %}审计日志详情{% endblock %}
{% block content %}
<header class="page-header">
<span class="eyebrow">日志详情</span>
<section class="page-header">
<span class="eyebrow">Audit Snapshot</span>
<h1 class="page-title">审计日志 #{{ log.id }}</h1>
<p class="page-lead">这里集中展示当前请求的输入、模型输出、知识库引用和工具调用记录</p>
<p style="margin-top: 14px;"><a class="button" href="{% url 'audit:list' %}">返回审计列表</a></p>
</header>
<p class="page-lead">详情页集中展示当前请求的输入、结构化输出、引用来源、工具调用和原始输出,用来解释这一轮 Agent 执行到底做了什么</p>
<div class="button-row">
<a class="button" href="{% url 'audit:list' %}">返回审计列表</a>
<a class="button" href="{% url 'platform_ui:command-center' %}">返回工作台大屏</a>
</div>
</section>
<section class="stack">
<article class="panel">
<h2 class="section-title">基础信息</h2>
<ul class="meta-list">
<li class="meta-badge">场景:{{ log.scenario_name }}</li>
<li class="meta-badge">状态:{{ log.status }}</li>
<li class="meta-badge">模型:{{ log.model_name }}</li>
<li class="meta-badge">耗时:{{ log.latency_ms }} ms</li>
</ul>
<section class="hero-metrics">
<article class="metric-card">
<div class="metric-label">场景</div>
<div class="metric-value">{{ log.scenario_name }}</div>
</article>
<article class="panel">
<h2 class="section-title">用户输入</h2>
<div class="detail-item">{{ log.user_input|linebreaksbr }}</div>
<article class="metric-card">
<div class="metric-label">状态</div>
<div class="metric-value">{{ log.get_status_display_text }}</div>
</article>
<article class="panel">
<h2 class="section-title">最终回答</h2>
<div class="detail-item">{{ log.final_answer|linebreaksbr }}</div>
<article class="metric-card">
<div class="metric-label">耗时</div>
<div class="metric-value">{{ log.latency_ms }} ms</div>
</article>
</section>
<article class="panel">
<h2 class="section-title">结构化输出</h2>
<pre class="code-block">{{ log.structured_output }}</pre>
</article>
<article class="panel">
<h2 class="section-title">引用来源</h2>
<pre class="code-block">{{ log.retrieved_chunks }}</pre>
</article>
<article class="panel">
<h2 class="section-title">工具调用</h2>
<pre class="code-block">{{ log.tool_calls }}</pre>
</article>
{% if log.error_message %}
<section class="layout-two-columns">
<div class="stack">
<article class="panel">
<h2 class="section-title">错误信息</h2>
<pre class="code-block">{{ log.error_message }}</pre>
<h2 class="section-title">用户输入</h2>
<div class="detail-item">{{ log.user_input|linebreaksbr }}</div>
</article>
{% endif %}
<article class="panel">
<h2 class="section-title">最终回答</h2>
<div class="detail-item">{{ log.final_answer|linebreaksbr }}</div>
</article>
<article class="panel">
<h2 class="section-title">结构化输出</h2>
<pre class="code-block">{{ log.structured_output }}</pre>
</article>
</div>
<div class="stack">
<article class="panel">
<h2 class="section-title">引用来源</h2>
<pre class="code-block">{{ log.retrieved_chunks }}</pre>
</article>
<article class="panel">
<h2 class="section-title">工具调用</h2>
<pre class="code-block">{{ log.tool_calls }}</pre>
</article>
<article class="panel">
<h2 class="section-title">原始输出</h2>
<pre class="code-block">{{ log.raw_output }}</pre>
</article>
{% if log.error_message %}
<article class="panel">
<h2 class="section-title">错误信息</h2>
<pre class="code-block">{{ log.error_message }}</pre>
</article>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -3,48 +3,76 @@
{% block title %}审计日志{% endblock %}
{% block content %}
<header class="page-header">
<span class="eyebrow">执行留痕</span>
<h1 class="page-title">审计日志</h1>
<p class="page-lead">每次 Agent 执行都会记录模型、检索片段、工具调用和最终结果,方便演示链路可解释</p>
<section class="page-header">
<span class="eyebrow">Audit Trail</span>
<h1 class="page-title">审计日志与执行留痕中心</h1>
<p class="page-lead">每次 Agent 执行都会保留输入、结构化结果、引用片段、工具调用和最终输出。这个页面用于说明系统为何可追溯、可复核、可解释。</p>
{% if selected_scenario_id %}
<p style="margin-top: 14px;">
<span class="meta-badge">当前筛选场景:{{ selected_scenario_id }}</span>
<div class="badge-row">
<span class="pill pill-accent">当前筛选场景:{{ selected_scenario_id }}</span>
<a class="button" href="{% url 'audit:list' %}">清空筛选</a>
</p>
</div>
{% endif %}
</header>
</section>
<article class="panel">
<table class="kv-table">
<thead>
<tr>
<th>ID</th>
<th>场景</th>
<th>输入摘要</th>
<th>状态</th>
<th>模型</th>
<th>耗时</th>
<th>创建时间</th>
<th>详情</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<section class="hero-metrics">
<article class="metric-card">
<div class="metric-label">日志总数</div>
<div class="metric-value">{{ logs|length }}</div>
<div class="metric-note">当前页面加载的执行快照数量。</div>
</article>
<article class="metric-card">
<div class="metric-label">最近状态</div>
<div class="metric-value">{% if logs %}{{ logs.0.get_status_display_text }}{% else %}暂无{% endif %}</div>
<div class="metric-note">默认按时间倒序展示最近一次 Agent 执行。</div>
</article>
<article class="metric-card">
<div class="metric-label">最近场景</div>
<div class="metric-value">{% if logs %}{{ logs.0.scenario_name }}{% else %}暂无{% endif %}</div>
<div class="metric-note">便于快速定位当前复试演示对应的执行记录。</div>
</article>
</section>
<section class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">执行快照列表</h2>
<p class="section-copy">保留真实审计数据列表,同时把展示形式升级为与首页、大屏一致的分析板风格。</p>
</div>
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<td>{{ log.id }}</td>
<td>{{ log.scenario_name }}</td>
<td>{{ log.get_user_input_summary }}</td>
<td>{{ log.get_status_display_text }}</td>
<td>{{ log.model_name }}</td>
<td>{{ log.latency_ms }} ms</td>
<td>{{ log.created_at|date:"Y-m-d H:i" }}</td>
<td><a class="button" href="{% url 'audit:detail' log.id %}">查看详情</a></td>
<th>ID</th>
<th>场景</th>
<th>输入摘要</th>
<th>状态</th>
<th>模型</th>
<th>耗时</th>
<th>创建时间</th>
<th>详情</th>
</tr>
{% empty %}
<tr><td colspan="8">暂无审计日志,先去执行一次对话吧。</td></tr>
{% endfor %}
</tbody>
</table>
</article>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ log.id }}</td>
<td>{{ log.scenario_name }}</td>
<td>{{ log.get_user_input_summary }}</td>
<td>
<span class="pill {% if log.status == 'success' %}pill-success{% else %}pill-danger{% endif %}">{{ log.get_status_display_text }}</span>
</td>
<td>{{ log.model_name }}</td>
<td>{{ log.latency_ms }} ms</td>
<td>{{ log.created_at|date:"Y-m-d H:i" }}</td>
<td><a class="button" href="{% url 'audit:detail' log.id %}">查看详情</a></td>
</tr>
{% empty %}
<tr><td colspan="8">暂无审计日志,先去执行一次审核工作台任务。</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@@ -3,349 +3,387 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Universal Agent Demo Framework{% endblock %}</title>
<title>{% block title %}注册审核智能体平台{% endblock %}</title>
<style>
:root {
--bg: #f4f1ea;
--surface: #fffdf8;
--surface-strong: #fff7ea;
--border: #d7c9b0;
--text: #2f261d;
--muted: #73614f;
--accent: #a54c2b;
--accent-soft: #f1d2b8;
--success: #2b6a4d;
--warning: #9a5a00;
--danger: #8d2f2f;
--shadow: 0 18px 40px rgba(91, 63, 36, 0.10);
--bg: #f5f7fa;
--surface: #ffffff;
--surface-soft: #f8fafc;
--border: #d8e0e8;
--text: #1f2d3d;
--muted: #6a7a8b;
--primary: #2f6fec;
--primary-soft: #eef4ff;
--success: #1f8f5f;
--success-soft: #ecfaf3;
--warning: #c97a16;
--warning-soft: #fff5e8;
--danger: #cc4b3e;
--danger-soft: #fff1ef;
--shadow: 0 8px 24px rgba(31, 45, 61, 0.06);
--radius: 14px;
--radius-lg: 18px;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--text);
background:
radial-gradient(circle at top left, #f7e2c8 0, transparent 30%),
linear-gradient(180deg, #f6efe2 0%, #f4f1ea 45%, #efe7da 100%);
min-height: 100vh;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.shell {
width: min(1180px, calc(100vw - 32px));
margin: 0 auto;
padding: 24px 0 40px;
background: var(--bg);
}
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; }
.topbar {
position: sticky;
top: 0;
z-index: 10;
background: rgba(255, 255, 255, 0.95);
border-bottom: 1px solid var(--border);
backdrop-filter: blur(8px);
}
.topbar-inner {
width: min(1680px, calc(100vw - 40px));
margin: 0 auto;
min-height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
align-items: center;
margin-bottom: 24px;
padding: 18px 20px;
border: 1px solid rgba(215, 201, 176, 0.7);
background: rgba(255, 253, 248, 0.88);
backdrop-filter: blur(12px);
border-radius: 22px;
box-shadow: var(--shadow);
}
.brand-title {
margin: 0;
font-size: 1.35rem;
font-weight: 700;
}
.brand-note {
margin: 6px 0 0;
color: var(--muted);
font-size: 0.95rem;
}
.nav-links {
.brand {
display: flex;
gap: 10px;
align-items: center;
gap: 12px;
min-width: 0;
}
.brand-mark {
width: 36px;
height: 36px;
border-radius: 10px;
background: var(--primary);
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
flex: none;
}
.brand h1 {
margin: 0;
font-size: 1rem;
line-height: 1.2;
}
.brand p {
margin: 2px 0 0;
color: var(--muted);
font-size: 0.82rem;
}
.topnav {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.topnav a {
padding: 9px 12px;
border-radius: 10px;
color: var(--muted);
font-size: 0.92rem;
}
.topnav a:hover {
background: var(--surface-soft);
color: var(--text);
}
.nav-link,
.button,
button {
.page {
width: min(1680px, calc(100vw - 40px));
margin: 24px auto 40px;
display: grid;
gap: 16px;
}
.page-header {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 22px 24px;
box-shadow: var(--shadow);
display: grid;
gap: 10px;
}
.eyebrow {
display: inline-flex;
width: fit-content;
padding: 4px 10px;
border-radius: 999px;
background: var(--primary-soft);
color: var(--primary);
font-size: 0.78rem;
font-weight: 700;
}
.page-title {
margin: 0;
font-size: 1.9rem;
line-height: 1.2;
}
.page-lead {
margin: 0;
color: var(--muted);
line-height: 1.65;
}
.panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 18px;
box-shadow: var(--shadow);
}
.section-heading {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
margin-bottom: 14px;
}
.section-title {
margin: 0;
font-size: 1.08rem;
line-height: 1.35;
}
.section-copy, .muted, .help-text {
color: var(--muted);
line-height: 1.6;
font-size: 0.92rem;
}
.grid-2 {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.workspace-grid {
display: grid;
grid-template-columns: 360px minmax(0, 1fr);
gap: 16px;
align-items: start;
}
.workspace-grid-wide {
display: grid;
grid-template-columns: minmax(0, 1.45fr) minmax(420px, 0.85fr);
gap: 16px;
align-items: start;
}
.stack { display: grid; gap: 14px; }
.metric-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.metric-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px;
}
.metric-label {
color: var(--muted);
font-size: 0.8rem;
}
.metric-value {
margin-top: 8px;
font-size: 1.55rem;
font-weight: 700;
}
.metric-note {
margin-top: 6px;
color: var(--muted);
font-size: 0.86rem;
line-height: 1.5;
}
.button-row, .badge-row, .meta-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0;
padding: 0;
list-style: none;
}
.button, button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border-radius: 999px;
min-height: 38px;
padding: 0 14px;
border-radius: 10px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
padding: 10px 16px;
font-size: 0.95rem;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.button-primary,
button {
border-color: var(--accent);
background: linear-gradient(135deg, #b9562f, #94452a);
color: #fffaf4;
box-shadow: 0 10px 22px rgba(165, 76, 43, 0.20);
.button-primary, button {
background: var(--primary);
color: #fff;
border-color: var(--primary);
}
.nav-link:hover,
.button:hover,
button:hover {
text-decoration: none;
transform: translateY(-1px);
box-shadow: 0 12px 24px rgba(91, 63, 36, 0.12);
}
.page-header {
margin-bottom: 24px;
}
.eyebrow {
display: inline-block;
margin-bottom: 10px;
padding: 6px 12px;
border-radius: 999px;
background: rgba(241, 210, 184, 0.8);
color: var(--accent);
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 0.04em;
}
.page-title {
margin: 0 0 10px;
font-size: clamp(2rem, 3vw, 3rem);
line-height: 1.1;
}
.page-lead {
margin: 0;
color: var(--muted);
font-size: 1rem;
max-width: 720px;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 18px;
}
.panel,
.card {
background: rgba(255, 253, 248, 0.94);
border: 1px solid rgba(215, 201, 176, 0.85);
border-radius: 24px;
padding: 20px;
box-shadow: var(--shadow);
}
.card h2,
.panel h2,
.panel h3 {
margin-top: 0;
}
.meta-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 12px 0 0;
padding: 0;
list-style: none;
color: var(--muted);
font-size: 0.92rem;
}
.meta-badge {
.pill, .meta-badge, .status {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
padding: 5px 10px;
border-radius: 999px;
background: var(--surface-strong);
border: 1px solid rgba(215, 201, 176, 0.75);
}
.layout-two-columns {
display: grid;
grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
gap: 20px;
align-items: start;
}
.stack {
display: grid;
gap: 16px;
}
textarea,
select,
input[type="text"],
input[type="file"] {
width: 100%;
border-radius: 16px;
border: 1px solid var(--border);
background: #fff;
padding: 12px 14px;
font: inherit;
color: var(--text);
background: var(--surface-soft);
font-size: 0.82rem;
}
.pill-accent { color: var(--primary); background: var(--primary-soft); border-color: #d8e5ff; }
.pill-success, .status-success { color: var(--success); background: var(--success-soft); border-color: #ccebdc; }
.pill-signal { color: var(--warning); background: var(--warning-soft); border-color: #f3dcc0; }
.pill-danger, .status-failed { color: var(--danger); background: var(--danger-soft); border-color: #f2d3cf; }
textarea { min-height: 150px; resize: vertical; }
label {
display: block;
margin-bottom: 8px;
font-weight: 700;
}
.help-text,
.muted {
color: var(--muted);
font-size: 0.92rem;
}
.checkbox-list {
.simple-list, .detail-list, .risk-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
gap: 8px;
margin-top: 10px;
}
.checkbox-item {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 10px 12px;
border-radius: 16px;
border: 1px solid rgba(215, 201, 176, 0.85);
}
.detail-item, .risk-item {
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface-soft);
line-height: 1.6;
}
.detail-item strong, .risk-item strong { display: block; margin-bottom: 4px; }
textarea, select, input[type="text"], input[type="file"] {
width: 100%;
border: 1px solid var(--border);
border-radius: 10px;
background: #fff;
}
.status {
display: inline-flex;
align-items: center;
padding: 6px 12px;
border-radius: 999px;
font-size: 0.9rem;
font-weight: 700;
background: var(--surface-strong);
color: var(--accent);
}
.status-success { color: var(--success); }
.status-failed { color: var(--danger); }
.notice {
padding: 14px 16px;
border-radius: 18px;
border: 1px solid rgba(215, 201, 176, 0.85);
background: rgba(255, 247, 234, 0.92);
padding: 11px 12px;
color: var(--text);
}
.notice-error {
border-color: rgba(141, 47, 47, 0.25);
background: rgba(255, 238, 238, 0.95);
color: var(--danger);
textarea { min-height: 140px; resize: vertical; line-height: 1.6; }
label { display: block; margin-bottom: 8px; font-weight: 600; }
.checkbox-list { display: grid; gap: 8px; }
.checkbox-item {
display: grid;
grid-template-columns: 20px minmax(0, 1fr);
gap: 10px;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--surface-soft);
}
.kv-table {
.table-wrap { overflow-x: auto; }
.data-table, .kv-table {
width: 100%;
border-collapse: collapse;
}
.kv-table th,
.kv-table td {
.data-table th, .data-table td, .kv-table th, .kv-table td {
padding: 12px 10px;
border-bottom: 1px solid rgba(215, 201, 176, 0.6);
vertical-align: top;
text-align: left;
border-bottom: 1px solid #e9eef3;
vertical-align: top;
line-height: 1.55;
}
.kv-table th {
width: 150px;
.data-table th, .kv-table th {
color: var(--muted);
font-size: 0.82rem;
font-weight: 700;
background: var(--surface-soft);
white-space: nowrap;
}
.nowrap { white-space: nowrap; }
.cell-min-220 { min-width: 220px; }
.cell-min-280 { min-width: 280px; }
.detail-list {
display: grid;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
.notice {
padding: 12px 14px;
border-radius: 10px;
border: 1px solid #d8e5ff;
background: var(--primary-soft);
color: var(--text);
}
.detail-item {
padding: 14px 16px;
border-radius: 18px;
background: #fff;
border: 1px solid rgba(215, 201, 176, 0.75);
.notice-error {
border-color: #f2d3cf;
background: var(--danger-soft);
color: var(--danger);
}
.detail-item strong {
display: block;
margin-bottom: 6px;
}
.code-block {
margin: 0;
padding: 14px;
border-radius: 18px;
background: #2f261d;
color: #fdf8f1;
overflow: auto;
padding: 12px;
border-radius: 10px;
background: #1f2937;
color: #f9fafb;
overflow-x: auto;
font-size: 0.84rem;
line-height: 1.55;
white-space: pre-wrap;
word-break: break-word;
font-size: 0.92rem;
line-height: 1.6;
}
.section-title {
margin: 0 0 12px;
font-size: 1.1rem;
.link-card {
display: block;
padding: 16px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface);
}
.link-card h3 { margin: 0 0 6px; font-size: 1rem; }
.link-card p { margin: 0; color: var(--muted); font-size: 0.9rem; line-height: 1.55; }
@media (max-width: 900px) {
.layout-two-columns { grid-template-columns: 1fr; }
.topbar { flex-direction: column; align-items: flex-start; }
.nav-links { justify-content: flex-start; }
@media (max-width: 1024px) {
.metric-grid, .grid-2, .grid-3, .workspace-grid, .workspace-grid-wide { grid-template-columns: 1fr; }
.topbar-inner { flex-direction: column; align-items: flex-start; padding: 12px 0; }
.topnav { justify-content: flex-start; }
}
</style>
</head>
<body>
<div class="shell">
<header class="topbar">
<div>
<p class="brand-title">Universal Agent Demo Framework</p>
<p class="brand-note">面向复试演示的可配置 AI Agent 单体系统</p>
<header class="topbar">
<div class="topbar-inner">
<div class="brand">
<div class="brand-mark">RA</div>
<div>
<h1>注册审核智能体平台</h1>
<p>极简后台原型</p>
</div>
</div>
<nav class="nav-links">
<a class="nav-link" href="{% url 'scenarios:index' %}">场景首页</a>
<a class="nav-link" href="{% url 'documents:list' %}">中心</a>
<a class="nav-link" href="{% url 'audit:list' %}">计日志</a>
<nav class="topnav">
<a href="{% url 'scenarios:index' %}">总览</a>
<a href="{% url 'documents:list' %}">中心</a>
<a href="{% url 'chat:index' 'document_review' %}">核工作台</a>
<a href="{% url 'platform_ui:knowledge-base' %}">知识库</a>
<a href="{% url 'platform_ui:mcp-center' %}">MCP</a>
<a href="{% url 'platform_ui:skills' %}">Skills</a>
<a href="{% url 'platform_ui:command-center' %}">工作台</a>
<a href="{% url 'audit:list' %}">审计</a>
</nav>
</header>
</div>
</header>
<main class="page">
{% if messages %}
<section class="stack" style="margin-bottom: 18px;">
<div class="stack">
{% for message in messages %}
<div class="notice{% if message.tags == 'error' %} notice-error{% endif %}">{{ message }}</div>
{% endfor %}
</section>
</div>
{% endif %}
{% block content %}{% endblock %}
</div>
</main>
</body>
</html>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}{{ scenario.name|default:"Agent 对话" }}{% endblock %}
{% block title %}{{ scenario.name|default:"Agent 审核工作台" }}{% endblock %}
{% block content %}
{% if error %}
@@ -8,23 +8,25 @@
{% endif %}
{% if scenario %}
<header class="page-header">
<span class="eyebrow">Agent 对话</span>
<section class="page-header">
<span class="eyebrow">Workspace</span>
<h1 class="page-title">{{ scenario.name }}</h1>
<p class="page-lead">{{ scenario.description }}</p>
<ul class="meta-list">
<li class="meta-badge">角色:{{ scenario.agent.role }}</li>
<li class="meta-badge">目标{{ scenario.agent.goal }}</li>
<li class="meta-badge">已入库文档:{{ document_count }}</li>
<li class="meta-badge">输出类型:{{ scenario.output.type }}</li>
</ul>
</header>
<p class="page-lead">左侧输入问题和选择文档,右侧查看执行结果。</p>
<div class="badge-row">
<span class="pill pill-accent">已入库文档:{{ document_count }}</span>
<span class="pill">输出{{ scenario.output.type }}</span>
</div>
</section>
<section class="layout-two-columns">
<section class="workspace-grid">
<div class="stack">
<article class="panel">
<h2 class="section-title">提问面板</h2>
<p class="muted">可以直接提问,也可以勾选部分已入库文档作为当前上下文范围。</p>
<div class="section-heading">
<div>
<h2 class="section-title">任务输入与资料范围</h2>
<p class="section-copy">左侧突出受控输入:先描述审核目标,再限定本轮使用的文档范围。</p>
</div>
</div>
<form method="post" class="stack">
{% csrf_token %}
<div>
@@ -36,7 +38,7 @@
</div>
<div>
{{ form.document_ids.label_tag }}
<p class="help-text">不勾选时默认检索当前场景全部已入库文档。</p>
<p class="help-text">不勾选时默认使用全部已入库文档。</p>
<div class="checkbox-list">
{% for checkbox in form.document_ids %}
<label class="checkbox-item">
@@ -51,34 +53,25 @@
<p class="notice notice-error">{{ form.document_ids.errors|join:" " }}</p>
{% endif %}
</div>
<div>
<div class="button-row">
<button type="submit">提交问题并执行 Agent</button>
</div>
</form>
</article>
<article class="panel">
<h2 class="section-title">执行说明</h2>
<h2 class="section-title">快捷示例</h2>
<ul class="detail-list">
<li class="detail-item">
<strong>1. 场景配置</strong>
系统会先读取当前 YAML 场景配置,确定角色、目标、工具和输出结构。
</li>
<li class="detail-item">
<strong>2. RAG 与工具</strong>
如果场景启用了知识库检索,系统会根据你的问题召回相关片段,并执行声明式工具。
</li>
<li class="detail-item">
<strong>3. 结构化结果</strong>
Agent Core 会优先解析 JSON 输出,解析失败时回退为稳定的展示结构。
</li>
<li class="detail-item">检查当前资料是否存在缺失项</li>
<li class="detail-item">抽取说明书中的关键字段</li>
<li class="detail-item">比较两份文档中的产品名称是否一致</li>
</ul>
</article>
</div>
<div class="stack">
<article class="panel">
<h2 class="section-title">回答总览</h2>
<h2 class="section-title">结果</h2>
{% if result %}
<ul class="meta-list">
<li class="meta-badge">模型:{{ result.model_name }}</li>
@@ -89,41 +82,18 @@
<strong>主回答</strong>
<div>{{ result.answer|linebreaksbr }}</div>
</div>
{% if audit_log %}
<p style="margin-top: 14px;">
<a class="button" href="{% url 'audit:detail' audit_log.id %}">查看本次审计日志</a>
</p>
{% endif %}
{% else %}
<div class="notice">提交问题后,这里会展示 Agent 的主回答、模型信息和执行状态。</div>
<div class="notice">提交任务后,这里会展示 Agent 的执行状态、主回答和过程摘要</div>
{% endif %}
</article>
{% if result %}
<article class="panel">
<h2 class="section-title">结构化结果</h2>
<table class="kv-table">
<tbody>
{% for key, value in result.structured_output.items %}
<tr>
<th>{{ key }}</th>
<td>
{% if key == "answer" or key == "summary" or key == "reply" %}
{{ value|linebreaksbr }}
{% else %}
<pre class="code-block">{{ value }}</pre>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</article>
<article class="panel">
<h2 class="section-title">引用片段</h2>
<h2 class="section-title">证据引用与工具调用</h2>
<p class="muted" style="margin-bottom: 14px;">引用片段与工具调用用于支撑结果可解释性。</p>
{% if result.references %}
<ul class="detail-list">
<h3 style="margin-top: 0;">引用片段</h3>
<ul class="detail-list" style="margin-bottom: 16px;">
{% for reference in result.references %}
<li class="detail-item">
<strong>{{ reference.source }}</strong>
@@ -132,13 +102,11 @@
{% endfor %}
</ul>
{% else %}
<div class="notice">当前回答没有引用知识库片段。</div>
<div class="notice" style="margin-bottom: 16px;">当前回答没有引用知识库片段。</div>
{% endif %}
</article>
<article class="panel">
<h2 class="section-title">工具调用</h2>
{% if result.tool_calls %}
<h3>工具调用</h3>
<ul class="detail-list">
{% for tool_call in result.tool_calls %}
<li class="detail-item">
@@ -164,6 +132,50 @@
{% endif %}
{% endif %}
</div>
<div class="stack">
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">结构化审核结果</h2>
<p class="section-copy">右侧结果舱用于展示缺失项、冲突项、字段池结果或风险清单。</p>
</div>
</div>
{% if result %}
<table class="kv-table">
<caption style="text-align:left; padding-bottom:12px; color:var(--ink-soft);">结构化结果</caption>
<tbody>
{% for key, value in result.structured_output.items %}
<tr>
<th>{{ key }}</th>
<td>
{% if key == "answer" or key == "summary" or key == "reply" %}
{{ value|linebreaksbr }}
{% else %}
<pre class="code-block">{{ value }}</pre>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="notice">执行任务后,这里会展示结构化审核结果和回填准备信息。</div>
{% endif %}
</article>
<article class="panel">
<h2 class="section-title">引用与审计</h2>
<ul class="detail-list">
<li class="detail-item">可查看引用片段、工具调用和本次审计日志。</li>
</ul>
{% if audit_log %}
<div class="button-row" style="margin-top: 16px;">
<a class="button" href="{% url 'audit:detail' audit_log.id %}">查看本次审计日志</a>
</div>
{% endif %}
</article>
</div>
</section>
{% endif %}
{% endblock %}

View File

@@ -1,54 +1,108 @@
{% extends "base.html" %}
{% block title %}文中心{% endblock %}
{% block title %}文中心{% endblock %}
{% block content %}
<header class="page-header">
<span class="eyebrow">知识库资料</span>
<h1 class="page-title">中心</h1>
<p class="page-lead">上传题目材料后,可以在这里管理文件状态,并手动触发入库</p>
<p style="margin-top: 14px;"><a class="button button-primary" href="{% url 'documents:upload' %}">上传新文件</a></p>
</header>
<section class="page-header">
<span class="eyebrow">Documents</span>
<h1 class="page-title">中心</h1>
<p class="page-lead">上传资料、查看状态、执行入库。页面只保留最常用操作</p>
<div class="button-row">
<a class="button button-primary" href="{% url 'documents:upload' %}">上传文件</a>
</div>
</section>
<article class="panel">
<table class="kv-table">
<thead>
<tr>
<th>文件名</th>
<th>场景</th>
<th>类型</th>
<th>大小</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for document in documents %}
<section class="metric-grid">
<article class="metric-card">
<div class="metric-label">文件总数</div>
<div class="metric-value">{{ status_counts.total }}</div>
</article>
<article class="metric-card">
<div class="metric-label">已完成入库</div>
<div class="metric-value">{{ status_counts.indexed }}</div>
</article>
<article class="metric-card">
<div class="metric-label">待入库</div>
<div class="metric-value">{{ status_counts.uploaded }}</div>
</article>
<article class="metric-card">
<div class="metric-label">失败</div>
<div class="metric-value">{{ status_counts.failed }}</div>
</article>
</section>
<section class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">异常提示</h2>
<p class="section-copy">只保留需要处理的异常。</p>
</div>
</div>
<ul class="risk-list">
{% for item in exception_items %}
<li class="risk-item">
<strong>{{ item.title }}</strong>
<div class="muted">{{ item.detail }}</div>
</li>
{% endfor %}
</ul>
</section>
<section class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">资料目录总览</h2>
<p class="section-copy">页面下方保留真实文件记录与手动入库动作,保证演示原型仍基于当前系统能力运行。</p>
</div>
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<td>{{ document.original_name }}</td>
<td>{{ document.scenario_id }}</td>
<td>{{ document.file_type }}</td>
<td>{{ document.size }}</td>
<td>{{ document.get_status_display_text }}</td>
<td>
{% if document.status != "indexed" %}
<form action="{% url 'documents:index' document.id %}" method="post">
{% csrf_token %}
<button type="submit">执行入库</button>
</form>
{% else %}
<span class="status status-success">已可用于检索</span>
{% endif %}
{% if document.error_message %}
<pre class="code-block" style="margin-top: 10px;">{{ document.error_message }}</pre>
{% endif %}
<p class="muted" style="margin-top: 10px;">上传时间:{{ document.created_at|date:"Y-m-d H:i" }}</p>
</td>
<th>文件名</th>
<th>场景</th>
<th>类型</th>
<th>大小</th>
<th>状态</th>
<th>操作与备注</th>
</tr>
{% empty %}
<tr><td colspan="6">暂无文件,请先上传题目材料。</td></tr>
{% endfor %}
</tbody>
</table>
</article>
</thead>
<tbody>
{% for document in documents %}
<tr>
<td>{{ document.original_name }}</td>
<td>{{ document.scenario_id }}</td>
<td>{{ document.file_type }}</td>
<td>{{ document.size }}</td>
<td>
<span class="pill {% if document.status == 'indexed' %}pill-success{% elif document.status == 'failed' %}pill-danger{% else %}pill-signal{% endif %}">
{{ document.get_status_display_text }}
</span>
</td>
<td>
<div class="stack">
{% if document.status != "indexed" %}
<form action="{% url 'documents:index' document.id %}" method="post">
{% csrf_token %}
<button type="submit">执行入库</button>
</form>
{% else %}
<span class="status status-success">已可参与检索与审核</span>
{% endif %}
{% if document.error_message %}
<pre class="code-block">{{ document.error_message }}</pre>
{% endif %}
<span class="muted">上传时间:{{ document.created_at|date:"Y-m-d H:i" }}</span>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6">暂无文件,请先导入申报资料或法规原文。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@@ -1,38 +1,63 @@
{% extends "base.html" %}
{% block title %}上传文件{% endblock %}
{% block title %}导入资料{% endblock %}
{% block content %}
<header class="page-header">
<span class="eyebrow">文件上传</span>
<h1 class="page-title">上传题目材料或知识库文档</h1>
<p class="page-lead">支持 `.txt`、`.md`、`.pdf` 和 `.docx`。上传后可以在文档中心手动执行入库</p>
</header>
<section class="page-header">
<span class="eyebrow">Batch Intake</span>
<h1 class="page-title">导入申报资料与法规依据文件</h1>
<p class="page-lead">上传页采用“引导式导入”思路,强调业务资料与法规依据资料的边界、目录类文件的优先级,以及上传后进入解析和切片流程的下一步动作</p>
</section>
<article class="panel" style="max-width: 760px;">
<form method="post" enctype="multipart/form-data" class="stack">
{% csrf_token %}
<div>
{{ form.scenario_id.label_tag }}
{{ form.scenario_id }}
{% if form.scenario_id.errors %}
<p class="notice notice-error">{{ form.scenario_id.errors|join:" " }}</p>
<section class="layout-two-columns">
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">资料导入向导</h2>
<p class="section-copy">当前支持 `.txt`、`.md`、`.pdf` 和 `.docx`。上传成功后即可回到文件中心执行解析与入库。</p>
</div>
</div>
<form method="post" enctype="multipart/form-data" class="stack">
{% csrf_token %}
<div>
{{ form.scenario_id.label_tag }}
{{ form.scenario_id }}
{% if form.scenario_id.errors %}
<p class="notice notice-error">{{ form.scenario_id.errors|join:" " }}</p>
{% endif %}
</div>
<div>
{{ form.file.label_tag }}
{{ form.file }}
{% if form.file.errors %}
<p class="notice notice-error">{{ form.file.errors|join:" " }}</p>
{% endif %}
</div>
{% if form.errors %}
<div class="notice notice-error">{{ form.errors }}</div>
{% endif %}
<div class="button-row">
<button type="submit">确认导入</button>
<a class="button" href="{% url 'documents:list' %}">返回文件中心</a>
</div>
</form>
</article>
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">上传前检查清单</h2>
<p class="section-copy">用业务语言告诉演示对象,平台并不是“随便上传,随便搜”。</p>
</div>
</div>
<div>
{{ form.file.label_tag }}
{{ form.file }}
{% if form.file.errors %}
<p class="notice notice-error">{{ form.file.errors|join:" " }}</p>
{% endif %}
<ul class="detail-list">
{% for item in upload_checks %}
<li class="detail-item">{{ item }}</li>
{% endfor %}
</ul>
<div class="notice" style="margin-top: 16px;">
建议先导入监管信息目录、申请表、说明书、产品列表和公告附件包,再进入完整性检查场景。
</div>
{% if form.errors %}
<div class="notice notice-error">{{ form.errors }}</div>
{% endif %}
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="submit">上传文件</button>
<a class="button" href="{% url 'documents:list' %}">返回文件列表</a>
</div>
</form>
</article>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}工作台大屏{% endblock %}
{% block content %}
<section class="page-header">
<span class="eyebrow">Workbench</span>
<h1 class="page-title">工作台</h1>
<p class="page-lead">用简洁视图展示当前批次状态、主要问题和下一步动作。</p>
</section>
<section class="metric-grid">
<article class="metric-card">
<div class="metric-label">当前阶段</div>
<div class="metric-value">{{ batch.stage }}</div>
</article>
<article class="metric-card">
<div class="metric-label">整体完成度</div>
<div class="metric-value">{{ batch.completion }}</div>
</article>
<article class="metric-card">
<div class="metric-label">高优先级问题</div>
<div class="metric-value">03</div>
</article>
</section>
<section class="grid-3">
<article class="panel">
<h2 class="section-title">流程</h2>
<div class="stack" style="margin-top: 14px;">
{% for step in workflow_overview %}
<div class="detail-item"><strong>{{ step.title }}</strong><div class="muted">{{ step.detail }}</div></div>
{% endfor %}
</div>
</article>
<article class="panel">
<h2 class="section-title">执行记录</h2>
<div class="stack" style="margin-top: 14px;">
{% for step in workflow_steps %}
<div class="detail-item"><strong>{{ step.time }} {{ step.title }}</strong><div class="muted">{{ step.detail }}</div></div>
{% endfor %}
</div>
</article>
<article class="panel">
<h2 class="section-title">待处理问题</h2>
<ul class="risk-list">
{% for risk in risk_board %}
<li class="risk-item">
<strong>{{ risk.title }}</strong>
<div class="muted">{{ risk.action|default:risk.detail }}</div>
</li>
{% endfor %}
</ul>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,198 @@
{% extends "base.html" %}
{% block title %}知识库配置{% endblock %}
{% block content %}
<section class="page-header">
<span class="eyebrow">Knowledge Base</span>
<h1 class="page-title">知识库配置</h1>
<p class="page-lead">支持传统增删改查:新增知识源、编辑规则项、删除无效配置、筛选和搜索当前知识内容。</p>
</section>
<section class="metric-grid">
{% for item in knowledge_stats %}
<article class="metric-card">
<div class="metric-label">{{ item.label }}</div>
<div class="metric-value">{{ item.value }}</div>
</article>
{% endfor %}
</section>
<section class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">操作栏</h2>
<p class="section-copy">把知识库做成传统后台,而不是只读展示页。</p>
</div>
<div class="button-row">
<a class="button button-primary" href="#">新增知识源</a>
<a class="button" href="#">新增规则项</a>
<a class="button" href="#">批量删除</a>
</div>
</div>
<div class="grid-3">
<div>
<label for="knowledge-search">搜索</label>
<input id="knowledge-search" type="text" value="体外诊断试剂" />
</div>
<div>
<label for="knowledge-type">知识类型</label>
<select id="knowledge-type">
{% for filter in knowledge_filters %}
<option{% if filter.active %} selected{% endif %}>{{ filter.label }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="knowledge-status">状态</label>
<select id="knowledge-status">
<option selected>全部状态</option>
<option>已生效</option>
<option>待人工校订</option>
<option>已入库</option>
</select>
</div>
</div>
</section>
<section class="workspace-grid-wide">
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">知识源管理</h2>
<p class="section-copy">传统 CRUD 列表,直接在表格里做查看、编辑、删除。</p>
</div>
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>编码</th>
<th>名称</th>
<th>类型</th>
<th>范围</th>
<th>状态</th>
<th>负责人</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for source in knowledge_sources %}
<tr>
<td class="nowrap">{{ source.code }}</td>
<td><div class="cell-min-280">{{ source.name }}</div></td>
<td class="nowrap">{{ source.type }}</td>
<td class="nowrap">{{ source.scope }}</td>
<td>
<span class="pill {% if source.status == '已生效' or source.status == '已入库' %}pill-success{% else %}pill-signal{% endif %}">
{{ source.status }}
</span>
</td>
<td class="nowrap">{{ source.owner }}</td>
<td class="nowrap">{{ source.updated_at }}</td>
<td>
<div class="button-row">
<a class="button" href="#">查看</a>
<a class="button" href="#">编辑</a>
<a class="button" href="#">删除</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</article>
<div class="stack">
<article class="panel">
<h2 class="section-title">{{ knowledge_form.title }}</h2>
<div class="stack" style="margin-top: 14px;">
{% for field in knowledge_form.fields %}
<div>
<label>{{ field.label }}</label>
<input type="text" value="{{ field.value }}" />
</div>
{% endfor %}
<div class="button-row">
<a class="button button-primary" href="#">保存知识源</a>
<a class="button" href="#">重置</a>
</div>
</div>
</article>
<article class="panel">
<h2 class="section-title">批量操作</h2>
<ul class="detail-list">
<li class="detail-item">支持批量启用 / 停用知识源</li>
<li class="detail-item">支持批量重建切片</li>
<li class="detail-item">支持批量删除过期规则</li>
</ul>
</article>
</div>
</section>
<section class="grid-2">
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">规则项管理</h2>
<p class="section-copy">规则项也用传统表格管理,支持新增、编辑、删除。</p>
</div>
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>编码</th>
<th>章节</th>
<th>规则名称</th>
<th>模板字段</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in rule_tree %}
<tr>
<td class="nowrap">{{ item.code }}</td>
<td class="nowrap">{{ item.chapter }}</td>
<td><div class="cell-min-220">{{ item.item }}</div></td>
<td><div class="cell-min-220">{{ item.field }}</div></td>
<td>
<span class="pill {% if item.status == '启用' %}pill-success{% else %}pill-signal{% endif %}">
{{ item.status }}
</span>
</td>
<td>
<div class="button-row">
<a class="button" href="#">查看</a>
<a class="button" href="#">编辑</a>
<a class="button" href="#">删除</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</article>
<article class="panel">
<h2 class="section-title">{{ rule_form.title }}</h2>
<div class="stack" style="margin-top: 14px;">
{% for field in rule_form.fields %}
<div>
<label>{{ field.label }}</label>
<input type="text" value="{{ field.value }}" />
</div>
{% endfor %}
<div class="button-row">
<a class="button button-primary" href="#">保存规则项</a>
<a class="button" href="#">删除规则项</a>
</div>
</div>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,67 @@
{% extends "base.html" %}
{% block title %}MCP 中心{% endblock %}
{% block content %}
<section class="page-header">
<span class="eyebrow">MCP Connectors</span>
<h1 class="page-title">外部 MCP 能力导入与协同接入中心</h1>
<p class="page-lead">MCP 页面说明平台不是封闭工作台,它可以导入法规源、飞书通知、模板服务和企业数据源,但仍服务于注册审核这一条主线。</p>
</section>
<section class="card-grid">
{% for connector in mcp_connectors %}
<article class="panel" style="padding: 20px;">
<div class="badge-row">
<span class="pill">{{ connector.kind }}</span>
<span class="pill {% if connector.status == '已连接' %}pill-success{% elif connector.status == '待验证' %}pill-signal{% else %}pill-danger{% endif %}">{{ connector.status }}</span>
</div>
<h3 style="margin-top: 16px;">{{ connector.name }}</h3>
<p class="muted">鉴权方式:{{ connector.auth }}</p>
<p>最近同步:{{ connector.sync }}</p>
</article>
{% endfor %}
</section>
<section class="layout-two-columns">
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">导入向导</h2>
<p class="section-copy">原型用一个简洁流程展示外部能力如何进入平台。</p>
</div>
</div>
<div class="flow-strip">
<article class="flow-node">
<div class="flow-index">01</div>
<h3>选择能力类型</h3>
<p>法规源、协同工具、模板服务或企业主数据。</p>
</article>
<article class="flow-node">
<div class="flow-index">02</div>
<h3>完成鉴权</h3>
<p>支持 API Key、App Token、文件轮询和 MCP Bridge。</p>
</article>
<article class="flow-node">
<div class="flow-index">03</div>
<h3>定义输入输出契约</h3>
<p>明确它向 Agent 暴露什么能力、返回什么结构。</p>
</article>
<article class="flow-node">
<div class="flow-index">04</div>
<h3>纳入审核编排</h3>
<p>由 Skill 或 Agent 任务按需调用,不在页面层散落实现。</p>
</article>
</div>
</article>
<article class="panel">
<h2 class="section-title">本题重点接入建议</h2>
<ul class="detail-list">
<li class="detail-item"><strong>飞书任务通知</strong> 把高风险项和责任人通知闭环到群聊机器人或会话入口。</li>
<li class="detail-item"><strong>Word 模板服务</strong> 用于报送版式输出与模板回填能力。</li>
<li class="detail-item"><strong>法规规则源</strong> 为后续规则包更新和版本治理提供自动化入口。</li>
</ul>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,69 @@
{% extends "base.html" %}
{% block title %}Skill Studio{% endblock %}
{% block content %}
<section class="page-header">
<span class="eyebrow">Skill Studio</span>
<h1 class="page-title">Skill 编辑、编排与运行预览</h1>
<p class="page-lead">Skill 页面用于解释 Agent 行为为何可控、可维护、可复用。通过角色说明、工具绑定、输入输出约束和测试运行预览,展示平台的治理能力。</p>
</section>
<section class="workspace-grid">
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">Skill 列表</h2>
<p class="section-copy">围绕注册审核主流程沉淀能力单元。</p>
</div>
</div>
<ul class="detail-list">
{% for skill in skills %}
<li class="detail-item">
<span class="pill {% if skill.status == '已发布' %}pill-success{% elif skill.status == '发布中' %}pill-accent{% else %}pill-signal{% endif %}">{{ skill.status }}</span>
<strong>{{ skill.name }}</strong>
<div class="muted">触发场景:{{ skill.trigger }}</div>
<div class="muted">工具链:{{ skill.tools }}</div>
</li>
{% endfor %}
</ul>
</article>
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">Skill 编辑区</h2>
<p class="section-copy">展示角色、约束、工具和输出契约如何被治理。</p>
</div>
</div>
<div class="stack">
<div class="detail-item">
<strong>角色说明</strong>
当前 Skill 负责对照 NMPA 注册申报资料要求执行完整性检查,优先输出命中项、缺失项、风险等级和建议动作。
</div>
<div class="detail-item">
<strong>工具绑定</strong>
规则树匹配、RAG 引用、字段池比对、风险映射、审计写入。
</div>
<div class="detail-item">
<strong>输入输出约束</strong>
输入限定资料范围、任务类型与批次;输出必须包含结构化结果、证据引用和人工复核提示。
</div>
</div>
</article>
<article class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">运行预览</h2>
<p class="section-copy">通过最近一次测试运行说明 Skill 的稳定性。</p>
</div>
</div>
<ul class="detail-list">
<li class="detail-item"><strong>最近测试结果</strong> 命中 3 个规则项,识别 1 个缺失项,生成 2 条建议动作。</li>
<li class="detail-item"><strong>命中工具</strong> completeness_rule_match、rag_lookup、risk_mapper</li>
<li class="detail-item"><strong>失败原因样例</strong> 当法规模板未配置时,页面会提示“法规规则未配置,无法执行完整性检查”。</li>
</ul>
</article>
</section>
{% endblock %}

View File

@@ -1,53 +1,82 @@
{% extends "base.html" %}
{% block title %}场景首页{% endblock %}
{% block title %}任务总览{% endblock %}
{% block content %}
<header class="page-header">
<span class="eyebrow">场景总览</span>
<h1 class="page-title">用同一套底座快速切换不同业务 Agent</h1>
<p class="page-lead">当前首页直接读取 YAML 场景配置。你可以从这里进入对话、上传资料,再用审计日志验证整条执行链路</p>
</header>
<section class="page-header">
<span class="eyebrow">Overview</span>
<h1 class="page-title">批次总览</h1>
<p class="page-lead">从这里直接进入知识库、文件中心、审核工作台和审计页。保留必要信息,不堆大段说明</p>
</section>
{% if scenario_issues %}
<section class="panel" style="margin-bottom: 20px;">
<h2 class="section-title">配置异常</h2>
<p class="muted">以下 YAML 场景文件存在问题,系统已自动跳过,不会影响其它合法场景展示。</p>
<ul class="detail-list">
{% for issue in scenario_issues %}
<li class="detail-item">
<strong>{{ issue.file_name }}</strong>
<div>{{ issue.message }}</div>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
<section class="card-grid">
{% for scenario in scenarios %}
<article class="card">
<h2>{{ scenario.name }}</h2>
<p>{{ scenario.description }}</p>
<ul class="meta-list">
<li class="meta-badge">场景 ID{{ scenario.id }}</li>
<li class="meta-badge">输出:{{ scenario.output.type }}</li>
<li class="meta-badge">RAG{% if scenario.rag.enabled %}已启用{% else %}未启用{% endif %}</li>
<li class="meta-badge">工具数:{{ scenario.tool_count }}</li>
</ul>
<p class="muted" style="margin-top: 14px;">适用题型:
{% if scenario.applicable_questions %}
{{ scenario.applicable_questions|join:"、" }}
{% else %}
暂未配置
{% endif %}
</p>
<p style="margin-top: 16px;">
<a class="button button-primary" href="{% url 'chat:index' scenario.id %}">进入对话</a>
</p>
<section class="metric-grid">
{% for metric in hero_metrics %}
<article class="metric-card">
<div class="metric-label">{{ metric.label }}</div>
<div class="metric-value">{{ metric.value }}</div>
</article>
{% empty %}
<div class="notice">暂无可用场景,请检查 `configs/` 目录和 YAML 配置内容。</div>
{% endfor %}
</section>
<section class="grid-2">
<a class="link-card" href="{% url 'platform_ui:knowledge-base' %}">
<h3>知识库配置</h3>
<p>查看规则树、知识源和切片策略。</p>
</a>
<a class="link-card" href="{% url 'documents:list' %}">
<h3>文件中心</h3>
<p>上传资料、执行入库、查看状态。</p>
</a>
<a class="link-card" href="{% url 'chat:index' 'document_review' %}">
<h3>审核工作台</h3>
<p>输入问题、选择文档、查看结果。</p>
</a>
<a class="link-card" href="{% url 'audit:list' %}">
<h3>审计日志</h3>
<p>查看每次执行的输入、输出和引用。</p>
</a>
</section>
<section class="panel">
<div class="section-heading">
<div>
<h2 class="section-title">已配置审核场景</h2>
<p class="section-copy">保留现有场景列表,直接进入使用。</p>
</div>
</div>
{% if scenario_issues %}
<div class="stack" style="margin-bottom: 18px;">
<div class="muted">配置异常</div>
{% for issue in scenario_issues %}
<article class="notice notice-error"><strong>{{ issue.file_name }}</strong>{{ issue.message }}</article>
{% endfor %}
</div>
{% endif %}
<div class="grid-2">
{% for scenario in scenarios %}
<article class="panel">
<div class="badge-row">
<span class="pill">{{ scenario.id }}</span>
<span class="pill {% if scenario.rag.enabled %}pill-success{% else %}pill-signal{% endif %}">RAG {% if scenario.rag.enabled %}开启{% else %}关闭{% endif %}</span>
</div>
<h3 style="margin: 14px 0 8px;">{{ scenario.name }}</h3>
<p>{{ scenario.description }}</p>
<p class="muted" style="margin-top: 10px;">适用题型:
{% if scenario.applicable_questions %}
{{ scenario.applicable_questions|join:"、" }}
{% else %}
暂未配置
{% endif %}
</p>
<div class="button-row" style="margin-top: 16px;">
<a class="button button-primary" href="{% url 'chat:index' scenario.id %}">进入审核工作台</a>
</div>
</article>
{% empty %}
<div class="notice">暂无可用场景,请检查 `configs/` 目录和 YAML 配置内容。</div>
{% endfor %}
</div>
</section>
{% endblock %}

View File

@@ -62,6 +62,23 @@ def test_audit_list_page_shows_user_input_summary(client, db):
assert "这是一个比较长的用户输入" in response.content.decode("utf-8")
def test_audit_detail_page_shows_raw_output(client, db):
result = AgentResult(
answer="结构化回答",
raw_output='{"answer":"结构化回答","confidence":"high"}',
status="success",
)
log = create_audit_log("knowledge_qa", "知识库问答助手", "问题", result)
response = client.get(reverse("audit:detail", args=[log.id]))
content = response.content.decode("utf-8")
assert response.status_code == 200
assert "原始输出" in content
assert "confidence" in content
assert "high" in content
def test_create_audit_log_masks_api_keys_from_error_message(db):
result = AgentResult(
answer="",
@@ -75,6 +92,19 @@ def test_create_audit_log_masks_api_keys_from_error_message(db):
assert "sk-***" in log.error_message
def test_create_audit_log_masks_embedding_api_keys_from_error_message(db):
result = AgentResult(
answer="",
status="failed",
error="EMBEDDING_API_KEY=embed-secret 调用失败",
)
log = create_audit_log("knowledge_qa", "知识库问答助手", "问题", result)
assert "embed-secret" not in log.error_message
assert "EMBEDDING_API_KEY=***" in log.error_message
def test_query_demo_records_reads_demo_business_record_table(db):
DemoBusinessRecord.objects.create(
scenario_id="quality_analysis",

View File

@@ -3,6 +3,7 @@ from agent_core.llm_provider import (
LLMConfigurationError,
create_embedding_provider,
create_llm_provider,
get_runtime_llm_config,
)
@@ -109,3 +110,17 @@ def test_embedding_provider_requires_api_key():
assert "EMBEDDING_API_KEY" in str(exc)
else:
raise AssertionError("expected EmbeddingConfigurationError")
def test_get_runtime_llm_config_uses_environment_and_overrides(monkeypatch):
monkeypatch.setenv("LLM_PROVIDER", "mock")
monkeypatch.setenv("LLM_API_KEY", "sk-env")
monkeypatch.setenv("LLM_BASE_URL", "https://env.example/v1")
monkeypatch.setenv("LLM_MODEL", "env-model")
config = get_runtime_llm_config({"LLM_MODEL": "override-model"})
assert config["LLM_PROVIDER"] == "mock"
assert config["LLM_API_KEY"] == "sk-env"
assert config["LLM_BASE_URL"] == "https://env.example/v1"
assert config["LLM_MODEL"] == "override-model"