Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MultiAgentWorkflow #17237

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open

MultiAgentWorkflow #17237

wants to merge 23 commits into from

Conversation

logan-markewich
Copy link
Collaborator

@logan-markewich logan-markewich commented Dec 10, 2024

A multi agent system implemented as a prebuilt workflow.

Currently, supports

  • state injection into latest user messages
  • accessing workflow context/state through the FunctionToolWithContext
  • human in the loop using the new ctx.wait_for_event()
  • both react and function calling llms
  • maintaining context between runs
  • direct (and controllable) handoffs between agents

Usage Example:
https://gist.github.com/logan-markewich/f91929a4d0c7e41515bed1d2851c566c

Current Diagram:
Screenshot 2024-12-29 at 12 42 12 PM

Todo:

  • dogfooding
  • more tests
  • improve docs?

@logan-markewich logan-markewich changed the title [wip] adding workflow tool MultiAgentWorkflow Jan 2, 2025
@logan-markewich logan-markewich marked this pull request as ready for review January 2, 2025 16:28
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jan 2, 2025
```python
workflow = MultiAgentWorkflow(
agents=[...],
initial_state={"counter": 0},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooc how is the state modified? by the user or by the agent?

are there constraints in 1) number of keys, and 2) the types of the values?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the state is thrown into the workflow context, so FunctionToolWithContext can access it and modify it. The constraints are the same as a normal workflow context imo -- if its not serializable, you might have issues in certain runtimes

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i see

return result.confirmation
```

When this function is called, it will block the workflow execution until the user sends the required confirmation event.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when is this function called?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By an agent, its meant to be an agent tool. I'll make this clearer.

You could also subclass or use this in your own workflows though

@@ -0,0 +1,257 @@
# Multi-Agent Workflows

The MultiAgentWorkflow uses Workflow Agents to allow you to create a system of multiple agents that can collaborate and hand off tasks to each other based on their specialized capabilities. This enables building more complex agent systems where different agents handle different aspects of a task.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we mention that this is built upon our core workflows classes?

the user will at least know that the syntax for running workflows and handling the output event stream is the same

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I should at least link to it somewhere, good point

Comment on lines +62 to +64
async for event in handler.stream_events():
if hasattr(event, "delta"):
print(event.delta, end="", flush=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these agents streaming dedicated events that can be shown in the UI (we're using this in create-llama).

There we're having the AgentRunEvent see
https://github.com/run-llama/create-llama/blob/main/templates/components/multiagent/python/app/workflows/events.py
(ignore to_response - this is for conversion to vercel data streams, this concern can be done outside of this PR)

here is an example using it to send the progress of tool calls:
https://github.com/run-llama/create-llama/blob/eec237c5feea1af9cdd5b276d34ebe3b8d0fd185/templates/components/multiagent/python/app/workflows/tools.py#L141

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! That is the main intention for these events, to show progress in some UI.

I didn't capture the concept of "in progress" or "completed" with this, its mostly all just events at points in time (here's the agent input, here's the agent stream, here's the agent output, heres a tool im about to call, here's the tool output) -- I could refactor, but not sure if its needed or not

Comment on lines +53 to +54
workflow = MultiAgentWorkflow(
agent_configs=[calculator_agent, retriever_agent]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about defining the entrypoint here?

Suggested change
workflow = MultiAgentWorkflow(
agent_configs=[calculator_agent, retriever_agent]
workflow = MultiAgentWorkflow(
entrypoint=[retriever_agent]
agent_configs=[calculator_agent, retriever_agent]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yea, that is actually a better UX (although I think there can only be one entry point 🤔)

Comment on lines +62 to +64
async for event in handler.stream_events():
if hasattr(event, "delta"):
print(event.delta, end="", flush=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could then also add a helper that is printing the events in a nice way for examples:

Suggested change
async for event in handler.stream_events():
if hasattr(event, "delta"):
print(event.delta, end="", flush=True)
async for event in handler.stream_events():
print_event(event)

Comment on lines +37 to +40
tools=[
FunctionTool.from_defaults(fn=add),
FunctionTool.from_defaults(fn=subtract),
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible in Python to wrap FunctionTool automatically, e.g.?

Suggested change
tools=[
FunctionTool.from_defaults(fn=add),
FunctionTool.from_defaults(fn=subtract),
],
tools=[
add, subtract,
],

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be possible, although now with FunctionTool and FunctionToolWithContext, I'll need to think a little harder about how to detect when each one is needed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:XXL This PR changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants