feat(audit): 增加审计日志与演示数据管理

This commit is contained in:
2026-05-30 00:10:26 +08:00
parent 4a831ee2c5
commit 5c9718ddb1
13 changed files with 333 additions and 0 deletions

1
apps/audit/__init__.py Normal file
View File

@@ -0,0 +1 @@

17
apps/audit/admin.py Normal file
View File

@@ -0,0 +1,17 @@
from django.contrib import admin
from .models import AgentAuditLog, DemoBusinessRecord
@admin.register(AgentAuditLog)
class AgentAuditLogAdmin(admin.ModelAdmin):
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")
@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")

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

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

View File

@@ -0,0 +1,46 @@
# Generated by Django 5.2.14 on 2026-05-29 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="AgentAuditLog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("scenario_id", models.CharField(db_index=True, max_length=100)),
("scenario_name", models.CharField(blank=True, max_length=200)),
("user_input", models.TextField()),
("retrieved_chunks", models.JSONField(blank=True, default=list)),
("tool_calls", models.JSONField(blank=True, default=list)),
("structured_output", models.JSONField(blank=True, default=dict)),
("final_answer", models.TextField(blank=True)),
("raw_output", models.TextField(blank=True)),
("model_name", models.CharField(blank=True, max_length=100)),
("latency_ms", models.PositiveIntegerField(default=0)),
(
"status",
models.CharField(db_index=True, default="success", max_length=20),
),
("error_message", models.TextField(blank=True)),
("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
],
options={
"ordering": ["-created_at"],
},
),
]

View File

@@ -0,0 +1,35 @@
# Generated for V1 demo business records.
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("audit", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="DemoBusinessRecord",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("scenario_id", models.CharField(db_index=True, max_length=100)),
("record_type", models.CharField(db_index=True, max_length=100)),
("title", models.CharField(max_length=255)),
("payload", models.JSONField(blank=True, default=dict)),
("created_at", models.DateTimeField(auto_now_add=True)),
],
options={
"ordering": ["-created_at"],
},
),
]

View File

40
apps/audit/models.py Normal file
View File

@@ -0,0 +1,40 @@
from django.db import models
class AgentAuditLog(models.Model):
STATUS_SUCCESS = "success"
STATUS_FAILED = "failed"
scenario_id = models.CharField(max_length=100, db_index=True)
scenario_name = models.CharField(max_length=200, blank=True)
user_input = models.TextField()
retrieved_chunks = models.JSONField(default=list, blank=True)
tool_calls = models.JSONField(default=list, blank=True)
structured_output = models.JSONField(default=dict, blank=True)
final_answer = models.TextField(blank=True)
raw_output = models.TextField(blank=True)
model_name = models.CharField(max_length=100, blank=True)
latency_ms = models.PositiveIntegerField(default=0)
status = models.CharField(max_length=20, default=STATUS_SUCCESS, db_index=True)
error_message = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
ordering = ["-created_at"]
def __str__(self) -> str:
return f"{self.scenario_name or self.scenario_id} #{self.pk}"
class DemoBusinessRecord(models.Model):
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)
payload = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self) -> str:
return self.title

36
apps/audit/services.py Normal file
View File

@@ -0,0 +1,36 @@
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:
return AgentAuditLog.objects.create(
scenario_id=scenario_id,
scenario_name=scenario_name,
user_input=user_input,
retrieved_chunks=agent_result.references,
tool_calls=agent_result.tool_calls,
structured_output=agent_result.structured_output,
final_answer=agent_result.answer,
raw_output=agent_result.raw_output,
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),
)

11
apps/audit/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.urls import path
from . import views
app_name = "audit"
urlpatterns = [
path("", views.log_list, name="list"),
path("<int:log_id>/", views.log_detail, name="detail"),
]

13
apps/audit/views.py Normal file
View File

@@ -0,0 +1,13 @@
from django.shortcuts import get_object_or_404, render
from .models import AgentAuditLog
def log_list(request):
logs = AgentAuditLog.objects.all()
return render(request, "audit/log_list.html", {"logs": logs})
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})