Your first multi-agent#

An agent on its own can be smart, but things get really interesting when multiple agents team up.

BAF allows building multi-agent systems where each agent has a specific role, shares information, and works together to get the job done more efficiently.

In this section, we will walk through a running example to see how agents interact in action. The system we are looking at is designed to help developers write source code.

Note

This tutorial may be too advanced for BAF beginners. We recommend you understanding the core ideas of agents (Wiki section) before diving into this example.

This system is composed by 3 agents:

  • Main Agent: The agent that interacts with the developer, collecting his/her requests. It will store a piece of code and will ask for code modifications to a coder agent (in this example, we only considered adding new functions).

  • Coder Agent: This agent receives coding requests (sent by the main agent) and leverages a specialized LLM to generate the requested code. Before replying back the generated code, it is sent to a reviewer agent to check for possible errors or bugs. If something was found, the coder agent iterates again to fix the received issues.

  • Code Reviewer Agent: This agent receives a piece of code and is in charge of finding errors or bugs, leveraging a specialized LLM.

Example Multi-Agent system for code development

Multi-Agent System#

Note

To enable the communication between agents, they must know their WebSocket URLs, which they will use to send/receive messages.

Learn how to send messages between agents here: Communication between agents: Multi-agent systems

Example Workflow:

  1. A developer imports the code of an application into the database.

  2. The developer asks the system to generate a function for processing user authentication.

  3. The Main Agent assigns the request to the Coder Agent to generate the function.

  4. The Coder Agent produces the code.

  5. The Coder Agent sends the request to the Reviewer Agent

  6. The Reviewer Agent analyzes the code for possible bugs or syntax errors.

  7. The Reviewer Agent finds a bug and sends it back to the Coder Agent.

  8. The Coder Agent updates the code fixing the received issue.

  9. The Coder Agent sends the request to the Reviewer Agent

  10. The Reviewer Agent does not find any issue and replies OK.

  11. The Coder Agent receives the OK and replies the new code to the Main Agent.

  12. The Main Agent updates the code database and awaits for a new developer query.

The following sections show the code implementation for each agent.

Developer Main Agent#

  1# You may need to add your working directory to the Python path. To do so, uncomment the following lines of code
  2# import sys
  3# sys.path.append("/Path/to/directory/agentic-framework") # Replace with your directory path
  4import base64
  5import logging
  6
  7from besser.agent.core.agent import Agent
  8from besser.agent.library.transition.events.base_events import ReceiveTextEvent, ReceiveFileEvent
  9from besser.agent.core.file import File
 10from besser.agent.core.session import Session
 11from besser.agent.exceptions.logger import logger
 12
 13# Configure the logging module (optional)
 14logger.setLevel(logging.INFO)
 15
 16# Create the agent
 17agent = Agent('main_agent')
 18# Load agent properties stored in a dedicated file
 19agent.load_properties('../config.ini')
 20# Define the platform your agent will use
 21websocket_platform = agent.use_websocket_platform(use_ui=True)
 22
 23# STATES
 24
 25initial_state = agent.new_state('initial_state', initial=True)
 26receive_code_state = agent.new_state('receive_code_state')
 27awaiting_request_state = agent.new_state('awaiting_request_state')
 28send_request_state = agent.new_state('send_request_state')
 29final_state = agent.new_state('final_state')
 30
 31# INTENTS
 32
 33yes_intent = agent.new_intent('yes_intent', [
 34    'yes',
 35])
 36
 37no_intent = agent.new_intent('bad_intent', [
 38    'no',
 39])
 40
 41
 42# STATES BODIES' DEFINITION + TRANSITIONS
 43
 44def initial_body(session: Session):
 45    websocket_platform.reply(session, "Hello, upload your code before starting.")
 46
 47
 48def initial_fallback(session: Session):
 49    websocket_platform.reply(session, "Please, upload a file before starting.")
 50
 51
 52initial_state.set_body(initial_body)
 53initial_state.when_event(ReceiveFileEvent()).go_to(receive_code_state)
 54initial_state.set_fallback_body(initial_fallback)
 55
 56
 57def receive_code_body(session: Session):
 58    if session.event.file:
 59        # Receiving a code file on the first interaction
 60        file: File = session.event.file
 61        code = base64.b64decode(file.base64).decode('utf-8')
 62        if session.event.file.type == 'text/x-python':
 63            code = f"```python\n{code}\n```"
 64        session.set('code', code)
 65    elif session.get('new_code'):
 66        # Receiving an updated code after finishing the agent workflow
 67        session.set('code', session.get('new_code'))
 68    websocket_platform.reply(session, "Thanks, I stored your code in my database. This is how it looks like:")
 69    websocket_platform.reply(session, session.get('code'))
 70
 71
 72receive_code_state.set_body(receive_code_body)
 73receive_code_state.go_to(awaiting_request_state)
 74
 75
 76def awaiting_request_body(session: Session):
 77    session.delete('new_code')
 78    websocket_platform.reply(session, "How can I assist you?")
 79
 80
 81awaiting_request_state.set_body(awaiting_request_body)
 82awaiting_request_state.when_event(ReceiveTextEvent())\
 83                      .with_condition(lambda session: session.event.human) \
 84                      .go_to(send_request_state)
 85
 86
 87def send_request_body(session: Session):
 88    websocket_platform.reply(session, "Let's see what I can do...")
 89    session.send_message_to_websocket(
 90        url='ws://localhost:8011',
 91        message={
 92            "request": session.event.message,
 93            "code": session.get('code')
 94        }
 95    )
 96
 97
 98send_request_state.set_body(send_request_body)
 99send_request_state.when_event(ReceiveTextEvent()) \
100                  .with_condition(lambda session: not session.event.human) \
101                  .go_to(final_state)
102
103
104def final_body(session: Session):
105    new_code: str = session.event.message
106    session.set('new_code', new_code)
107    websocket_platform.reply(session, "Take a look at the new code:")
108    websocket_platform.reply(session, new_code)
109    websocket_platform.reply(session, "Do you want to merge the new code?")
110    websocket_platform.reply_options(session, ['Yes', 'No'])
111
112
113final_state.set_body(final_body)
114final_state.when_intent_matched(yes_intent).go_to(receive_code_state)
115final_state.when_intent_matched(no_intent).go_to(awaiting_request_state)
116
117# RUN APPLICATION
118
119if __name__ == '__main__':
120    agent.run()

Coder Agent#

  1# You may need to add your working directory to the Python path. To do so, uncomment the following lines of code
  2# import sys
  3# sys.path.append("/Path/to/directory/agentic-framework") # Replace with your directory path
  4import logging
  5
  6from besser.agent.core.agent import Agent
  7from besser.agent.library.transition.events.base_events import ReceiveJSONEvent
  8from besser.agent.core.session import Session
  9from besser.agent.exceptions.logger import logger
 10from besser.agent.nlp.llm.llm_openai_api import LLMOpenAI
 11from besser.agent.platforms.websocket import WEBSOCKET_PORT
 12
 13# Configure the logging module (optional)
 14logger.setLevel(logging.INFO)
 15
 16# Create the agent
 17agent = Agent('coder_agent')
 18# Load agent properties stored in a dedicated file
 19agent.load_properties('../config.ini')
 20agent.set_property(WEBSOCKET_PORT, 8011)
 21# Define the platform your agent will use
 22websocket_platform = agent.use_websocket_platform(use_ui=False)
 23
 24# Create the LLM
 25gpt = LLMOpenAI(
 26    agent=agent,
 27    name='gpt-4o-mini',
 28    parameters={},
 29    num_previous_messages=10
 30)
 31
 32# STATES
 33
 34initial_state = agent.new_state('initial_state', initial=True)
 35generate_code_state = agent.new_state('generate_code_state')
 36update_code_state = agent.new_state('update_code_state')
 37reply_code_state = agent.new_state('reply_code_state')
 38
 39# INTENTS
 40
 41ok_intent = agent.new_intent('yes_intent', [
 42    'ok',
 43])
 44
 45# STATES BODIES' DEFINITION + TRANSITIONS
 46
 47initial_state.when_event(ReceiveJSONEvent()) \
 48             .with_condition(lambda session: not session.event.human) \
 49             .go_to(generate_code_state)
 50
 51
 52def generate_code_body(session: Session):
 53    message = session.event.message
 54    new_code: str = gpt.predict(
 55        message=f"Given the following code:\n\n"
 56                f"{message['code']}\n\n"
 57                f"{message['request']}\n\n"
 58                f"Return only the code (full code with the additions)."
 59        )
 60    session.set('new_code', new_code)
 61    session.send_message_to_websocket(
 62        url='ws://localhost:8012',
 63        message=new_code
 64    )
 65
 66
 67generate_code_state.set_body(generate_code_body)
 68generate_code_state.when_intent_matched(ok_intent).go_to(reply_code_state)
 69# TODO : fix no_intent_matched
 70generate_code_state.when_no_intent_matched().got_to(update_code_state)
 71
 72
 73def update_code_body(session: Session):
 74    issues: str = session.event.message
 75    new_code: str = gpt.predict(
 76        message=f'Given the following code:\n\n'
 77                f'{session.get("new_code")}\n\n'
 78                f'Update it with the following requirements/fixing these issues (just reply with the new code):\n\n'
 79                f'{issues}'
 80    )
 81    session.set('new_code', new_code)
 82    session.send_message_to_websocket(
 83        url='ws://localhost:8012',
 84        message=new_code
 85
 86    )
 87
 88
 89update_code_state.set_body(update_code_body)
 90update_code_state.when_intent_matched(ok_intent).go_to(reply_code_state)
 91# TODO : fix no_intent_matched
 92update_code_state.when_no_intent_matched().go_to(update_code_state)
 93
 94
 95def reply_code_body(session: Session):
 96    websocket_platform.reply(session, session.get('new_code'))
 97
 98
 99reply_code_state.set_body(reply_code_body)
100reply_code_state.go_to(initial_state)
101
102
103# RUN APPLICATION
104
105if __name__ == '__main__':
106    agent.run()

Code Reviewer Agent#

 1# You may need to add your working directory to the Python path. To do so, uncomment the following lines of code
 2# import sys
 3# sys.path.append("/Path/to/directory/agentic-framework") # Replace with your directory path
 4import logging
 5
 6from besser.agent.core.agent import Agent
 7from besser.agent.library.transition.events.base_events import ReceiveTextEvent
 8from besser.agent.core.session import Session
 9from besser.agent.exceptions.logger import logger
10from besser.agent.nlp.llm.llm_openai_api import LLMOpenAI
11from besser.agent.platforms.websocket import WEBSOCKET_PORT
12
13# Configure the logging module (optional)
14logger.setLevel(logging.INFO)
15
16# Create the agent
17agent = Agent('reviewer_agent')
18# Load agent properties stored in a dedicated file
19agent.load_properties('../config.ini')
20agent.set_property(WEBSOCKET_PORT, 8012)
21# Define the platform your agent will use
22websocket_platform = agent.use_websocket_platform(use_ui=False)
23
24# Create the LLM
25gpt = LLMOpenAI(
26    agent=agent,
27    name='gpt-4o-mini',
28    parameters={},
29    num_previous_messages=10
30)
31
32# STATES
33
34initial_state = agent.new_state('initial_state', initial=True)
35code_review_state = agent.new_state('code_review_state')
36
37# INTENTS
38
39issues_intent = agent.new_intent('new_function_intent', [
40    'issues'
41])
42
43ok_intent = agent.new_intent('yes_intent', [
44    'ok',
45])
46
47
48# STATES BODIES' DEFINITION + TRANSITIONS
49
50initial_state.when_event(ReceiveTextEvent()) \
51             .with_condition(lambda session: not session.event.human) \
52             .go_to(code_review_state)
53
54
55def code_review_body(session: Session):
56    code: str = session.event.message
57    answer: str = gpt.predict(
58        message=f"You are a code reviewer. Given the following code, try to find if there are syntax errors.\n"
59                f"If you think there are no errors, just reply 'ok'.\n\n"
60                f"{code}"
61    )
62    websocket_platform.reply(session, answer)
63
64
65code_review_state.set_body(code_review_body)
66code_review_state.go_to(initial_state)
67
68
69# RUN APPLICATION
70
71if __name__ == '__main__':
72    agent.run()