feat(agent-runtime): 实现运行审计与状态服务
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Agent 运行、事件、工具调用和人工确认模型。"""
|
||||
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
||||
from common.models import TimeStampedModel, UserOwnedModel
|
||||
|
||||
|
||||
class RunStatus(models.TextChoices):
|
||||
PENDING = "pending", "等待"
|
||||
RUNNING = "running", "运行中"
|
||||
WAITING_APPROVAL = "waiting_approval", "等待确认"
|
||||
SUCCEEDED = "succeeded", "成功"
|
||||
FAILED = "failed", "失败"
|
||||
CANCELLED = "cancelled", "已取消"
|
||||
|
||||
|
||||
class AgentRun(UserOwnedModel):
|
||||
"""一次可审计的 Agent 运行;摘要字段不得保存敏感原文。"""
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
title = models.CharField("标题", max_length=160)
|
||||
status = models.CharField("状态", max_length=32, choices=RunStatus, default=RunStatus.PENDING)
|
||||
lock_version = models.PositiveBigIntegerField("锁版本", default=0)
|
||||
input_summary = models.JSONField("输入摘要", default=dict)
|
||||
output_summary = models.JSONField("输出摘要", null=True, blank=True)
|
||||
agent_name = models.CharField("Agent 名称", max_length=100, default="job_research")
|
||||
agent_version = models.CharField("Agent 版本", max_length=40, blank=True)
|
||||
instruction_version = models.CharField("指令版本", max_length=40, blank=True)
|
||||
toolset_version = models.CharField("工具集版本", max_length=40, blank=True)
|
||||
model_name = models.CharField("模型名称", max_length=100, blank=True)
|
||||
trace_id = models.CharField("追踪标识", max_length=128, blank=True)
|
||||
started_at = models.DateTimeField("开始时间", null=True, blank=True)
|
||||
finished_at = models.DateTimeField("结束时间", null=True, blank=True)
|
||||
duration_ms = models.PositiveBigIntegerField("耗时毫秒", null=True, blank=True)
|
||||
input_tokens = models.PositiveBigIntegerField("输入 Token", null=True, blank=True)
|
||||
output_tokens = models.PositiveBigIntegerField("输出 Token", null=True, blank=True)
|
||||
estimated_cost = models.DecimalField("估算费用", max_digits=12, decimal_places=6, null=True, blank=True)
|
||||
error_code = models.CharField("错误码", max_length=80, blank=True)
|
||||
error_summary = models.TextField("错误摘要", blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ("-created_at",)
|
||||
indexes = [models.Index(fields=("owner", "created_at"), name="run_owner_created_idx")]
|
||||
constraints = [models.CheckConstraint(condition=Q(lock_version__gte=0), name="run_lock_nonnegative")]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class AgentRunEvent(models.Model):
|
||||
"""按序追加的不可变运行事件。"""
|
||||
|
||||
run = models.ForeignKey(AgentRun, on_delete=models.PROTECT, related_name="events")
|
||||
sequence = models.PositiveBigIntegerField("事件序号")
|
||||
event_type = models.CharField("事件类型", max_length=64)
|
||||
summary = models.CharField("摘要", max_length=240)
|
||||
payload_summary = models.JSONField("数据摘要", default=dict)
|
||||
occurred_at = models.DateTimeField("发生时间")
|
||||
created_at = models.DateTimeField("记录时间", auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ("sequence",)
|
||||
constraints = [models.UniqueConstraint(fields=("run", "sequence"), name="event_run_sequence_uniq")]
|
||||
|
||||
|
||||
class ToolCallStatus(models.TextChoices):
|
||||
STARTED = "started", "已开始"
|
||||
SUCCEEDED = "succeeded", "成功"
|
||||
FAILED = "failed", "失败"
|
||||
|
||||
|
||||
class ToolCall(TimeStampedModel):
|
||||
"""记录一次工具调用的脱敏输入、结果和失败信息。"""
|
||||
|
||||
run = models.ForeignKey(AgentRun, on_delete=models.PROTECT, related_name="tool_calls")
|
||||
call_id = models.CharField("调用标识", max_length=128)
|
||||
tool_name = models.CharField("工具名称", max_length=100)
|
||||
tool_version = models.CharField("工具版本", max_length=40, blank=True)
|
||||
idempotency_key = models.CharField("幂等键", max_length=128, blank=True)
|
||||
status = models.CharField("状态", max_length=20, choices=ToolCallStatus, default=ToolCallStatus.STARTED)
|
||||
arguments_summary = models.JSONField("参数摘要", default=dict)
|
||||
result_summary = models.JSONField("结果摘要", null=True, blank=True)
|
||||
error_code = models.CharField("错误码", max_length=80, blank=True)
|
||||
error_summary = models.TextField("错误摘要", blank=True)
|
||||
started_at = models.DateTimeField("开始时间")
|
||||
finished_at = models.DateTimeField("结束时间", null=True, blank=True)
|
||||
duration_ms = models.PositiveBigIntegerField("耗时毫秒", null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [models.Index(fields=("run", "started_at"), name="tool_run_started_idx")]
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=("run", "call_id"), name="tool_run_call_uniq"),
|
||||
models.UniqueConstraint(
|
||||
fields=("run", "idempotency_key"),
|
||||
condition=~Q(idempotency_key=""),
|
||||
name="tool_run_idempotency_uniq",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class ApprovalStatus(models.TextChoices):
|
||||
PENDING = "pending", "待处理"
|
||||
APPROVED = "approved", "已通过"
|
||||
REJECTED = "rejected", "已拒绝"
|
||||
EXPIRED = "expired", "已过期"
|
||||
CANCELLED = "cancelled", "已取消"
|
||||
|
||||
|
||||
class HumanApproval(TimeStampedModel):
|
||||
"""保存高风险动作的人工确认请求与处理证据。"""
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
run = models.ForeignKey(AgentRun, on_delete=models.PROTECT, related_name="approvals")
|
||||
request_key = models.CharField("请求键", max_length=128)
|
||||
approval_type = models.CharField("确认类型", max_length=64)
|
||||
status = models.CharField("状态", max_length=20, choices=ApprovalStatus, default=ApprovalStatus.PENDING)
|
||||
request_summary = models.JSONField("请求摘要", default=dict)
|
||||
decision_summary = models.JSONField("处理摘要", null=True, blank=True)
|
||||
requested_at = models.DateTimeField("请求时间")
|
||||
resolved_at = models.DateTimeField("处理时间", null=True, blank=True)
|
||||
resolved_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="resolved_agent_approvals",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
indexes = [models.Index(fields=("status", "requested_at"), name="approval_status_time_idx")]
|
||||
constraints = [models.UniqueConstraint(fields=("run", "request_key"), name="approval_run_key_uniq")]
|
||||
Reference in New Issue
Block a user