feat(ai): phase B function calling in-app (agent loop + endpoint /agent)
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Successful in 1m13s
CI / build (push) Successful in 36s
CI / e2e (push) Successful in 10m13s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Successful in 1m13s
CI / build (push) Successful in 36s
CI / e2e (push) Successful in 10m13s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- backend/ai_chat.py: chat_completion provider-agnostique (OpenAI-compat tools/tool_calls + Gemini functionDeclarations/functionCall), retry sans tools si rejete - backend/agent/loop.py: run_agent multi-etapes (limite 10, truncation, confirmation two-step), LLM injectable - endpoint opt-in POST /api/ai/bookslm/agent (events SSE tool/message/confirmation), extraction _resolve_system_prompt - tests: test_ai_chat.py, test_agent_loop.py + 3 tests endpoint (728 passed au total) - ROADMAP B1/B2/B3/B7 livres ; B4/B5/B6 restants
This commit is contained in:
+19
-15
@@ -117,7 +117,7 @@ DEFAULT_PROVIDER: ProviderName = os.getenv("AI_DEFAULT_PROVIDER", "deepseek") #
|
||||
|
||||
def get_default_provider() -> str:
|
||||
"""Resolve the default provider: ``data/config.json`` > env > ``deepseek``."""
|
||||
provider = _read_app_config().get("ai_default_provider") or os.getenv("AI_DEFAULT_PROVIDER", "deepseek")
|
||||
provider: str = str(_read_app_config().get("ai_default_provider") or os.getenv("AI_DEFAULT_PROVIDER", "deepseek"))
|
||||
return provider if provider in PROVIDERS else "deepseek"
|
||||
|
||||
|
||||
@@ -152,6 +152,23 @@ def _get_provider_config(provider: ProviderName | None = None) -> dict:
|
||||
return {"name": p, **cfg}
|
||||
|
||||
|
||||
def _build_headers(cfg: dict) -> dict:
|
||||
"""Build HTTP headers for an OpenAI-compatible provider config.
|
||||
|
||||
Most providers use ``Authorization: Bearer KEY``. Some (Xiaomi MiMo) use a
|
||||
dedicated header like ``api-key: KEY`` — supported via the
|
||||
``auth_header_name`` key in PROVIDERS (defaults to ``Authorization``).
|
||||
"""
|
||||
header_name = cfg.get("auth_header_name") or "Authorization"
|
||||
header_value = cfg["auth_header"].format(api_key=cfg["api_key"])
|
||||
if header_name == "Authorization" and not header_value.lower().startswith("bearer "):
|
||||
header_value = "Bearer " + header_value
|
||||
return {
|
||||
header_name: header_value,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
|
||||
async def _call_deepseek_openrouter(prompt: str, system: str, provider: ProviderName | None = None,
|
||||
temperature: float = 0.7, max_tokens: int = 2048) -> str:
|
||||
"""Call OpenAI-compatible API (DeepSeek, OpenRouter, Xiaomi MiMo, etc.)."""
|
||||
@@ -159,20 +176,7 @@ async def _call_deepseek_openrouter(prompt: str, system: str, provider: Provider
|
||||
# Debug: log masked key to diagnose 401
|
||||
key_preview = cfg["api_key"][:8] + "..." + cfg["api_key"][-4:] if len(cfg["api_key"]) > 12 else "***"
|
||||
logger.info(f"AI call: provider={cfg['name']} model={cfg['model']} key={key_preview}")
|
||||
# Most providers use "Authorization: Bearer KEY". Some (Xiaomi MiMo) use a
|
||||
# dedicated header like "api-key: KEY". We support both via the
|
||||
# `auth_header_name` key in PROVIDERS — defaults to "Authorization".
|
||||
header_name = cfg.get("auth_header_name") or "Authorization"
|
||||
header_value = cfg["auth_header"].format(api_key=cfg["api_key"])
|
||||
# If the auth_header template doesn't include "Bearer " but the default
|
||||
# header is Authorization, prepend it. This preserves backward compatibility
|
||||
# for providers that store just the raw key.
|
||||
if header_name == "Authorization" and not header_value.lower().startswith("bearer "):
|
||||
header_value = "Bearer " + header_value
|
||||
headers = {
|
||||
header_name: header_value,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
headers = _build_headers(cfg)
|
||||
payload = {
|
||||
"model": cfg["model"],
|
||||
"messages": [
|
||||
|
||||
Reference in New Issue
Block a user