Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend
17 changes: 15 additions & 2 deletions src/api/routers/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,22 @@ def test_llm_config() -> OkResponse[str]:

try:
client = LLMClient(runtime_cfg)
# 暴露实际路由参数,便于排查「连不上 / 报错」时定位是 model 前缀还是 base_url 问题
routed_kwargs = client._litellm_kwargs(temperature=0.7, max_tokens=None)
diagnosis = {
"route": {
"model": routed_kwargs.get("model"),
"custom_llm_provider": routed_kwargs.get("custom_llm_provider"),
"api_base": routed_kwargs.get("api_base"),
"is_custom": (runtime_cfg.type or "").strip().lower() == "custom",
"llm_format": runtime_cfg.llm_format,
},
}
resp = client.call([{"role": "user", "content": "hello"}])
raw_json = json.dumps(_to_jsonable(resp.raw), ensure_ascii=False)
return OkResponse[str](data=raw_json)
diagnosis["reply"] = resp.content
diagnosis["usage"] = resp.usage
diagnosis["ok"] = True
return OkResponse[str](data=json.dumps(diagnosis, ensure_ascii=False))
except Exception as ex:
return OkResponse[str](
success=False,
Expand Down
5 changes: 5 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ class LLMConfig:
base_url: Optional[str] = None
api_version: Optional[str] = None
azure_endpoint: Optional[str] = None
# API 协议格式:仅对「自定义 Provider」生效,用于路由到对应的上游端点。
# "openai" -> OpenAI 兼容(/v1/chat/completions)
# "anthropic" -> Anthropic 格式(/v1/messages)
# 为空时按 base_url 启发式推断(含 /anthropic 视作 anthropic,否则 openai)。
llm_format: Optional[str] = None


@dataclass
Expand Down
47 changes: 38 additions & 9 deletions src/llm/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,52 @@ def __init__(self, config: LLMConfig):
self.config = config

def _litellm_kwargs(self, temperature: float = 0.7, max_tokens: Optional[int] = None, **kwargs) -> Dict:
"""从 config 构建 litellm completion 参数,不依赖环境变量"""
model = self.config.model
if "/" not in model and self.config.provider:
model = f"{self.config.provider}/{model}"
"""从 config 构建 litellm completion 参数,不依赖环境变量。

路由策略(单点收口):
- 内置 provider(deepseek / anthropic / openai / azure ...):使用 litellm 原生
`provider/model` 形式,由 litellm 自行解析 base_url / 鉴权。
- 自定义 provider:统一通过 ``custom_llm_provider`` 指定上游协议格式,
传 **裸模型名**(不带 provider 前缀)+ ``api_base``,避免把任意 provider ID
拼成 ``<custom-id>/<model>`` 这种 litellm 无法识别的前缀。
协议格式由 ``llm_format`` 决定(openai / anthropic),缺省时按 base_url 启发式。
"""
model = (self.config.model or "").strip()
provider = (self.config.provider or "").strip()
is_custom = (self.config.type or "").strip().lower() == "custom"

kw: Dict = {
"model": model,
"temperature": temperature,
"max_tokens": max_tokens,
}
if self.config.api_key:
kw["api_key"] = self.config.api_key
# base_url:自建或自定义端点;Azure 时也可用 azure_endpoint
api_base = self.config.base_url or getattr(self.config, "azure_endpoint", None)
if api_base and self.config.type != "builtin":
kw["api_base"] = api_base
if getattr(self.config, "api_version", None):
kw["api_version"] = self.config.api_version

api_base = self.config.base_url or getattr(self.config, "azure_endpoint", None)

if is_custom:
# 自定义 Provider:不要拼 provider 前缀,用 custom_llm_provider 路由协议格式
fmt = (self.config.llm_format or "").strip().lower()
if not fmt:
# 启发式:base_url 含 /anthropic 视作 Anthropic 格式端点,否则 OpenAI 兼容
fmt = "anthropic" if "/anthropic" in (api_base or "").lower() else "openai"
fmt = fmt if fmt in ("openai", "anthropic") else "openai"
kw["model"] = model
kw["custom_llm_provider"] = fmt
if api_base:
kw["api_base"] = api_base
else:
# 内置 provider:litellm 原生 provider/model 形式
litellm_model = model
if "/" not in litellm_model and provider:
litellm_model = f"{provider}/{litellm_model}"
kw["model"] = litellm_model
# 自建/兼容端点:builtin 模式下仍允许覆盖 base_url(如自托管的 openai 网关)
if api_base:
kw["api_base"] = api_base

kw.update(kwargs)
return {k: v for k, v in kw.items() if v is not None}

Expand Down
2 changes: 2 additions & 0 deletions src/schemas/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class LLMConfigUpdate(BaseModel):
LLM_model: Optional[str] = None
LLM_baseurl: Optional[str] = None
type: Optional[str] = None
# 自定义 Provider 的 API 协议格式:openai | anthropic
LLM_format: Optional[str] = None


class CodeAgentConfigUpdate(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions src/services/config_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def get_llm_runtime_config() -> Optional[LLMConfig]:
model=model,
type=tp,
base_url=cfg.get("LLM_baseurl") or None,
llm_format=(cfg.get("LLM_format") or "").strip().lower() or None,
)


Expand Down