Fix agent tool support, fix many other bugs

This commit is contained in:
Shivering Ocelot committed 2026-08-23 00:36:37 -05:00
1 parent 2b44dcebf4
commit 6c5140fb92
5 files changed
+40 -17

No files matched your search

+4 -4
View File
@@ -34,6 +34,7 @@ class Agent:
self.config = {field.name: data.get(field.name, field.default) for field in getattr(agent_class, 'schema_fields', [])}
self.resources = resources
# should be in expert init?
self.tools = []
for resource in self.resources:
if resource.kind != 'tool': continue
@@ -66,7 +67,6 @@ class Agent:
**kwargs,
},
)
await self.run(run)
out_stream = Stream(self.app)
def on_output_event(stream):
@@ -77,6 +77,7 @@ class Agent:
out_stream.close()
run.on('output', on_output_event)
run.on('done', on_done)
await self.run(run)
return out_stream
@@ -95,14 +96,11 @@ class Agent:
user_id=self.user_id,
input={
"messages": messages,
"tools": getattr(self, "tools", []),
"chat_id": message.chat_id,
"thread_id": message.thread_id,
},
)
await self.run(run)
async def on_output_event(stream):
print("[agent] run output " + repr(stream))
new_message = await self.app.send_chat_message(
@@ -118,6 +116,8 @@ class Agent:
})
await run.on_async('output', on_output_event)
await self.run(run)
return run
def _role_for_message(self, message):
+13 -5
View File
@@ -24,11 +24,14 @@ class ExpertAgent(Agent):
async def run(self, run):
opts = {}
using_tools = False
# Is this roon using *Agent's* tools, as opposed to run tools
using_agent_tools = False
if 'tools' not in run.input:
# Agent tools will fully replace run tools if none are present,
# otherwise they won't be used
if self.tools:
run.input['tools'] = self.tools
using_tools = True
using_agent_tools = True
if self.config.get("temperature") is not None:
opts["temperature"] = self.config["temperature"]
@@ -52,12 +55,11 @@ class ExpertAgent(Agent):
output_stream = stream
run.on('output', on_output)
run.on('inference-end', lambda event: inference_end.set())
run.on('tool-call', lambda tc: tool_calls.append(tc))
run.on('tool-call', lambda tc: print(f"[expert] tool call: {tc}") or tool_calls.append(tc))
while True:
# Reset data
inference_end.clear()
tool_calls = []
output_stream = None
# Call inference, passing the run to get events out
@@ -73,8 +75,10 @@ class ExpertAgent(Agent):
await inference_end.wait()
messages.append(inference.LlamaMessage('assistant', output_stream and await output_stream.content_wait() or ""))
print(f"[expert] collected tool calls: {len(tool_calls)}, using? {using_agent_tools}")
if using_tools and tool_calls:
if using_agent_tools and tool_calls:
print("Will execute tool calls")
for tc in tool_calls:
tool = next(t for t in self.tools if t.name == tc.tool_name)
if not tool:
@@ -82,7 +86,11 @@ class ExpertAgent(Agent):
continue
tc.result = await tool.execute_async(**tc.arguments)
messages.append(inference.LlamaMessage('tool', tc))
tool_calls = []
# Buffer time to let openrouter rest?
await asyncio.sleep(2)
else:
print("No tool calls to execute, finish")
break
run.close()
+12 -8
View File
@@ -57,7 +57,7 @@ def format_openai(messages):
formatted.append({
'role': 'tool',
'tool_call_id': msg.content.id,
'content': json.dumps(msg.content.output),
'content': json.dumps(msg.content.result),
})
else:
fmsg = {'role': msg.role, 'content': msg.content}
@@ -337,6 +337,9 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
'model': model,
'messages': format_openai(messages),
'stream': True,
# Options
'timeout_ms': 45_000,
'retries': 3,
}
# Pass tools in OpenAI-compatible format if provided
@@ -344,7 +347,6 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
if formatted_tools:
kwargs['tools'] = formatted_tools
try:
stream_response = await app.openrouter_client.chat.send_async(**kwargs)
# OpenAI format streams tool calls in parts
@@ -390,17 +392,19 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
except Exception:
print("[openrouter] Bad tool call arguments for " + repr(tc))
print(traceback.format_exc())
except asyncio.CancelledError:
print(f"[openrouter] task cancelled for run {agent_run.id}")
raise
except Exception:
print(f"[openrouter] exception during inference")
print(traceback.format_exc())
emit('close')
print("END OPENROUTER INFERENCE")
def handle_done(task):
try:
task.result()
except Exception as e:
print(f"[openrouter] Worker error: {e}")
print(traceback.format_exc())
task = asyncio.create_task(worker())
task.add_done_callback(handle_done)
agent_run.task = task
+2
View File
@@ -111,6 +111,7 @@ async def start_server(app, shutdown_event):
try:
user_id = auth_manager.load_token(auth_req['token'])
if not await app.verify_user_id(user_id):
print("Invalid Token");
await websocket.send(json.dumps({'error': 'invalid credentials'}))
return
except Exception as err:
@@ -125,6 +126,7 @@ async def start_server(app, shutdown_event):
user_id = app.verify_user_credentials(auth_req['username'], auth_req['password'])
if user_id is None:
print("Invalid Credentials");
await websocket.send(json.dumps({'error': 'invalid credentials'}))
return
+9
View File
@@ -10,6 +10,14 @@ function objectView (name, mod) {
}
}
function staticView (name, mod) {
return {
name,
path: `/${name}`,
component: mod
}
}
// These routers must be handled by the server by rendering the index page
// file: backend/server.py
const router = createRouter({
@@ -50,6 +58,7 @@ const router = createRouter({
},
objectView('chat', () => import('../views/Chat.vue')),
objectView('agent', () => import('../views/Agent.vue')),
staticView('user', () => import('../views/User.vue')),
],
})