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