release: bump version to 0.62.0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled

This commit is contained in:
linkong
2026-05-21 01:37:32 +08:00
parent 5c65ee24d6
commit fbca381512
138 changed files with 21303 additions and 5721 deletions

View File

@@ -45,6 +45,7 @@ def get_provider_service(
x_ai_model: str | None = Header(default=None),
x_ai_max_tokens: str | None = Header(default=None),
x_ai_anthropic_version: str | None = Header(default=None),
x_ai_model_provider_apis: str | None = Header(default=None),
) -> ProviderService:
overrides = {
"provider": x_ai_provider,
@@ -53,6 +54,7 @@ def get_provider_service(
"api_key": x_ai_api_key,
"model": x_ai_model,
"anthropic_version": x_ai_anthropic_version,
"model_provider_apis": x_ai_model_provider_apis,
}
if x_ai_max_tokens:
overrides["max_tokens"] = x_ai_max_tokens

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
import json
from typing import Any
import httpx
@@ -14,7 +15,6 @@ from aiprovider.schemas import (
SituationalAnalysisResponse,
)
def _normalize_provider(value: str) -> str:
return (value or "disabled").strip().lower()
@@ -62,6 +62,9 @@ class ProviderService:
self.anthropic_version = str(
overrides.get("anthropic_version") or settings.AI_ANTHROPIC_VERSION
)
self.model_provider_apis = self._parse_model_provider_apis(
overrides.get("model_provider_apis")
)
def get_status(self) -> AIProviderStatusResponse:
enabled = self.provider != "disabled"
@@ -93,11 +96,13 @@ class ProviderService:
prompt = self._build_prompt(payload)
if self.provider_api == "openai-completions":
provider_api = self._resolve_model_provider_api(model)
if provider_api == "openai-completions":
data = await self._request_openai_compatible(model, prompt, payload.system_prompt)
content = self._extract_openai_content(data)
content_blocks = self._extract_openai_blocks(data)
elif self.provider_api == "anthropic-messages":
elif provider_api == "anthropic-messages":
data = await self._request_anthropic_messages(
model,
prompt,
@@ -106,7 +111,7 @@ class ProviderService:
)
content = self._extract_anthropic_content(data)
content_blocks = self._extract_anthropic_blocks(data)
elif self.provider_api == "ollama-generate":
elif provider_api == "ollama-generate":
data = await self._request_ollama(model, prompt, payload.system_prompt)
content = self._extract_ollama_content(data)
content_blocks = self._extract_ollama_blocks(data)
@@ -132,6 +137,26 @@ class ProviderService:
def _requires_api_key(self) -> bool:
return self.provider_api != "ollama-generate"
def _resolve_model_provider_api(self, model: str) -> str:
return self.model_provider_apis.get(model) or self.provider_api
def _parse_model_provider_apis(self, value: Any) -> dict[str, str]:
if isinstance(value, dict):
raw = value
elif isinstance(value, str) and value.strip():
try:
parsed = json.loads(value)
except json.JSONDecodeError:
return {}
raw = parsed if isinstance(parsed, dict) else {}
else:
raw = {}
return {
str(model): _normalize_provider_api(str(provider_api))
for model, provider_api in raw.items()
if model and provider_api
}
def _build_prompt(self, payload: SituationalAnalysisRequest) -> str:
sections = [
f"任务标题:\n{payload.title}",
@@ -314,13 +339,19 @@ class ProviderService:
message = choices[0].get("message") or {}
content = message.get("content")
if isinstance(content, str):
return content
if content:
return content
reasoning_content = message.get("reasoning_content")
return reasoning_content if isinstance(reasoning_content, str) else ""
if isinstance(content, list):
return "".join(
item.get("text", "")
for item in content
if isinstance(item, dict)
)
reasoning_content = message.get("reasoning_content")
if isinstance(reasoning_content, str):
return reasoning_content
return ""
def _extract_openai_blocks(self, payload: dict[str, Any]) -> list[AIContentBlock]:
@@ -331,9 +362,14 @@ class ProviderService:
message = choices[0].get("message") or {}
content = message.get("content")
if isinstance(content, str):
return [AIContentBlock(type="text", text=content)]
blocks = [AIContentBlock(type="text", text=content)] if content else []
reasoning_content = message.get("reasoning_content")
if isinstance(reasoning_content, str) and reasoning_content:
blocks.append(AIContentBlock(type="thinking", thinking=reasoning_content))
return blocks
if not isinstance(content, list):
return []
reasoning_content = message.get("reasoning_content")
return [AIContentBlock(type="thinking", thinking=reasoning_content)] if isinstance(reasoning_content, str) and reasoning_content else []
blocks: list[AIContentBlock] = []
for item in content:
@@ -346,7 +382,11 @@ class ProviderService:
metadata={k: v for k, v in item.items() if k not in {"type", "text"}},
)
)
reasoning_content = message.get("reasoning_content")
if isinstance(reasoning_content, str) and reasoning_content:
blocks.append(AIContentBlock(type="thinking", thinking=reasoning_content))
return blocks
def _extract_anthropic_content(self, payload: dict[str, Any]) -> str:
content = payload.get("content")
if isinstance(content, str):