feat(bot): GigaChat function-calling provider for the chat agent; never 500 on provider errors
CI / ruff · pytest (pull_request) Successful in 2m15s
CI / ruff · pytest (pull_request) Successful in 2m15s
OpenRouter returns 403 «Access denied by security policy» from the RF server, and parsing that string error crashed the request (500 instead of the fallback answer). Error parsing is robust now and any agent exception degrades to the extractive answer. GigaChat tool provider (GigaChat-2-Pro by default) converts the agent's OpenAI-style history to function_call / role=function; auto mode prefers GigaChat when credentials are set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from app.config import Settings, get_settings
|
||||
from app.core.types import ScrubbedText
|
||||
from app.llm.agent.prompts import SYSTEM_PROMPT
|
||||
from app.llm.agent.providers import (
|
||||
GigaChatToolProvider,
|
||||
MockAgentProvider,
|
||||
OpenRouterProvider,
|
||||
ProviderError,
|
||||
@@ -49,7 +50,7 @@ def build_provider(settings: Settings | None = None) -> ToolChatProvider | None:
|
||||
s = settings or get_settings()
|
||||
mode = s.chat_agent
|
||||
if mode == "auto":
|
||||
mode = "openrouter" if s.openrouter_api_key else "off"
|
||||
mode = "gigachat" if s.gigachat_credentials else "openrouter" if s.openrouter_api_key else "off"
|
||||
if mode == "openrouter":
|
||||
if not s.openrouter_api_key:
|
||||
log.warning("chat agent: openrouter selected without OPENROUTER_API_KEY — disabled")
|
||||
@@ -58,15 +59,16 @@ def build_provider(settings: Settings | None = None) -> ToolChatProvider | None:
|
||||
if mode == "mock":
|
||||
return MockAgentProvider()
|
||||
if mode == "gigachat":
|
||||
# TODO(GigaChat): function calling via the `gigachat` package (functions=..., message.function_call).
|
||||
log.warning("chat agent: gigachat provider not implemented yet — disabled")
|
||||
return None
|
||||
if not s.gigachat_credentials:
|
||||
log.warning("chat agent: gigachat selected without GIGACHAT_CREDENTIALS — disabled")
|
||||
return None
|
||||
return GigaChatToolProvider(s)
|
||||
return None
|
||||
|
||||
|
||||
async def _chat_with_retry(provider: ToolChatProvider, messages: list[dict[str, Any]]):
|
||||
"""One retry: OpenRouter occasionally fails a single call (seen live as intermittent ProviderError)."""
|
||||
wire = _wire(messages) if provider.name != "mock" else messages
|
||||
wire = _wire(messages) if provider.name == "openrouter" else messages # gigachat needs the tool `name`
|
||||
try:
|
||||
return await provider.chat(wire, TOOL_SPECS)
|
||||
except ProviderError as exc:
|
||||
@@ -139,6 +141,9 @@ async def answer_chat(
|
||||
except (ProviderError, TimeoutError) as exc:
|
||||
log.error("chat agent provider=%s failed: %s %s", provider.name, type(exc).__name__, exc)
|
||||
return None
|
||||
except Exception: # noqa: BLE001 — the agent is optional: any bug must degrade to the extractive answer, not a 500
|
||||
log.exception("chat agent provider=%s crashed", provider.name)
|
||||
return None
|
||||
|
||||
|
||||
def _finish(text: str, ctx: ToolContext, provider: str) -> AgentAnswer | None:
|
||||
|
||||
Reference in New Issue
Block a user