chore: commit remaining working changes
Backend: - agent: confirmation, persistence, app, langgraph_setup updates - routes: agent_superset_explore, environments, git helpers/operations - services: git sync refactoring - tests: git_status_route expanded Frontend: - Navbar: minor cleanup - Profile: i18n (en/ru), page enhancements, integration tests - New: _llm_params.py
This commit is contained in:
@@ -12,6 +12,7 @@ from typing import Any
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from src.agent._llm_params import chat_openai_kwargs
|
||||
from src.agent._tool_resolver import (
|
||||
extract_tool_call_from_state,
|
||||
find_tool,
|
||||
@@ -284,13 +285,12 @@ async def _format_tool_output_via_llm(
|
||||
config = await _fetch_llm_config()
|
||||
if config and config.get("configured"):
|
||||
try:
|
||||
llm = ChatOpenAI(
|
||||
llm = ChatOpenAI(**chat_openai_kwargs(
|
||||
model=config.get("default_model", "gpt-4o-mini"),
|
||||
base_url=config.get("base_url", "https://api.openai.com/v1"),
|
||||
api_key=config["api_key"],
|
||||
temperature=0,
|
||||
max_tokens=1024,
|
||||
)
|
||||
))
|
||||
prompt = (
|
||||
f"Tool '{tool_name}' returned this data:\n\n{text}\n\n"
|
||||
"Summarize this data in a concise, human-readable format. "
|
||||
|
||||
70
backend/src/agent/_llm_params.py
Normal file
70
backend/src/agent/_llm_params.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# backend/src/agent/_llm_params.py
|
||||
# #region AgentChat.LlmParams [C:3] [TYPE Module] [SEMANTICS agent-chat,llm,openai,compatibility]
|
||||
# @defgroup AgentChat Shared LLM parameter compatibility helpers.
|
||||
# @LAYER Service
|
||||
# @BRIEF Build provider-safe ChatOpenAI kwargs and raw OpenAI payloads.
|
||||
# @POST Unsupported sampling parameters are omitted for reasoning/codex models.
|
||||
# @RATIONALE Some OpenAI-compatible gateways reject temperature for reasoning/codex
|
||||
# models. Centralising the guard prevents resume, health-check, and title
|
||||
# generation paths from diverging.
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
_TEMPERATURE_UNSUPPORTED_PREFIXES = (
|
||||
"codex/",
|
||||
"omni/codex/",
|
||||
"gpt-5",
|
||||
"o1",
|
||||
"o3",
|
||||
"o4",
|
||||
)
|
||||
|
||||
|
||||
def _canonical_model_name(model: str | None) -> str:
|
||||
"""Return provider-stripped, lowercase model name for compatibility checks."""
|
||||
name = (model or "").strip().lower()
|
||||
if name.startswith(("codex/", "omni/codex/")):
|
||||
return name
|
||||
if "/" in name:
|
||||
return name.rsplit("/", 1)[-1]
|
||||
return name
|
||||
|
||||
|
||||
def supports_temperature(model: str | None) -> bool:
|
||||
"""Return False for model families whose APIs reject temperature."""
|
||||
name = _canonical_model_name(model)
|
||||
return not any(name.startswith(prefix) for prefix in _TEMPERATURE_UNSUPPORTED_PREFIXES)
|
||||
|
||||
|
||||
def chat_openai_kwargs(
|
||||
*,
|
||||
model: str,
|
||||
base_url: str | None,
|
||||
api_key: str,
|
||||
max_tokens: int,
|
||||
temperature: float = 0,
|
||||
) -> dict[str, Any]:
|
||||
"""Build ChatOpenAI kwargs without unsupported temperature for reasoning models."""
|
||||
kwargs: dict[str, Any] = {
|
||||
"model": model,
|
||||
"base_url": base_url,
|
||||
"api_key": api_key,
|
||||
"max_tokens": max_tokens,
|
||||
}
|
||||
if supports_temperature(model):
|
||||
kwargs["temperature"] = temperature
|
||||
return kwargs
|
||||
|
||||
|
||||
def add_temperature_if_supported(
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
model: str | None,
|
||||
temperature: float = 0,
|
||||
) -> dict[str, Any]:
|
||||
"""Mutate and return payload with temperature only when the model supports it."""
|
||||
if supports_temperature(model):
|
||||
payload["temperature"] = temperature
|
||||
return payload
|
||||
# #endregion AgentChat.LlmParams
|
||||
@@ -16,6 +16,7 @@ import uuid
|
||||
import httpx
|
||||
|
||||
from src.agent._config import AGENT_PREFETCH_DASHBOARD_LIMIT as _PREFETCH_LIMIT, FASTAPI_URL, SERVICE_JWT as _SERVICE_JWT
|
||||
from src.agent._llm_params import add_temperature_if_supported
|
||||
from src.core.logger import logger
|
||||
|
||||
SAVE_API_URL = FASTAPI_URL + "/api/agent/conversations/save"
|
||||
@@ -195,8 +196,8 @@ async def _call_llm_for_title(user_text: str) -> str | None:
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 15,
|
||||
"temperature": 0,
|
||||
}
|
||||
add_temperature_if_supported(payload, model=model)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -34,6 +34,7 @@ from langchain_openai import ChatOpenAI
|
||||
from openai import APIConnectionError, APITimeoutError, AuthenticationError
|
||||
|
||||
from src.agent._config import GRADIO_SERVER_NAME, GRADIO_SERVER_PORT, STORAGE_ROOT as _STORAGE_ROOT
|
||||
from src.agent._llm_params import chat_openai_kwargs
|
||||
from src.agent._confirmation import (
|
||||
_pending_confirmations,
|
||||
confirmation_payload,
|
||||
@@ -216,13 +217,12 @@ async def _check_llm_provider_health() -> str:
|
||||
if not config or not config.get("configured"):
|
||||
return _llm_status["status"]
|
||||
|
||||
llm = ChatOpenAI(
|
||||
llm = ChatOpenAI(**chat_openai_kwargs(
|
||||
model=config.get("default_model", "gpt-4o-mini"),
|
||||
base_url=config.get("base_url", "https://api.openai.com/v1"),
|
||||
api_key=config.get("api_key", ""),
|
||||
temperature=0,
|
||||
max_tokens=1,
|
||||
)
|
||||
))
|
||||
await llm.ainvoke([HumanMessage(content="ping")])
|
||||
_llm_status["status"] = "ok"
|
||||
_llm_status["last_error"] = ""
|
||||
@@ -244,6 +244,11 @@ async def _check_llm_provider_health() -> str:
|
||||
_llm_status["last_check_ts"] = time.time()
|
||||
return "auth_error"
|
||||
except Exception as exc:
|
||||
if "usage_metadata.total_tokens" in str(exc):
|
||||
_llm_status["status"] = "ok"
|
||||
_llm_status["last_error"] = ""
|
||||
_llm_status["last_check_ts"] = time.time()
|
||||
return "ok"
|
||||
logger.explore("LLM health check failed",
|
||||
error=str(exc),
|
||||
extra={"src": "AgentChat.GradioApp.LlmHealthCheck"})
|
||||
@@ -535,11 +540,24 @@ async def agent_handler( # noqa: C901 — intentionally complex C4 orchestratio
|
||||
if kind == "on_chat_model_stream":
|
||||
chunk = event["data"]["chunk"]
|
||||
if hasattr(chunk, "content") and chunk.content:
|
||||
content = chunk.content
|
||||
if isinstance(content, str):
|
||||
token_text = content
|
||||
elif isinstance(content, list):
|
||||
token_text = "".join(
|
||||
str(item.get("text") or item.get("content") or "")
|
||||
if isinstance(item, dict) else str(item)
|
||||
for item in content
|
||||
)
|
||||
else:
|
||||
token_text = str(content)
|
||||
if not token_text:
|
||||
continue
|
||||
emitted_any = True
|
||||
assistant_parts.append(chunk.content)
|
||||
assistant_parts.append(token_text)
|
||||
yield json.dumps({
|
||||
"content": chunk.content,
|
||||
"metadata": {"type": "stream_token", "token": chunk.content},
|
||||
"content": token_text,
|
||||
"metadata": {"type": "stream_token", "token": token_text},
|
||||
})
|
||||
elif kind == "on_tool_start":
|
||||
tool_name = event["name"]
|
||||
@@ -677,14 +695,15 @@ async def agent_handler( # noqa: C901 — intentionally complex C4 orchestratio
|
||||
"content": f"❌ Ошибка: {exc}",
|
||||
"metadata": {"type": "error", "code": "PROCESSING_ERROR", "detail": str(exc)},
|
||||
})
|
||||
await save_conversation(conv_id, visible_user_text, user_id, assistant_text="".join(assistant_parts))
|
||||
await save_conversation(conv_id, visible_user_text, user_id, assistant_text="".join(str(part) for part in assistant_parts))
|
||||
return
|
||||
|
||||
await save_conversation(conv_id, visible_user_text, user_id, assistant_text="".join(assistant_parts))
|
||||
assistant_text = "".join(str(part) for part in assistant_parts)
|
||||
await save_conversation(conv_id, visible_user_text, user_id, assistant_text=assistant_text)
|
||||
await _generate_title_best_effort(conv_id, visible_user_text)
|
||||
logger.reflect(
|
||||
"Agent handler completed",
|
||||
payload={"conv_id": conv_id, "assistant_len": len("".join(assistant_parts))},
|
||||
payload={"conv_id": conv_id, "assistant_len": len(assistant_text)},
|
||||
extra={"src": "AgentChat.GradioApp.Handler"},
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from langgraph.prebuilt import create_react_agent
|
||||
from psycopg.rows import dict_row
|
||||
|
||||
from src.agent._config import FASTAPI_URL, AGENT_CONFIRM_TOOLS, AGENT_INTERRUPT_BEFORE as _INTERRUPT_BEFORE
|
||||
from src.agent._llm_params import chat_openai_kwargs
|
||||
from src.core.logger import logger
|
||||
|
||||
# ── Monkey-patch: OpenAI SDK for Pydantic BaseModel classes ──
|
||||
@@ -167,13 +168,12 @@ async def create_agent(
|
||||
extra={"src": "AgentChat.LangGraph.Setup"},
|
||||
)
|
||||
|
||||
llm = ChatOpenAI(
|
||||
llm = ChatOpenAI(**chat_openai_kwargs(
|
||||
model=model,
|
||||
base_url=base_url,
|
||||
api_key=api_key,
|
||||
temperature=0,
|
||||
max_tokens=2048,
|
||||
)
|
||||
))
|
||||
|
||||
# System prompt — env_id injected deterministically, not in user message
|
||||
prompt = (
|
||||
|
||||
Reference in New Issue
Block a user