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