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.

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:
A developer imports the code of an application into the database.
The developer asks the system to generate a function for processing user authentication.
The Main Agent assigns the request to the Coder Agent to generate the function.
The Coder Agent produces the code.
The Coder Agent sends the request to the Reviewer Agent
The Reviewer Agent analyzes the code for possible bugs or syntax errors.
The Reviewer Agent finds a bug and sends it back to the Coder Agent.
The Coder Agent updates the code fixing the received issue.
The Coder Agent sends the request to the Reviewer Agent
The Reviewer Agent does not find any issue and replies OK.
The Coder Agent receives the OK and replies the new code to the Main Agent.
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()