Files

138 lines
4.6 KiB
Markdown

# Conol.ai Tool Use Protocol
Gateway v6.0+ supports OpenAI-compatible tool calling through conol.ai's SSE API.
## Sending Tools to Conol.ai
Tools are passed in the session creation body as an Anthropic-format array:
```python
session_body = {
"source": {"type": "home"},
"messages": conol_msgs,
"timezone": "Europe/Kyiv",
"agentModel": model,
"agentEffort": "low",
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
],
}
```
Both Anthropic-format (`name`, `input_schema`) and OpenAI-format (`type: "function"`, `function: {name, parameters}`) are accepted by conol.ai. The gateway converts OpenAI format to Anthropic before sending.
## Receiving Tool Calls from SSE
Conol.ai emits tool calls as SSE `history_delta` events with `tool_call` log entries:
```json
{
"type": "history_delta",
"stages": [{
"id": "...",
"logs": [{
"role": "assistant",
"type": "tool_call",
"content": [{
"name": "WebSearch",
"type": "tool_call",
"input": {"query": "Paris weather"},
"result": [{"text": "83°F, Overcast..."}]
}]
}]
}]
}
```
Key fields:
- `name` — tool name (string)
- `input` — JSON arguments (dict)
- `result` — optional: array of `{"text": "..."}` objects with tool output
## Converting to OpenAI Format
```python
def conol_to_openai_tool_calls(events):
tool_calls = []
for ev in events:
if ev.tool_name:
tool_calls.append({
"id": f"call_{uuid.uuid4().hex[:8]}",
"type": "function",
"function": {
"name": ev.tool_name,
"arguments": json.dumps(ev.tool_input, ensure_ascii=False),
},
})
return tool_calls
```
## Built-in Tools (Auto-called by conol.ai)
Conol.ai models have built-in tools that fire automatically even when no user tools are provided:
| Tool | Purpose |
|------|---------|
| `WebSearch` | Web search via Exa.ai |
| `Fetch` | Fetch URL content with prompt |
| `SetIntention` | Internal meta-tool for task routing |
| `WriteNote` | Create Obsidian-style notes |
| `ReadNote` | Read persisted notes |
These appear in SSE as `tool_call` logs alongside any user-provided tools.
## Streaming Tool Calls
In streaming mode, tool calls are emitted as SSE chunks with `delta.tool_calls[]`:
```json
{
"object": "chat.completion.chunk",
"choices": [{
"delta": {"tool_calls": [{"id": "call_abc", "type": "function", "function": {"name": "WebSearch", "arguments": "{\"query\":\"...\"}"}}]},
"finish_reason": null
}]
}
```
Then final chunk with `finish_reason: "tool_calls"` or `"stop"`.
## Tool Result Loop (Tool → Model → Response)
To continue a conversation after tool calls, send tool results back as messages with `role: "tool"`:
```json
{
"model": "gpt-5.6-luna",
"messages": [
{"role": "user", "content": "Weather in Paris?"},
{"role": "assistant", "content": null, "tool_calls": [{"id": "call_1", "function": {"name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}}]},
{"role": "tool", "tool_call_id": "call_1", "content": "83°F, Overcast"}
]
}
```
The gateway converts `role: "tool"` messages to text: `[Tool result #call_1]: 83°F, Overcast`.
## Pitfalls
### Duplicate Tool Calls
Conol.ai sometimes emits duplicate `tool_call` events in the SSE stream (same name, same arguments, different stage IDs). The gateway currently returns all of them. For production use, deduplicate by `(name, json.dumps(input))` key.
### Tool + Text in Same Response
Conol.ai models may return BOTH text content AND tool calls in the same response. OpenAI convention is `content: null` when tool_calls are present. The gateway currently returns both — downstream clients expecting strict OpenAI protocol may need `content: null` when `tool_calls` is non-empty.
### Tool Choice
`tool_choice: "auto"/"none"/"required"` is passed but conol.ai does not enforce strict tool choice — it always has its built-in tools available. For `tool_choice: "none"`, filter tool calls out in the gateway.
### Streaming + Tool Calls
When streaming with tools, conol.ai sends thinking text first, then tool calls, then final text. The gateway collects all events and emits tool calls first, then text. This causes a delay before the first chunk — the full response must arrive before streaming begins. Future improvement: buffer and emit as events arrive.