Fix agent tool support, fix many other bugs
This commit is contained in:
1 parent
2b44dcebf4
commit
6c5140fb92
5 files changed
+78
-55
No files matched your search
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
+50
-46
@@ -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,63 +347,64 @@ 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)
|
||||
stream_response = await app.openrouter_client.chat.send_async(**kwargs)
|
||||
|
||||
# OpenAI format streams tool calls in parts
|
||||
# We could, but I won't assume any tool call is complete until the message is over
|
||||
tool_calls = []
|
||||
# OpenAI format streams tool calls in parts
|
||||
# We could, but I won't assume any tool call is complete until the message is over
|
||||
tool_calls = []
|
||||
|
||||
async for event in stream_response:
|
||||
if agent_run and agent_run.cancelled:
|
||||
print(f"Cancelling openrouter inference for run {agent_run.id}")
|
||||
# TODO: Need to cancell?
|
||||
break
|
||||
async for event in stream_response:
|
||||
if agent_run and agent_run.cancelled:
|
||||
print(f"Cancelling openrouter inference for run {agent_run.id}")
|
||||
# TODO: Need to cancell?
|
||||
break
|
||||
|
||||
if event.choices and len(event.choices) > 0:
|
||||
delta = event.choices[0].delta
|
||||
if event.choices and len(event.choices) > 0:
|
||||
delta = event.choices[0].delta
|
||||
|
||||
if delta.content:
|
||||
emit('output', delta.content)
|
||||
if delta.reasoning:
|
||||
emit('reasoning', delta.reasoning)
|
||||
if delta.tool_calls:
|
||||
print("[openrouter] tool call:" + repr(delta.tool_calls))
|
||||
for tc in delta.tool_calls:
|
||||
if tc.index >= len(tool_calls):
|
||||
tool_calls.append(ToolCall(tool_name="", arguments="", id=""))
|
||||
tool_call = tool_calls[tc.index]
|
||||
if delta.content:
|
||||
emit('output', delta.content)
|
||||
if delta.reasoning:
|
||||
emit('reasoning', delta.reasoning)
|
||||
if delta.tool_calls:
|
||||
print("[openrouter] tool call:" + repr(delta.tool_calls))
|
||||
for tc in delta.tool_calls:
|
||||
if tc.index >= len(tool_calls):
|
||||
tool_calls.append(ToolCall(tool_name="", arguments="", id=""))
|
||||
tool_call = tool_calls[tc.index]
|
||||
|
||||
if tc.id:
|
||||
tool_call.id += tc.id
|
||||
if tc.id:
|
||||
tool_call.id += tc.id
|
||||
|
||||
if tc.function:
|
||||
if tc.function.name:
|
||||
tool_call.tool_name += tc.function.name
|
||||
if tc.function.arguments:
|
||||
tool_call.arguments += tc.function.arguments
|
||||
else:
|
||||
print("[openrouter] no event choices: " + repr(event))
|
||||
if tc.function:
|
||||
if tc.function.name:
|
||||
tool_call.tool_name += tc.function.name
|
||||
if tc.function.arguments:
|
||||
tool_call.arguments += tc.function.arguments
|
||||
else:
|
||||
print("[openrouter] no event choices: " + repr(event))
|
||||
|
||||
for tc in tool_calls:
|
||||
try:
|
||||
arguments = json.loads(tc.arguments)
|
||||
tc.arguments = arguments
|
||||
emit('tool-call', tc)
|
||||
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())
|
||||
for tc in tool_calls:
|
||||
try:
|
||||
arguments = json.loads(tc.arguments)
|
||||
tc.arguments = arguments
|
||||
emit('tool-call', tc)
|
||||
except Exception:
|
||||
print("[openrouter] Bad tool call arguments for " + repr(tc))
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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')),
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user