0f38d8d588
Includes deep-agents-ui integration, rework detection via Gitea, tool-call sanitization fixes, and startup scripts.
63 lines
2.6 KiB
Python
63 lines
2.6 KiB
Python
"""Middleware: блокирует вызовы несуществующих инструментов."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from langchain.agents.middleware import AgentMiddleware, AgentState
|
|
from langchain_core.messages import ToolMessage
|
|
from langchain_core.tools import BaseTool
|
|
|
|
|
|
class SanitizeToolCallsMiddleware(AgentMiddleware[AgentState[Any], Any]):
|
|
"""Перехватывает вызовы инструментов с неизвестными именами и возвращает
|
|
понятное сообщение об ошибке вместо падения рантайма."""
|
|
|
|
def __init__(self, known_tools: set[str]):
|
|
self.known_tools = known_tools
|
|
self.tools_by_name: dict[str, BaseTool] = {}
|
|
|
|
@staticmethod
|
|
def _normalize_tool_name(name: str) -> str:
|
|
# gpt-oss иногда шлёт read_file<|channel|>commentary вместо read_file
|
|
if "<|channel|>" in name:
|
|
name = name.split("<|channel|>", 1)[0]
|
|
return name.strip()
|
|
|
|
def _prepare_request(self, request) -> str:
|
|
"""Нормализует имя и перепривязывает tool после lookup в ToolNode."""
|
|
raw_name = request.tool_call.get("name", "")
|
|
name = self._normalize_tool_name(raw_name)
|
|
if name != raw_name:
|
|
request.tool_call["name"] = name
|
|
if request.tool is None and name in self.tools_by_name:
|
|
request.tool = self.tools_by_name[name]
|
|
return name
|
|
|
|
def _reject(self, request, name: str) -> ToolMessage:
|
|
available = sorted(self.known_tools)
|
|
return ToolMessage(
|
|
content=(
|
|
f"Инструмент '{name}' не существует. "
|
|
f"Доступные инструменты: {available}. "
|
|
"Исправь имя инструмента и попробуй снова."
|
|
),
|
|
tool_call_id=request.tool_call["id"],
|
|
name=name,
|
|
)
|
|
|
|
def wrap_tool_call(self, request, handler):
|
|
name = self._prepare_request(request)
|
|
if name not in self.known_tools:
|
|
return self._reject(request, name)
|
|
if request.tool is None:
|
|
return self._reject(request, name)
|
|
return handler(request)
|
|
|
|
async def awrap_tool_call(self, request, handler):
|
|
name = self._prepare_request(request)
|
|
if name not in self.known_tools:
|
|
return self._reject(request, name)
|
|
if request.tool is None:
|
|
return self._reject(request, name)
|
|
return await handler(request)
|