feat: complete multimodal ticket intake
This commit is contained in:
@@ -1,16 +1,24 @@
|
||||
"""Agent 版本校验、运行快照和多步骤执行编排。
|
||||
|
||||
运行时只使用已冻结的版本配置,确保模型、Skill、输入映射和授权动作可以事后还原。
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from time import monotonic
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.db.models import Max
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.agents.models import (
|
||||
AgentDefinition,
|
||||
AgentRun,
|
||||
AgentRunAttempt,
|
||||
AgentRunEvent,
|
||||
AgentRunStep,
|
||||
AgentStepBinding,
|
||||
AgentVersion,
|
||||
@@ -34,10 +42,70 @@ class AgentValidationError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def append_run_event(
|
||||
run: AgentRun | int,
|
||||
event_type: str,
|
||||
payload: dict | None = None,
|
||||
) -> AgentRunEvent:
|
||||
run_id = run.id if isinstance(run, AgentRun) else run
|
||||
locked_run = AgentRun.objects.select_for_update().get(id=run_id)
|
||||
if event_type in {"completed", "failed", "cancelled", "timed_out"}:
|
||||
existing = locked_run.events.order_by("-sequence").first()
|
||||
if existing is not None and existing.event_type == event_type:
|
||||
return existing
|
||||
last_sequence = locked_run.events.aggregate(value=Max("sequence"))["value"] or 0
|
||||
event_payload = {
|
||||
"run_status": locked_run.status,
|
||||
"progress_stage": locked_run.progress_stage,
|
||||
"progress_message": locked_run.progress_message,
|
||||
**(payload or {}),
|
||||
}
|
||||
return AgentRunEvent.objects.create(
|
||||
run=locked_run,
|
||||
sequence=last_sequence + 1,
|
||||
event_type=event_type,
|
||||
payload=event_payload,
|
||||
)
|
||||
|
||||
|
||||
def _set_run_progress(run: AgentRun, stage: str, message: str) -> None:
|
||||
run.progress_stage = stage
|
||||
run.progress_message = message
|
||||
run.save(update_fields=["progress_stage", "progress_message"])
|
||||
event_type = stage
|
||||
if stage == "completed" and run.input_payload.get("draft_public_id"):
|
||||
event_type = "extraction_completed"
|
||||
append_run_event(
|
||||
run,
|
||||
event_type,
|
||||
{"current_step": run.current_step, "message": message},
|
||||
)
|
||||
|
||||
|
||||
def _step_progress(code: str, name: str) -> tuple[str, str]:
|
||||
if code == "facts":
|
||||
return "extracting", "正在解析沟通记录"
|
||||
if code == "draft":
|
||||
return "generating", "正在生成工单草稿"
|
||||
return "executing", f"正在执行:{name}"
|
||||
|
||||
|
||||
def validate_agent_version(version: AgentVersion, *, publishing: bool = False) -> None:
|
||||
"""校验步骤顺序、模型能力、Skill 状态、输入映射和授权动作。"""
|
||||
|
||||
validate_json_schema(version.input_schema, "Agent 输入 Schema")
|
||||
if version.example_input:
|
||||
validate_payload(version.input_schema, version.example_input, "Agent 示例输入")
|
||||
if (
|
||||
not isinstance(version.allowed_actions, list)
|
||||
or any(
|
||||
not isinstance(action, str) or not action.strip()
|
||||
for action in version.allowed_actions
|
||||
)
|
||||
or len(set(version.allowed_actions)) != len(version.allowed_actions)
|
||||
):
|
||||
raise AgentValidationError("Agent 授权动作必须是无重复的非空字符串列表")
|
||||
if version.temperature_override is not None and not 0 <= version.temperature_override <= 2:
|
||||
raise AgentValidationError("Temperature 必须在 0 到 2 之间")
|
||||
if (
|
||||
@@ -45,13 +113,26 @@ def validate_agent_version(version: AgentVersion, *, publishing: bool = False) -
|
||||
and not 1 <= version.max_output_tokens_override <= 32768
|
||||
):
|
||||
raise AgentValidationError("最大输出 Token 超出允许范围")
|
||||
steps = list(version.steps.select_related("skill_version__definition"))
|
||||
steps = list(
|
||||
version.steps.select_related(
|
||||
"skill_version__definition",
|
||||
"model_deployment__provider",
|
||||
"agent_version__model_deployment__provider",
|
||||
)
|
||||
)
|
||||
if not steps:
|
||||
raise AgentValidationError("Agent 至少需要一个 Skill 步骤")
|
||||
if len(steps) > 10:
|
||||
raise AgentValidationError("Agent 最多允许 10 个步骤")
|
||||
known_steps: set[str] = set()
|
||||
for step in steps:
|
||||
deployment = step.model_deployment or version.model_deployment
|
||||
if deployment.capability not in {"text_chat", "vision_chat"}:
|
||||
raise AgentValidationError(
|
||||
f"步骤 {step.code} 必须绑定文本或视觉模型,当前为 {deployment.capability}"
|
||||
)
|
||||
if publishing and (not deployment.enabled or not deployment.provider.enabled):
|
||||
raise AgentValidationError(f"步骤 {step.code} 绑定的模型部署未启用")
|
||||
if publishing and step.skill_version.status != "published":
|
||||
raise AgentValidationError(f"步骤 {step.code} 必须绑定已发布 Skill")
|
||||
for target, source in step.input_mapping.items():
|
||||
@@ -110,6 +191,7 @@ def ensure_agent_draft(definition: AgentDefinition, user=None) -> AgentVersion:
|
||||
system_prompt=source.system_prompt,
|
||||
input_schema=deepcopy(source.input_schema),
|
||||
example_input=deepcopy(source.example_input),
|
||||
allowed_actions=deepcopy(source.allowed_actions),
|
||||
temperature_override=source.temperature_override,
|
||||
max_output_tokens_override=source.max_output_tokens_override,
|
||||
created_by=user,
|
||||
@@ -121,6 +203,7 @@ def ensure_agent_draft(definition: AgentDefinition, user=None) -> AgentVersion:
|
||||
code=step.code,
|
||||
order=step.order,
|
||||
skill_version=step.skill_version,
|
||||
model_deployment=step.model_deployment,
|
||||
input_mapping=deepcopy(step.input_mapping),
|
||||
)
|
||||
for step in source.steps.all()
|
||||
@@ -130,6 +213,8 @@ def ensure_agent_draft(definition: AgentDefinition, user=None) -> AgentVersion:
|
||||
|
||||
|
||||
def build_config_snapshot(version: AgentVersion) -> dict:
|
||||
"""冻结一次运行所需的 Agent、模型、Skill、映射和策略配置。"""
|
||||
|
||||
deployment = version.model_deployment
|
||||
return {
|
||||
"agent": {
|
||||
@@ -137,6 +222,7 @@ def build_config_snapshot(version: AgentVersion) -> dict:
|
||||
"code": version.definition.code,
|
||||
"version": version.version,
|
||||
"status": version.status,
|
||||
"allowed_actions": deepcopy(version.allowed_actions),
|
||||
},
|
||||
"model": {
|
||||
"deployment_id": deployment.id,
|
||||
@@ -155,12 +241,102 @@ def build_config_snapshot(version: AgentVersion) -> dict:
|
||||
"skill_code": step.skill_version.definition.code,
|
||||
"skill_version": step.skill_version.version,
|
||||
"input_mapping": step.input_mapping,
|
||||
"model": {
|
||||
"deployment_id": (
|
||||
step.model_deployment_id or version.model_deployment_id
|
||||
),
|
||||
"inherited": step.model_deployment_id is None,
|
||||
"provider_name": (
|
||||
step.model_deployment.provider.name
|
||||
if step.model_deployment_id
|
||||
else deployment.provider.name
|
||||
),
|
||||
"model_name": (
|
||||
step.model_deployment.model_name
|
||||
if step.model_deployment_id
|
||||
else deployment.model_name
|
||||
),
|
||||
"capability": (
|
||||
step.model_deployment.capability
|
||||
if step.model_deployment_id
|
||||
else deployment.capability
|
||||
),
|
||||
},
|
||||
}
|
||||
for step in version.steps.select_related("skill_version__definition")
|
||||
for step in version.steps.select_related(
|
||||
"skill_version__definition",
|
||||
"model_deployment__provider",
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def queue_agent_run(
|
||||
version: AgentVersion,
|
||||
input_payload: dict,
|
||||
*,
|
||||
created_by=None,
|
||||
sandbox: bool = False,
|
||||
config_snapshot_extra: dict | None = None,
|
||||
defer_until_commit: bool = False,
|
||||
) -> AgentRun:
|
||||
"""创建运行及初始事件,并按事务边界决定何时投递 Celery 任务。"""
|
||||
|
||||
validate_agent_version(version, publishing=not sandbox)
|
||||
validate_payload(version.input_schema, input_payload, "Agent 输入")
|
||||
config_snapshot = build_config_snapshot(version)
|
||||
if config_snapshot_extra:
|
||||
config_snapshot.update(deepcopy(config_snapshot_extra))
|
||||
run = AgentRun.objects.create(
|
||||
definition=version.definition,
|
||||
agent_version=version,
|
||||
sandbox=sandbox,
|
||||
input_payload=deepcopy(input_payload),
|
||||
config_snapshot=config_snapshot,
|
||||
total_steps=version.steps.count(),
|
||||
created_by=created_by,
|
||||
)
|
||||
append_run_event(run, "queued", {"message": "Agent 运行已进入处理队列"})
|
||||
|
||||
def dispatch() -> None:
|
||||
from apps.agents.tasks import execute_agent_run
|
||||
|
||||
try:
|
||||
result = execute_agent_run.delay(run.id)
|
||||
except Exception as error:
|
||||
run.status = "failed"
|
||||
run.progress_stage = "failed"
|
||||
run.progress_message = "Agent 运行入队失败"
|
||||
run.error_code = "queue_failed"
|
||||
run.error_message = str(error)
|
||||
run.finished_at = timezone.now()
|
||||
run.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"progress_stage",
|
||||
"progress_message",
|
||||
"error_code",
|
||||
"error_message",
|
||||
"finished_at",
|
||||
]
|
||||
)
|
||||
append_run_event(
|
||||
run,
|
||||
"failed",
|
||||
{"error_code": run.error_code, "error_message": run.error_message},
|
||||
)
|
||||
return
|
||||
run.task_id = result.id or ""
|
||||
run.save(update_fields=["task_id"])
|
||||
|
||||
if defer_until_commit:
|
||||
transaction.on_commit(dispatch)
|
||||
else:
|
||||
dispatch()
|
||||
run.refresh_from_db()
|
||||
return run
|
||||
|
||||
|
||||
def _parse_json_output(raw_output: str) -> Any:
|
||||
value = raw_output.strip()
|
||||
if value.startswith("```"):
|
||||
@@ -172,6 +348,83 @@ def _parse_json_output(raw_output: str) -> Any:
|
||||
raise SkillValidationError(f"模型输出不是合法 JSON: {error.msg}") from error
|
||||
|
||||
|
||||
def _empty_schema_value(schema: dict) -> Any:
|
||||
value_type = schema.get("type")
|
||||
if value_type == "string" and (not schema.get("enum") or "" in schema["enum"]):
|
||||
return ""
|
||||
if value_type == "array":
|
||||
return []
|
||||
if value_type == "object":
|
||||
return {}
|
||||
return None
|
||||
|
||||
|
||||
def _complete_object_shape(value: dict, schema: dict) -> dict:
|
||||
completed = dict(value)
|
||||
properties = schema.get("properties", {})
|
||||
for code in schema.get("required", []):
|
||||
if code in completed:
|
||||
continue
|
||||
empty_value = _empty_schema_value(properties.get(code, {}))
|
||||
if empty_value is not None:
|
||||
completed[code] = empty_value
|
||||
return completed
|
||||
|
||||
|
||||
def _normalize_nested_output(output: Any, output_schema: dict) -> Any:
|
||||
"""Repair common flattened model output without another model round-trip."""
|
||||
if not isinstance(output, dict):
|
||||
return output
|
||||
properties = output_schema.get("properties", {})
|
||||
required = set(output_schema.get("required", []))
|
||||
|
||||
if {"facts", "evidence", "missing_fields"}.issubset(required):
|
||||
fact_schema = properties.get("facts", {})
|
||||
fact_codes = set(fact_schema.get("properties", {}))
|
||||
nested_facts = output.get("facts")
|
||||
if isinstance(nested_facts, dict):
|
||||
facts = _complete_object_shape(nested_facts, fact_schema)
|
||||
elif fact_codes.intersection(output):
|
||||
facts = _complete_object_shape(
|
||||
{code: output[code] for code in fact_codes if code in output},
|
||||
fact_schema,
|
||||
)
|
||||
else:
|
||||
facts = None
|
||||
if facts is not None:
|
||||
evidence = _complete_object_shape(
|
||||
output.get("evidence", {}) if isinstance(output.get("evidence"), dict) else {},
|
||||
properties.get("evidence", {}),
|
||||
)
|
||||
missing = list(output.get("missing_fields", []))
|
||||
for code, value in facts.items():
|
||||
if not value and code not in missing:
|
||||
missing.append(code)
|
||||
return {"facts": facts, "evidence": evidence, "missing_fields": missing}
|
||||
|
||||
if {"fields", "missing_fields", "questions"}.issubset(required):
|
||||
field_schema = properties.get("fields", {})
|
||||
field_codes = set(field_schema.get("properties", {}))
|
||||
nested_fields = output.get("fields")
|
||||
if isinstance(nested_fields, dict):
|
||||
fields = _complete_object_shape(nested_fields, field_schema)
|
||||
elif field_codes.intersection(output):
|
||||
fields = _complete_object_shape(
|
||||
{code: output[code] for code in field_codes if code in output},
|
||||
field_schema,
|
||||
)
|
||||
else:
|
||||
fields = None
|
||||
if fields is not None:
|
||||
questions = output.get("questions", output.get("follow_up_questions", []))
|
||||
return {
|
||||
"fields": fields,
|
||||
"missing_fields": list(output.get("missing_fields", [])),
|
||||
"questions": list(questions) if isinstance(questions, list) else [],
|
||||
}
|
||||
return output
|
||||
|
||||
|
||||
def _record_attempt(
|
||||
step: AgentRunStep,
|
||||
invocation: ModelInvocation,
|
||||
@@ -199,34 +452,58 @@ def _invoke_for_step(
|
||||
image_parts: list[dict] | None = None,
|
||||
) -> ModelInvocation:
|
||||
version = run.agent_version
|
||||
deployment = step.model_deployment or version.model_deployment
|
||||
messages = []
|
||||
if version.system_prompt:
|
||||
messages.append({"role": "system", "content": version.system_prompt})
|
||||
user_content: str | list[dict] = prompt
|
||||
if image_parts:
|
||||
user_content = [{"type": "text", "text": prompt}, *image_parts]
|
||||
step_image_parts = None
|
||||
if image_parts and deployment.capability == "vision_chat":
|
||||
if not deployment.provider.image_egress_approved:
|
||||
raise AgentValidationError(
|
||||
f"步骤 {step.code} 的模型渠道未授权图片出境"
|
||||
)
|
||||
step_image_parts = image_parts
|
||||
if step_image_parts:
|
||||
user_content = [{"type": "text", "text": prompt}, *step_image_parts]
|
||||
messages.append({"role": "user", "content": user_content})
|
||||
snapshot_messages = [
|
||||
{"role": item["role"], "content": prompt if item["role"] == "user" else item["content"]}
|
||||
for item in messages
|
||||
]
|
||||
invocation = ModelInvocation.objects.create(
|
||||
deployment=version.model_deployment,
|
||||
deployment=deployment,
|
||||
invocation_type="agent_step",
|
||||
input_snapshot={
|
||||
"messages": snapshot_messages,
|
||||
"image_refs": run.input_payload.get("image_refs", []) if image_parts else [],
|
||||
"image_refs": (
|
||||
run.input_payload.get("image_refs", []) if step_image_parts else []
|
||||
),
|
||||
"run_id": str(run.public_id),
|
||||
"step": step.code,
|
||||
},
|
||||
created_by=run.created_by,
|
||||
)
|
||||
|
||||
if attempt_type == "structure_repair":
|
||||
_set_run_progress(run, "repairing", "正在修正结构化结果")
|
||||
|
||||
def mark_retry(retry_number: int, _error_code: str) -> None:
|
||||
run.retry_count += 1
|
||||
run.progress_stage = "retrying"
|
||||
run.progress_message = f"模型响应暂时不稳定,正在进行第 {retry_number} 次重试"
|
||||
run.save(update_fields=["retry_count", "progress_stage", "progress_message"])
|
||||
|
||||
execute_invocation(
|
||||
invocation,
|
||||
messages,
|
||||
temperature=version.temperature_override,
|
||||
max_output_tokens=version.max_output_tokens_override,
|
||||
on_retry=mark_retry,
|
||||
total_timeout_seconds=settings.AI_AGENT_MODEL_TIMEOUT_SECONDS,
|
||||
)
|
||||
if invocation.status == "succeeded":
|
||||
_set_run_progress(run, "validating", "正在校验结构化结果")
|
||||
_record_attempt(step, invocation, attempt_type)
|
||||
if invocation.retry_count:
|
||||
AgentRunAttempt.objects.create(
|
||||
@@ -264,6 +541,8 @@ def _repair_output(
|
||||
|
||||
|
||||
def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) -> AgentRun:
|
||||
"""按冻结配置顺序执行所有步骤,记录尝试、产物、进度和终态事件。"""
|
||||
|
||||
run = AgentRun.objects.select_related(
|
||||
"definition",
|
||||
"agent_version__model_deployment__provider",
|
||||
@@ -272,27 +551,41 @@ def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) ->
|
||||
started = monotonic()
|
||||
run.status = "running"
|
||||
run.started_at = timezone.now()
|
||||
run.save(update_fields=["status", "started_at"])
|
||||
run.progress_stage = "starting"
|
||||
run.progress_message = "正在准备 Agent 运行"
|
||||
run.save(update_fields=["status", "started_at", "progress_stage", "progress_message"])
|
||||
append_run_event(run, "starting", {"message": run.progress_message})
|
||||
step_outputs: dict[str, dict] = {}
|
||||
try:
|
||||
bindings = list(
|
||||
run.agent_version.steps.select_related(
|
||||
"skill_version__definition",
|
||||
"model_deployment__provider",
|
||||
"agent_version__model_deployment__provider",
|
||||
)
|
||||
)
|
||||
for binding in bindings:
|
||||
run.refresh_from_db(fields=["cancellation_requested_at"])
|
||||
if run.cancellation_requested_at:
|
||||
run.status = "cancelled"
|
||||
_set_run_progress(run, "cancelled", "运行已取消")
|
||||
break
|
||||
run.current_step = binding.code
|
||||
run.save(update_fields=["current_step"])
|
||||
progress_stage, progress_message = _step_progress(
|
||||
binding.code,
|
||||
binding.skill_version.definition.name,
|
||||
)
|
||||
_set_run_progress(run, progress_stage, progress_message)
|
||||
step = AgentRunStep.objects.create(
|
||||
run=run,
|
||||
code=binding.code,
|
||||
name=binding.skill_version.definition.name,
|
||||
order=binding.order,
|
||||
skill_version=binding.skill_version,
|
||||
model_deployment=(
|
||||
binding.model_deployment or run.agent_version.model_deployment
|
||||
),
|
||||
status="running",
|
||||
started_at=timezone.now(),
|
||||
)
|
||||
@@ -312,6 +605,10 @@ def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) ->
|
||||
raise AgentValidationError(invocation.error_message or "模型调用失败")
|
||||
try:
|
||||
output = _parse_json_output(invocation.output_text)
|
||||
output = _normalize_nested_output(
|
||||
output,
|
||||
binding.skill_version.output_schema,
|
||||
)
|
||||
validate_payload(binding.skill_version.output_schema, output, "模型输出")
|
||||
except SkillValidationError as error:
|
||||
repair = _repair_output(
|
||||
@@ -326,6 +623,10 @@ def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) ->
|
||||
repair.error_message or "结构修复失败"
|
||||
) from error
|
||||
output = _parse_json_output(repair.output_text)
|
||||
output = _normalize_nested_output(
|
||||
output,
|
||||
binding.skill_version.output_schema,
|
||||
)
|
||||
validate_payload(binding.skill_version.output_schema, output, "修复输出")
|
||||
step.output_payload = output
|
||||
step.status = "succeeded"
|
||||
@@ -337,6 +638,7 @@ def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) ->
|
||||
run.status = "failed"
|
||||
run.error_code = "step_failed"
|
||||
run.error_message = f"步骤 {binding.code} 失败: {error}"
|
||||
_set_run_progress(run, "failed", "Agent 运行失败")
|
||||
finally:
|
||||
attempts = step.attempts.all()
|
||||
step.input_tokens = sum(item.input_tokens for item in attempts)
|
||||
@@ -349,14 +651,36 @@ def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) ->
|
||||
run.refresh_from_db(fields=["cancellation_requested_at"])
|
||||
if run.cancellation_requested_at:
|
||||
run.status = "cancelled"
|
||||
_set_run_progress(run, "cancelled", "运行已取消")
|
||||
break
|
||||
if run.status == "running":
|
||||
run.status = "succeeded"
|
||||
run.final_output = step.output_payload if bindings else None
|
||||
try:
|
||||
from apps.tickets.support_analysis import validate_support_analysis_output
|
||||
|
||||
validate_support_analysis_output(run, run.final_output)
|
||||
from apps.knowledge.distillation import (
|
||||
validate_knowledge_distillation_output,
|
||||
)
|
||||
|
||||
validate_knowledge_distillation_output(run, run.final_output)
|
||||
except ValueError as error:
|
||||
run.status = "failed"
|
||||
run.error_code = (
|
||||
"untrusted_support_output"
|
||||
if run.definition.code == "support-analysis"
|
||||
else "untrusted_knowledge_distillation_output"
|
||||
)
|
||||
run.error_message = str(error)
|
||||
_set_run_progress(run, "failed", "Agent 结果未通过业务信任校验")
|
||||
else:
|
||||
run.status = "succeeded"
|
||||
_set_run_progress(run, "completed", "草稿生成完成")
|
||||
except Exception as error:
|
||||
run.status = "failed"
|
||||
run.error_code = "runtime_error"
|
||||
run.error_message = "Agent 运行发生未预期错误"
|
||||
_set_run_progress(run, "failed", "Agent 运行失败")
|
||||
OperationAuditLog.objects.create(
|
||||
user=run.created_by,
|
||||
module="agents",
|
||||
@@ -371,6 +695,75 @@ def execute_agent_run(run_id: int, *, image_parts: list[dict] | None = None) ->
|
||||
run.duration_ms = max(int((monotonic() - started) * 1000), 0)
|
||||
run.finished_at = timezone.now()
|
||||
run.save()
|
||||
if run.definition.code == "support-analysis":
|
||||
from apps.workitems.services import sync_support_analysis_work_item
|
||||
|
||||
if run.status == "succeeded":
|
||||
try:
|
||||
from apps.tickets.support_analysis import process_support_analysis_result
|
||||
|
||||
process_support_analysis_result(run)
|
||||
except Exception as error:
|
||||
OperationAuditLog.objects.create(
|
||||
user=run.created_by,
|
||||
module="tickets",
|
||||
action="support_post_processing_failed",
|
||||
target_type="agent_run",
|
||||
target_id=str(run.public_id),
|
||||
request_snapshot={"error_type": type(error).__name__},
|
||||
result="failed",
|
||||
)
|
||||
sync_support_analysis_work_item(
|
||||
run,
|
||||
error_code="post_processing_failed",
|
||||
error_message="支持分析结果后处理失败,请人工接管",
|
||||
)
|
||||
else:
|
||||
sync_support_analysis_work_item(run)
|
||||
else:
|
||||
sync_support_analysis_work_item(run)
|
||||
if run.definition.code == "knowledge-distillation":
|
||||
from apps.knowledge.models import TicketKnowledgeDistillation
|
||||
from apps.workitems.services import sync_knowledge_distillation_work_item
|
||||
|
||||
try:
|
||||
distillation = run.ticket_knowledge_distillation
|
||||
except TicketKnowledgeDistillation.DoesNotExist:
|
||||
distillation = None
|
||||
if distillation is not None:
|
||||
if run.status == "succeeded":
|
||||
try:
|
||||
from apps.knowledge.distillation import (
|
||||
process_knowledge_distillation_result,
|
||||
)
|
||||
|
||||
process_knowledge_distillation_result(run)
|
||||
except Exception as error:
|
||||
OperationAuditLog.objects.create(
|
||||
user=run.created_by,
|
||||
module="knowledge",
|
||||
action="knowledge_distillation_post_processing_failed",
|
||||
target_type="agent_run",
|
||||
target_id=str(run.public_id),
|
||||
request_snapshot={"error_type": type(error).__name__},
|
||||
result="failed",
|
||||
)
|
||||
sync_knowledge_distillation_work_item(
|
||||
distillation.ticket,
|
||||
run=run,
|
||||
error_code="post_processing_failed",
|
||||
error_message="知识蒸馏结果后处理失败,请人工接管",
|
||||
)
|
||||
else:
|
||||
sync_knowledge_distillation_work_item(
|
||||
distillation.ticket,
|
||||
run=run,
|
||||
)
|
||||
else:
|
||||
sync_knowledge_distillation_work_item(
|
||||
distillation.ticket,
|
||||
run=run,
|
||||
)
|
||||
OperationAuditLog.objects.create(
|
||||
user=run.created_by,
|
||||
module="agents",
|
||||
@@ -395,6 +788,7 @@ def replace_agent_steps(version: AgentVersion, steps: list[dict]) -> None:
|
||||
code=item["code"],
|
||||
order=index + 1,
|
||||
skill_version_id=item["skill_version_id"],
|
||||
model_deployment_id=item.get("model_deployment_id"),
|
||||
input_mapping=item.get("input_mapping", {}),
|
||||
)
|
||||
for index, item in enumerate(steps)
|
||||
|
||||
Reference in New Issue
Block a user