feat(audit): 增加场景筛选与日志摘要展示

This commit is contained in:
2026-05-30 00:31:13 +08:00
parent c2b3a3b4f7
commit 81f17319ff
4 changed files with 73 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ from django.db import models
class AgentAuditLog(models.Model):
# 审计状态需要同时服务数据库检索和前端展示。
STATUS_SUCCESS = "success"
STATUS_FAILED = "failed"
@@ -25,6 +26,19 @@ class AgentAuditLog(models.Model):
def __str__(self) -> str:
return f"{self.scenario_name or self.scenario_id} #{self.pk}"
def get_status_display_text(self) -> str:
"""返回更适合页面展示的中文状态。"""
return {
self.STATUS_SUCCESS: "执行成功",
self.STATUS_FAILED: "执行失败",
}.get(self.status, self.status)
def get_user_input_summary(self, max_length: int = 28) -> str:
"""在列表页展示用户输入摘要,避免长文本撑破表格。"""
if len(self.user_input) <= max_length:
return self.user_input
return f"{self.user_input[:max_length]}..."
class DemoBusinessRecord(models.Model):
scenario_id = models.CharField(max_length=100, db_index=True)

View File

@@ -4,8 +4,19 @@ from .models import AgentAuditLog
def log_list(request):
# 列表页支持按场景筛选,方便演示时快速定位同一类场景的执行记录。
scenario_id = (request.GET.get("scenario_id") or "").strip()
logs = AgentAuditLog.objects.all()
return render(request, "audit/log_list.html", {"logs": logs})
if scenario_id:
logs = logs.filter(scenario_id=scenario_id)
return render(
request,
"audit/log_list.html",
{
"logs": logs,
"selected_scenario_id": scenario_id,
},
)
def log_detail(request, log_id: int):

View File

@@ -7,6 +7,12 @@
<span class="eyebrow">执行留痕</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>
<a class="button" href="{% url 'audit:list' %}">清空筛选</a>
</p>
{% endif %}
</header>
<article class="panel">
@@ -15,9 +21,11 @@
<tr>
<th>ID</th>
<th>场景</th>
<th>输入摘要</th>
<th>状态</th>
<th>模型</th>
<th>耗时</th>
<th>创建时间</th>
<th>详情</th>
</tr>
</thead>
@@ -26,13 +34,15 @@
<tr>
<td>{{ log.id }}</td>
<td>{{ log.scenario_name }}</td>
<td>{{ log.status }}</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>
</tr>
{% empty %}
<tr><td colspan="6">暂无审计日志,先去执行一次对话吧。</td></tr>
<tr><td colspan="8">暂无审计日志,先去执行一次对话吧。</td></tr>
{% endfor %}
</tbody>
</table>

View File

@@ -27,6 +27,41 @@ def test_audit_list_page_shows_log(client, db):
assert "知识库问答助手" in response.content.decode("utf-8")
def test_audit_list_can_filter_by_scenario(client, db):
create_audit_log(
"knowledge_qa",
"知识库问答助手",
"制度问题",
AgentResult(answer="回答一", status="success"),
)
create_audit_log(
"quality_analysis",
"质量异常分析助手",
"质量问题",
AgentResult(answer="回答二", status="success"),
)
response = client.get(reverse("audit:list"), {"scenario_id": "knowledge_qa"})
content = response.content.decode("utf-8")
assert response.status_code == 200
assert "知识库问答助手" in content
assert "质量异常分析助手" not in content
def test_audit_list_page_shows_user_input_summary(client, db):
create_audit_log(
"knowledge_qa",
"知识库问答助手",
"这是一个比较长的用户输入,用于确认列表页会展示输入摘要。",
AgentResult(answer="回答", status="success"),
)
response = client.get(reverse("audit:list"))
assert "这是一个比较长的用户输入" in response.content.decode("utf-8")
def test_create_audit_log_masks_api_keys_from_error_message(db):
result = AgentResult(
answer="",