wip(backend/tool): tool collection and execution is now agents job
This commit is contained in:
1 parent
f3a8376df6
commit
2ba8096a3c
2 files changed
+100
-84
No files matched your search
@@ -183,6 +183,18 @@ class Agent:
|
||||
return [r for r in self.resources
|
||||
if r.kind == kind and (key is None or r.key == key)]
|
||||
|
||||
def get_tools(self, run=None):
|
||||
"""Collect the tools available to this agent.
|
||||
|
||||
Returns the agent-configured tools (derived from its resources) plus
|
||||
any remote tools referenced by the run input. When a run is given,
|
||||
remote names in ``run.input['remotes']`` are resolved and appended.
|
||||
"""
|
||||
tools = list(self.tools)
|
||||
if run is not None and 'remotes' in run.input:
|
||||
tools += self.app.getRemoteTools(self.user_id, run.input['remotes'])
|
||||
return tools
|
||||
|
||||
|
||||
class DebugAgent(Agent):
|
||||
identifier = "debug"
|
||||
|
||||
@@ -8,6 +8,77 @@ Expert AI with customizable system prompt.
|
||||
Will get guidelines, documents, and other advanced features in the future.
|
||||
""".strip()
|
||||
|
||||
# This needs to move to inference, just like build_messages
|
||||
async def chat_completion_loop(app, run, messages, model_id=None, tools=None):
|
||||
"""Drive ``inference.chat_completion`` until the model is finished.
|
||||
|
||||
Wires the run's output / reasoning / tool-call events, executes requested
|
||||
tools when ``tool_policy`` is 'auto', and feeds tool results back into
|
||||
``messages`` (mutated in place) so the model can continue. Returns once the
|
||||
model produces a final answer with no further tool calls.
|
||||
"""
|
||||
tools = list(tools or [])
|
||||
execute_tools = run.input.get('tool_policy', 'auto') == 'auto'
|
||||
|
||||
tool_calls = []
|
||||
output_stream = None
|
||||
reasoning_stream = None
|
||||
inference_end = asyncio.Event()
|
||||
|
||||
def on_output(stream):
|
||||
nonlocal output_stream
|
||||
output_stream = stream
|
||||
|
||||
def on_reasoning(stream):
|
||||
nonlocal reasoning_stream
|
||||
reasoning_stream = stream
|
||||
|
||||
run.on('output', on_output)
|
||||
run.on('reasoning', on_reasoning)
|
||||
run.on('tool-call', tool_calls.append)
|
||||
run.on('inference-end', lambda _: inference_end.set())
|
||||
|
||||
while True:
|
||||
# Reset per-iteration state
|
||||
output_stream = None
|
||||
reasoning_stream = None
|
||||
inference_end.clear()
|
||||
|
||||
await inference.chat_completion(
|
||||
app,
|
||||
model_id=model_id,
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
agent_run=run,
|
||||
)
|
||||
|
||||
await inference_end.wait()
|
||||
|
||||
content = output_stream and await output_stream.content_wait()
|
||||
reasoning = reasoning_stream and await reasoning_stream.content_wait()
|
||||
|
||||
if content or reasoning:
|
||||
msg = inference.LlamaMessage('assistant', content or "")
|
||||
if reasoning:
|
||||
msg.reasoning = reasoning
|
||||
messages.append(msg)
|
||||
|
||||
if execute_tools and tool_calls:
|
||||
for tc in tool_calls:
|
||||
tool = next((t for t in tools if t.name == tc.tool_name), None)
|
||||
if tool is None:
|
||||
print(f"[agent] unknown tool name: {tc.tool_name}")
|
||||
continue
|
||||
tc.result = await tool.execute_async(**tc.arguments)
|
||||
messages.append(inference.LlamaMessage('tool', tc))
|
||||
tool_calls.clear()
|
||||
# Buffer time to let the provider rest before the next call
|
||||
await asyncio.sleep(2)
|
||||
elif content or not reasoning:
|
||||
break
|
||||
|
||||
run.close()
|
||||
|
||||
class ExpertAgent(Agent):
|
||||
identifier = "expert"
|
||||
description = CLASS_DESCRIPTION
|
||||
@@ -26,36 +97,22 @@ class ExpertAgent(Agent):
|
||||
Agent.__init__(self, *args)
|
||||
|
||||
async def run(self, run):
|
||||
opts = {}
|
||||
# Apply agent configuration overrides to the run input
|
||||
for key in ("temperature", "repetition_penalty", "reasoning_effort"):
|
||||
if self.config.get(key) is not None:
|
||||
run.input[key] = self.config[key]
|
||||
|
||||
# Is this roon using *Agent's* tools, as opposed to run tools
|
||||
using_agent_tools = False
|
||||
tools = run.input.get('tools', [])
|
||||
# Provide the agent's default tools unless the run supplies its own.
|
||||
# Only agent-provided tools are executable within the loop.
|
||||
tools = run.input.get("tools", [])
|
||||
if tools:
|
||||
run.input['tool_policy'] = 'ignore'
|
||||
else:
|
||||
tools += self.get_tools(run)
|
||||
|
||||
if not tools:
|
||||
# Agent tools will fully replace run tools if none are present,
|
||||
# otherwise they won't be used
|
||||
if self.tools:
|
||||
tools = self.tools
|
||||
using_agent_tools = True
|
||||
print("Final tool object: " + repr(run.input.get('tools', [])))
|
||||
|
||||
# Inject remotes as tools
|
||||
if 'remotes' in run.input:
|
||||
tools += self.app.getRemoteTools(self.user_id, run.input['remotes'])
|
||||
using_agent_tools = True
|
||||
|
||||
print("Final tool object: " + repr(tools))
|
||||
if tools: run.input['tools'] = tools
|
||||
|
||||
if self.config.get("temperature") is not None:
|
||||
run.input["temperature"] = self.config["temperature"]
|
||||
if self.config.get("repetition_penalty") is not None:
|
||||
run.input["repetition_penalty"] = self.config["repetition_penalty"]
|
||||
if self.config.get("reasoning_effort") is not None:
|
||||
run.input["reasoning_effort"] = self.config["reasoning_effort"]
|
||||
|
||||
|
||||
# Build message list based on input
|
||||
# Build the initial message list
|
||||
messages = inference.build_messages(
|
||||
system_prompt=self.config.get("system_prompt", ""),
|
||||
instruction=run.input.get("instruction"),
|
||||
@@ -63,63 +120,10 @@ class ExpertAgent(Agent):
|
||||
messages=run.input.get("messages"),
|
||||
)
|
||||
|
||||
# Collect inference output. These are resused across calls
|
||||
tool_calls = []
|
||||
output_stream = None
|
||||
reasoning_stream = None
|
||||
inference_end = asyncio.Event()
|
||||
def on_output(stream):
|
||||
nonlocal output_stream
|
||||
output_stream = stream
|
||||
def on_reasoning(stream):
|
||||
nonlocal reasoning_stream
|
||||
reasoning_stream = stream
|
||||
run.on('output', on_output)
|
||||
run.on('reasoning', on_reasoning)
|
||||
run.on('inference-end', lambda event: inference_end.set())
|
||||
run.on('tool-call', lambda tc: print(f"[expert] tool call: {tc}") or tool_calls.append(tc))
|
||||
|
||||
while True:
|
||||
# Reset data
|
||||
inference_end.clear()
|
||||
output_stream = None
|
||||
|
||||
# Call inference, passing the run to get events out
|
||||
await inference.chat_completion(
|
||||
await chat_completion_loop(
|
||||
self.app,
|
||||
run,
|
||||
messages,
|
||||
model_id=self.config.get("model"),
|
||||
messages=messages,
|
||||
tools=run.input.get("tools", []),
|
||||
opts=opts,
|
||||
agent_run=run,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
await inference_end.wait()
|
||||
|
||||
content = output_stream and await output_stream.content_wait();
|
||||
reasoning = reasoning_stream and await reasoning_stream.content_wait();
|
||||
|
||||
if content or reasoning:
|
||||
msg = inference.LlamaMessage('assistant', content or "")
|
||||
if reasoning: msg.reasoning = reasoning
|
||||
messages.append(msg)
|
||||
print(f"[expert] collected tool calls: {len(tool_calls)}, using? {using_agent_tools}")
|
||||
|
||||
if using_agent_tools and tool_calls:
|
||||
print("Will execute tool calls")
|
||||
for tc in tool_calls:
|
||||
try:
|
||||
tool = next(t for t in tools if t.name == tc.tool_name)
|
||||
if not tool: raise StopIteration()
|
||||
tc.result = await tool.execute_async(**tc.arguments)
|
||||
messages.append(inference.LlamaMessage('tool', tc))
|
||||
except StopIteration:
|
||||
print(f"Bad agent tool name: {tc.name}")
|
||||
tool_calls = []
|
||||
# Buffer time to let openrouter rest?
|
||||
await asyncio.sleep(2)
|
||||
elif content or not reasoning:
|
||||
print("No tool calls to execute, finish")
|
||||
break
|
||||
|
||||
run.close()
|
||||
Reference in new issue
Block a user