feat: add secure agent and MCP extensions

This commit is contained in:
hailin
2026-07-27 19:02:37 +08:00
parent 07c9ea1cc4
commit d93cb75c10
29 changed files with 3186 additions and 21 deletions

View File

@@ -1,3 +1,9 @@
"""模型调用执行器。
统一处理超时预算、可重试错误、凭据脱敏、调用快照和 Token/耗时记录。
"""
from collections.abc import Callable
from dataclasses import dataclass
from time import monotonic, sleep
from typing import Any
@@ -38,12 +44,18 @@ def _request_completion(
*,
temperature: float,
max_output_tokens: int,
timeout_seconds: float | None = None,
) -> CompletionResult:
provider = deployment.provider
request_timeout = min(max(provider.default_timeout_seconds, 1), 300)
if timeout_seconds is not None:
request_timeout = min(request_timeout, max(timeout_seconds, 1))
client = OpenAI(
api_key=decrypt_secret(provider.api_key_encrypted),
base_url=provider.base_url,
timeout=min(max(provider.default_timeout_seconds, 1), 300),
timeout=request_timeout,
# Retry here so the platform can expose it instead of waiting on opaque SDK retries.
max_retries=0,
)
response = client.chat.completions.create(
model=deployment.model_name,
@@ -84,7 +96,11 @@ def execute_invocation(
*,
temperature: float | None = None,
max_output_tokens: int | None = None,
on_retry: Callable[[int, str], None] | None = None,
total_timeout_seconds: float | None = None,
) -> ModelInvocation:
"""在总时间预算内调用模型,分类错误、限制重试并记录脱敏终态。"""
deployment = invocation.deployment
if deployment is None or not deployment.enabled or not deployment.provider.enabled:
invocation.status = "failed"
@@ -111,6 +127,12 @@ def execute_invocation(
result: CompletionResult | None = None
last_error: Exception | None = None
for attempt in range(2):
elapsed = monotonic() - started
remaining = total_timeout_seconds - elapsed if total_timeout_seconds is not None else None
if remaining is not None and remaining <= 0:
invocation.error_code = "timeout"
invocation.error_message = "模型请求超过 Agent 时间预算"
break
try:
result = _request_completion(
deployment,
@@ -119,6 +141,7 @@ def execute_invocation(
if temperature is not None
else deployment.default_temperature,
max_output_tokens=max_output_tokens or deployment.max_output_tokens,
timeout_seconds=remaining,
)
invocation.retry_count = attempt
break
@@ -130,6 +153,18 @@ def execute_invocation(
invocation.error_code = code
invocation.error_message = message
break
if total_timeout_seconds is not None:
remaining_after_error = total_timeout_seconds - (monotonic() - started)
if remaining_after_error <= 1:
invocation.error_code = code
invocation.error_message = message
break
invocation.retry_count = attempt + 1
invocation.error_code = code
invocation.error_message = message
invocation.save(update_fields=["retry_count", "error_code", "error_message"])
if on_retry:
on_retry(attempt + 1, code)
sleep(1)
invocation.duration_ms = max(int((monotonic() - started) * 1000), 0)
@@ -156,6 +191,8 @@ def create_model_test(
prompt: str,
user=None,
) -> ModelInvocation:
"""创建可审计的异步模型连通测试,不绕过正式模型执行器。"""
return ModelInvocation.objects.create(
deployment=deployment,
invocation_type="model_test",