feat: OpenAI-compatible orchestrator + backend auto-routing
- openai_orchestrator.py — new ReAct tool loop engine for any OpenAI-compatible endpoint (OpenRouter, Open WebUI, Ollama, LiteLLM); model handles both tool loop and final response, no Claude handoff needed - tools/__init__.py — auto-derive OpenAI JSON Schema from existing Gemini FunctionDeclarations so tool definitions have a single source of truth - routers/orchestrator.py — route to openai_orchestrator when model registry "orchestrator" role resolves to a local_openai type host - routers/chat.py — pass role to _backend_label(); fix fallback_used logic (only meaningful for explicit backend overrides, not auto-routing) - static/app.js — add null/"auto" to backend cycle; fetch local model hint without overriding the auto default on page load - model_registry.py — _normalize() back-fills host_type on old registry files - requirements.txt — add openai>=1.0.0 - ARCH__BACKENDS.md — document OpenAI-compat backend and routing logic Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -551,3 +551,61 @@ async def call_tool(name: str, args: dict) -> str:
|
||||
if fn is None:
|
||||
return f"Unknown tool: {name}"
|
||||
return await fn(**args)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OpenAI JSON Schema format — auto-derived from the Gemini declarations above
|
||||
# so there is a single source of truth for tool definitions.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_GEMINI_TYPE_TO_JSON = {
|
||||
"OBJECT": "object",
|
||||
"STRING": "string",
|
||||
"INTEGER": "integer",
|
||||
"NUMBER": "number",
|
||||
"BOOLEAN": "boolean",
|
||||
"ARRAY": "array",
|
||||
}
|
||||
|
||||
|
||||
def _schema_to_json(schema) -> dict:
|
||||
"""Recursively convert a Gemini types.Schema to a JSON Schema dict."""
|
||||
type_name = getattr(getattr(schema, "type", None), "name", "STRING")
|
||||
result: dict = {"type": _GEMINI_TYPE_TO_JSON.get(type_name, "string")}
|
||||
|
||||
if getattr(schema, "description", None):
|
||||
result["description"] = schema.description
|
||||
|
||||
props = getattr(schema, "properties", None) or {}
|
||||
if result["type"] == "object":
|
||||
result["properties"] = {k: _schema_to_json(v) for k, v in props.items()}
|
||||
|
||||
req = getattr(schema, "required", None)
|
||||
if req:
|
||||
result["required"] = list(req)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _build_openai_tools() -> list[dict]:
|
||||
"""Convert TOOL_DECLARATIONS (Gemini format) to OpenAI tool schemas."""
|
||||
out = []
|
||||
for decl in TOOL_DECLARATIONS[0].function_declarations:
|
||||
params = (
|
||||
_schema_to_json(decl.parameters)
|
||||
if decl.parameters
|
||||
else {"type": "object", "properties": {}}
|
||||
)
|
||||
out.append({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": decl.name,
|
||||
"description": decl.description or "",
|
||||
"parameters": params,
|
||||
},
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
# OpenAI-format tool list — pass to client.chat.completions.create(tools=...)
|
||||
OPENAI_TOOL_SCHEMAS: list[dict] = _build_openai_tools()
|
||||
|
||||
Reference in New Issue
Block a user