mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-15 02:17:26 +00:00
Fix OpenAI tool compatibility (#509)
* Fix OpenAI tool compatibility; add agent mode for OpenAI compatible servers like Llama.cpp; add testcase to verify completeness
This commit is contained in:
1 parent
395fcc4126
commit
b299422699
5 files changed
+61
-3
No files matched your search
@@ -552,6 +552,8 @@ Choose the context that best matches the type of integration you are using.
|
||||
When launching Serena, specify the context using `--context <context-name>`.
|
||||
Note that for cases where parameter lists are specified (e.g. Claude Desktop), you must add two parameters to the list.
|
||||
|
||||
If you are using a local server (such as Llama.cpp) which requires you to use OpenAI-compatible tool descriptions, use context `oaicompat-agent` instead of `agent`.
|
||||
|
||||
#### Modes
|
||||
|
||||
Modes further refine Serena's behavior for specific types of tasks or interaction styles. Multiple modes can be active simultaneously, allowing you to combine their effects. Modes influence the system prompt and can also alter the set of available tools by excluding certain ones.
|
||||
|
||||
+11
-2
@@ -38,7 +38,7 @@ def configure_logging(*args, **kwargs) -> None: # type: ignore
|
||||
|
||||
|
||||
# patch the logging configuration function in fastmcp, because it's hard-coded and broken
|
||||
server.configure_logging = configure_logging
|
||||
server.configure_logging = configure_logging # type: ignore
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -106,6 +106,15 @@ class SerenaMCPFactory:
|
||||
# ---- simplify anyOf/oneOf if they only differ by integer/number ----
|
||||
for key in ("oneOf", "anyOf"):
|
||||
if key in node and isinstance(node[key], list):
|
||||
# Special case: anyOf or oneOf with "type X" and "null"
|
||||
if len(node[key]) == 2:
|
||||
types = [sub.get("type") for sub in node[key]]
|
||||
if "null" in types:
|
||||
non_null_type = next(t for t in types if t != "null")
|
||||
if isinstance(non_null_type, str):
|
||||
node["type"] = non_null_type
|
||||
node.pop(key, None)
|
||||
continue
|
||||
simplified = []
|
||||
changed = False
|
||||
for sub in node[key]:
|
||||
@@ -333,7 +342,7 @@ class SerenaMCPFactorySingleProcess(SerenaMCPFactory):
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]:
|
||||
openai_tool_compatible = self.context.name in ["chatgpt", "codex"]
|
||||
openai_tool_compatible = self.context.name in ["chatgpt", "codex", "oaicompat-agent"]
|
||||
self._set_mcp_tools(mcp_server, openai_tool_compatible=openai_tool_compatible)
|
||||
log.info("MCP server lifetime setup complete")
|
||||
yield
|
||||
@@ -0,0 +1,8 @@
|
||||
description: All tools except InitialInstructionsTool for agent context, uses OpenAI compatible tool definitions
|
||||
prompt: |
|
||||
You are running in agent context where the system prompt is provided externally. You should use symbolic
|
||||
tools when possible for code understanding and modification.
|
||||
excluded_tools:
|
||||
- initial_instructions
|
||||
|
||||
tool_description_overrides: {}
|
||||
@@ -25,7 +25,7 @@ class BaseMockTool(Tool):
|
||||
"""A mock Tool class for testing."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(MockAgent())
|
||||
super().__init__(MockAgent()) # type: ignore
|
||||
|
||||
|
||||
class BasicTool(BaseMockTool):
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.config.serena_config import SerenaConfig
|
||||
from serena.mcp import SerenaMCPFactorySingleProcess
|
||||
from serena.tools.tools_base import ToolRegistry
|
||||
|
||||
|
||||
@pytest.mark.parametrize("context", ("chatgpt", "codex", "oaicompat-agent"))
|
||||
def test_all_tool_parameters_have_type(context):
|
||||
"""
|
||||
For every tool exposed by Serena, ensure that the generated
|
||||
Open‑AI schema contains a ``type`` entry for each parameter.
|
||||
"""
|
||||
cfg = SerenaConfig(gui_log_window_enabled=False, web_dashboard=False, log_level=logging.ERROR)
|
||||
registry = ToolRegistry()
|
||||
cfg.included_optional_tools = tuple(registry.get_tool_names_optional())
|
||||
factory = SerenaMCPFactorySingleProcess(context=context)
|
||||
# Initialize the agent so that the tools are available
|
||||
factory._instantiate_agent(cfg, [])
|
||||
tools = list(factory._iter_tools())
|
||||
|
||||
for tool in tools:
|
||||
mcp_tool = factory.make_mcp_tool(tool, openai_tool_compatible=True)
|
||||
params = mcp_tool.parameters
|
||||
|
||||
# Collect any parameter that lacks a type
|
||||
issues = []
|
||||
print(f"Checking tool {tool}")
|
||||
|
||||
if "properties" not in params:
|
||||
issues.append(f"Tool {tool.get_name()!r} missing properties section")
|
||||
else:
|
||||
for pname, prop in params["properties"].items():
|
||||
if "type" not in prop:
|
||||
issues.append(f"Tool {tool.get_name()!r} parameter {pname!r} missing 'type'")
|
||||
if issues:
|
||||
raise AssertionError("\n".join(issues))
|
||||
Reference in new issue
Block a user