feat: init commint
This commit is contained in:
@@ -21,6 +21,12 @@ from openai import (
|
||||
|
||||
from apps.modelhub.crypto import CredentialEncryptionError, decrypt_secret
|
||||
from apps.modelhub.models import ModelDeployment, ModelInvocation
|
||||
from apps.modelhub.structured_output import (
|
||||
STRUCTURED_OUTPUT_TEST_PROMPT,
|
||||
STRUCTURED_OUTPUT_TEST_SCHEMA,
|
||||
StructuredOutputError,
|
||||
build_structured_request,
|
||||
)
|
||||
|
||||
TRANSIENT_ERRORS = (APIConnectionError, APITimeoutError, RateLimitError)
|
||||
|
||||
@@ -44,6 +50,7 @@ def _request_completion(
|
||||
*,
|
||||
temperature: float,
|
||||
max_output_tokens: int,
|
||||
response_format: dict[str, Any] | None = None,
|
||||
timeout_seconds: float | None = None,
|
||||
) -> CompletionResult:
|
||||
provider = deployment.provider
|
||||
@@ -57,11 +64,16 @@ def _request_completion(
|
||||
# Retry here so the platform can expose it instead of waiting on opaque SDK retries.
|
||||
max_retries=0,
|
||||
)
|
||||
request_options: dict[str, Any] = {
|
||||
"model": deployment.model_name,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"max_tokens": max_output_tokens,
|
||||
}
|
||||
if response_format is not None:
|
||||
request_options["response_format"] = response_format
|
||||
response = client.chat.completions.create(
|
||||
model=deployment.model_name,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
max_tokens=max_output_tokens,
|
||||
**request_options,
|
||||
)
|
||||
content = response.choices[0].message.content or ""
|
||||
usage = response.usage
|
||||
@@ -96,13 +108,21 @@ def execute_invocation(
|
||||
*,
|
||||
temperature: float | None = None,
|
||||
max_output_tokens: int | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
schema_name: str | 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:
|
||||
if (
|
||||
deployment is None
|
||||
or deployment.archived_at is not None
|
||||
or deployment.provider.archived_at is not None
|
||||
or not deployment.enabled
|
||||
or not deployment.provider.enabled
|
||||
):
|
||||
invocation.status = "failed"
|
||||
invocation.error_code = "deployment_unavailable"
|
||||
invocation.error_message = "模型配置或渠道未启用"
|
||||
@@ -110,6 +130,27 @@ def execute_invocation(
|
||||
invocation.save(update_fields=["status", "error_code", "error_message", "finished_at"])
|
||||
return invocation
|
||||
|
||||
request_messages = messages
|
||||
response_format = None
|
||||
structured_metadata = None
|
||||
if output_schema is not None:
|
||||
try:
|
||||
request_messages, response_format, structured_metadata = build_structured_request(
|
||||
messages,
|
||||
mode=deployment.structured_output_mode,
|
||||
output_schema=output_schema,
|
||||
schema_name=schema_name or f"invocation_{invocation.public_id.hex}",
|
||||
)
|
||||
except StructuredOutputError as error:
|
||||
invocation.status = "failed"
|
||||
invocation.error_code = error.code
|
||||
invocation.error_message = str(error)
|
||||
invocation.finished_at = timezone.now()
|
||||
invocation.save(
|
||||
update_fields=["status", "error_code", "error_message", "finished_at"]
|
||||
)
|
||||
return invocation
|
||||
|
||||
invocation.status = "running"
|
||||
invocation.started_at = timezone.now()
|
||||
invocation.model_snapshot = {
|
||||
@@ -121,8 +162,18 @@ def execute_invocation(
|
||||
"capability": deployment.capability,
|
||||
"temperature": temperature if temperature is not None else deployment.default_temperature,
|
||||
"max_output_tokens": max_output_tokens or deployment.max_output_tokens,
|
||||
"structured_output": structured_metadata,
|
||||
}
|
||||
invocation.save(update_fields=["status", "started_at", "model_snapshot"])
|
||||
if structured_metadata is not None:
|
||||
invocation.input_snapshot = {
|
||||
**invocation.input_snapshot,
|
||||
"structured_output": structured_metadata,
|
||||
"output_schema": output_schema,
|
||||
"response_format": response_format,
|
||||
}
|
||||
invocation.save(
|
||||
update_fields=["status", "started_at", "model_snapshot", "input_snapshot"]
|
||||
)
|
||||
started = monotonic()
|
||||
result: CompletionResult | None = None
|
||||
last_error: Exception | None = None
|
||||
@@ -136,17 +187,21 @@ def execute_invocation(
|
||||
try:
|
||||
result = _request_completion(
|
||||
deployment,
|
||||
messages,
|
||||
request_messages,
|
||||
temperature=temperature
|
||||
if temperature is not None
|
||||
else deployment.default_temperature,
|
||||
max_output_tokens=max_output_tokens or deployment.max_output_tokens,
|
||||
response_format=response_format,
|
||||
timeout_seconds=remaining,
|
||||
)
|
||||
invocation.retry_count = attempt
|
||||
break
|
||||
except Exception as error: # provider libraries expose several subclasses
|
||||
code, message, transient = _error_details(error)
|
||||
if response_format is not None and code == "bad_request":
|
||||
code = "structured_output_unsupported"
|
||||
message = "模型渠道拒绝当前结构化输出配置"
|
||||
last_error = error
|
||||
invocation.retry_count = attempt
|
||||
if not transient or attempt == 1:
|
||||
@@ -199,3 +254,21 @@ def create_model_test(
|
||||
input_snapshot={"messages": [{"role": "user", "content": prompt}]},
|
||||
created_by=user,
|
||||
)
|
||||
|
||||
|
||||
def create_structured_output_test(
|
||||
deployment: ModelDeployment,
|
||||
user=None,
|
||||
) -> ModelInvocation:
|
||||
"""创建单次、不可修复的结构化输出能力测试。"""
|
||||
|
||||
return ModelInvocation.objects.create(
|
||||
deployment=deployment,
|
||||
invocation_type="structured_output_test",
|
||||
input_snapshot={
|
||||
"messages": [{"role": "user", "content": STRUCTURED_OUTPUT_TEST_PROMPT}],
|
||||
"output_schema": STRUCTURED_OUTPUT_TEST_SCHEMA,
|
||||
"schema_name": f"deployment_{deployment.id}_structured_output_test",
|
||||
},
|
||||
created_by=user,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user