Skip to content

Instantly share code, notes, and snippets.

@ruvnet
Last active January 31, 2025 23:08
Show Gist options
  • Save ruvnet/5cdbbd43ab3a0c728fdd3e7a2a8aedd9 to your computer and use it in GitHub Desktop.
Save ruvnet/5cdbbd43ab3a0c728fdd3e7a2a8aedd9 to your computer and use it in GitHub Desktop.
5cdbbd43ab3a0c728fdd3e7a2a8aedd9

Multi-Agent Concierge System

Introduction: Multi-Agent Conversational System

Notebook created By rUv, cause he could.

Based on the the multi-agent concept created by Llama Index.

This notebook presents an advanced, modular multi-agent conversational system designed to navigate complex task trees within a financial system. The system leverages multiple specialized agents, each responsible for handling distinct tasks such as stock lookup, user authentication, account balance inquiries, money transfers, and overall orchestration of the conversation flow. The architecture is built to be highly customizable, allowing for the seamless integration of new agents and functionalities as needed.

Core Components:

  1. Stock Lookup Agent:

    • Handles requests for stock price information.
    • Can assist users in searching for stock symbols based on company names and retrieving current trading prices.
  2. Authentication Agent:

    • Manages user login processes by validating credentials and storing session tokens.
    • Ensures that sensitive operations, such as balance checks or money transfers, are only accessible to authenticated users.
  3. Account Balance Agent:

    • Provides users with information on their account balances.
    • Requires authentication to ensure security and privacy.
  4. Money Transfer Agent:

    • Facilitates secure transfers of funds between user accounts.
    • Verifies sufficient balance and user authentication before proceeding with transactions.
  5. Orchestration Agent:

    • Acts as the central decision-making unit.
    • Determines which agent should handle the next step in the conversation based on the current user state and input.

Advanced Usage and Customization:

The system is designed to be highly flexible, allowing for extensive customization and expansion:

  • Adding New Agents:

    • The architecture supports the easy addition of new agents. Developers can define new agent behaviors, tools, and system prompts, then integrate them into the orchestration logic.
    • Example: You might add an agent for handling investment portfolio analysis, with tools for calculating portfolio performance, assessing risk, and providing recommendations.
  • Customization of Agent Behaviors:

    • Each agent's behavior can be customized through the use of configurable parameters. For instance, you can adjust the stock lookup agent to fetch real-time data from different financial APIs, or modify the authentication agent to use multi-factor authentication.
    • Configuration UI: Google Colab’s inline UI elements can be used to create interactive forms that allow users to customize agent parameters dynamically before running the notebook.
  • Interactive Configuration:

    • Users can define and modify agent labels, task descriptions, tool parameters, and even the system prompt directly from the Colab interface, without altering the core codebase.
    • This feature is particularly useful for experimenting with different configurations or adapting the system to different financial environments.
  • Error Handling and Graceful Exit:

    • The system is built with robust error handling to manage unexpected inputs and user interruptions.
    • For example, the main loop includes a mechanism to gracefully handle user-initiated exits (e.g., via Ctrl+C), ensuring that the notebook exits cleanly without displaying unnecessary error messages.

Example Workflow:

  • User Interaction: The user initiates the conversation by selecting an agent or inputting a query. The orchestration agent interprets the input and routes the request to the appropriate agent.
  • Task Execution: The selected agent processes the request, whether it’s looking up a stock price, checking an account balance, or transferring funds.
  • State Management: The system maintains the current state of the user, including session details and task progress, ensuring that each step of the conversation is informed by the user’s previous actions.
  • Dynamic Decision-Making: Based on the outcomes of each interaction, the orchestration agent decides the next step, keeping the conversation focused and efficient.

Summary

This notebook is a powerful tool for building and managing a multi-agent conversational system tailored to complex financial tasks. With its modular design, customizable features, and robust handling of interactions, it offers a flexible framework for creating sophisticated AI-driven financial assistants. Whether you’re managing simple inquiries or navigating intricate financial workflows, this system provides the tools and structure needed to develop a responsive and intelligent conversational experience.

Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/ruvnet/5cdbbd43ab3a0c728fdd3e7a2a8aedd9/notebook.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "HVBHRO3FStSZ"
},
"source": [
"### Introduction: Multi-Agent Conversational System\n",
"#### [Created By rUv, cause he could.](https://github.com/ruvnet)\n",
"\n",
"This notebook presents an advanced, modular multi-agent conversational system designed to navigate complex task trees within a financial system. The system leverages multiple specialized agents, each responsible for handling distinct tasks such as stock lookup, user authentication, account balance inquiries, money transfers, and overall orchestration of the conversation flow. The architecture is built to be highly customizable, allowing for the seamless integration of new agents and functionalities as needed.\n",
"\n",
"#### Core Components:\n",
"\n",
"1. **Stock Lookup Agent**:\n",
" - Handles requests for stock price information.\n",
" - Can assist users in searching for stock symbols based on company names and retrieving current trading prices.\n",
"\n",
"2. **Authentication Agent**:\n",
" - Manages user login processes by validating credentials and storing session tokens.\n",
" - Ensures that sensitive operations, such as balance checks or money transfers, are only accessible to authenticated users.\n",
"\n",
"3. **Account Balance Agent**:\n",
" - Provides users with information on their account balances.\n",
" - Requires authentication to ensure security and privacy.\n",
"\n",
"4. **Money Transfer Agent**:\n",
" - Facilitates secure transfers of funds between user accounts.\n",
" - Verifies sufficient balance and user authentication before proceeding with transactions.\n",
"\n",
"5. **Orchestration Agent**:\n",
" - Acts as the central decision-making unit.\n",
" - Determines which agent should handle the next step in the conversation based on the current user state and input.\n",
"\n",
"#### Example Workflow:\n",
"\n",
"- **User Interaction**: The user initiates the conversation by selecting an agent or inputting a query. The orchestration agent interprets the input and routes the request to the appropriate agent.\n",
"- **Task Execution**: The selected agent processes the request, whether it’s looking up a stock price, checking an account balance, or transferring funds.\n",
"- **State Management**: The system maintains the current state of the user, including session details and task progress, ensuring that each step of the conversation is informed by the user’s previous actions.\n",
"- **Dynamic Decision-Making**: Based on the outcomes of each interaction, the orchestration agent decides the next step, keeping the conversation focused and efficient.\n",
"\n",
"### Summary\n",
"\n",
"This notebook is a powerful tool for building and managing a multi-agent conversational system tailored to complex financial tasks. With its modular design, customizable features, and robust handling of interactions, it offers a flexible framework for creating sophisticated AI-driven financial assistants. Whether you’re managing simple inquiries or navigating intricate financial workflows, this system provides the tools and structure needed to develop a responsive and intelligent conversational experience."
],
"id": "HVBHRO3FStSZ"
},
{
"cell_type": "markdown",
"source": [
"## Install requirements and configure OpenAi API Key"
],
"metadata": {
"id": "CpHuHbl0co9r"
},
"id": "CpHuHbl0co9r"
},
{
"cell_type": "code",
"source": [
"# Install the OpenAI library (uncomment if needed)\n",
"!pip install python-dotenv colorama llama-index\n",
"\n",
"# Import necessary libraries\n",
"import openai\n",
"from google.colab import userdata\n",
"\n",
"# Retrieve and set the API key\n",
"api_key = userdata.get('OPENAI_API_KEY')\n",
"openai.api_key = api_key\n",
"\n",
"# Verify the API key is set (this is just for demonstration and should not be used in production code)\n",
"if openai.api_key:\n",
" print(\"OpenAI API key is set. Ready to proceed!\")\n",
"else:\n",
" print(\"OpenAI API key is not set. Please check your setup.\")"
],
"metadata": {
"id": "NSxpj5ZDW6fj"
},
"id": "NSxpj5ZDW6fj",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Check everything is installed"
],
"metadata": {
"id": "V4O_wzrtcxEp"
},
"id": "V4O_wzrtcxEp"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "tQrbLqDBStSc",
"outputId": "009029e1-49c9-4acf-fe54-a07755c829f4"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"LlamaIndex import successful!\n"
]
}
],
"source": [
"from dotenv import load_dotenv\n",
"load_dotenv()\n",
"from enum import Enum\n",
"from typing import List\n",
"import pprint\n",
"from colorama import Fore, Back, Style\n",
"from llama_index.core.memory import ChatMemoryBuffer\n",
"from llama_index.core.tools import FunctionTool\n",
"from llama_index.llms.openai import OpenAI\n",
"from llama_index.agent.openai import OpenAIAgent\n",
"\n",
"class Speaker(str, Enum):\n",
" STOCK_LOOKUP = \"stock_lookup\"\n",
" AUTHENTICATE = \"authenticate\"\n",
" ACCOUNT_BALANCE = \"account_balance\"\n",
" TRANSFER_MONEY = \"transfer_money\"\n",
" CONCIERGE = \"concierge\"\n",
" ORCHESTRATOR = \"orchestrator\"\n",
"\n",
"print(\"LlamaIndex import successful!\") # Added a success message"
],
"id": "tQrbLqDBStSc"
},
{
"cell_type": "markdown",
"source": [
"## Multi Agent Configuration"
],
"metadata": {
"id": "PJcF4k-wc6to"
},
"id": "PJcF4k-wc6to"
},
{
"cell_type": "markdown",
"source": [
"A multi-agent conversational system for navigating a complex task tree.\n",
"In this demo, the user is navigating a financial system.\n",
"\n",
"**Agent 1:** look up a stock price.\n",
"Does not require authentication, has specialized tools for looking up stock prices.\n",
"\n",
"**Agent 2:** authenticate the user.\n",
"A required step before the user can interact with some of the other agents.\n",
"\n",
"**Agent 3:** look up an account balance.\n",
"Requires the user to be authenticated first.\n",
"\n",
"**Agent 4:** transfer money between accounts.\n",
"Requires the user to be authenticated first, and have looked up their balance already.\n",
"\n",
"**Concierge agent:** a catch-all agent that helps navigate between the other 4.\n",
"\n",
"**Orchestration agent:** decides which agent to run based on the current state of the user."
],
"metadata": {
"id": "sIggMuzzaqfJ"
},
"id": "sIggMuzzaqfJ"
},
{
"cell_type": "markdown",
"source": [
"### Stock lookup agent\n",
"This section defines the Stock Lookup Agent, responsible for assisting users in finding stock prices. The agent can search for a stock symbol based on a company name and retrieve the current trading price of the stock. It includes tools for looking up stock prices, searching for stock symbols, and signaling the completion of the task. The agent is designed to guide users through the stock lookup process, ensuring accuracy by relying on predefined tools for symbol searches and enforcing the completion of tasks before transitioning to other agents."
],
"metadata": {
"id": "c1ajhAZ_dLKh"
},
"id": "c1ajhAZ_dLKh"
},
{
"cell_type": "code",
"source": [
"# Stock lookup agent\n",
"def stock_lookup_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def lookup_stock_price(stock_symbol: str) -> str:\n",
" \"\"\"Useful for looking up a stock price.\"\"\"\n",
" print(f\"Looking up stock price for {stock_symbol}\")\n",
" return f\"Symbol {stock_symbol} is currently trading at $100.00\"\n",
"\n",
" def search_for_stock_symbol(str: str) -> str:\n",
" \"\"\"Useful for searching for a stock symbol given a free-form company name.\"\"\"\n",
" print(\"Searching for stock symbol\")\n",
" return str.upper()\n",
"\n",
" def done() -> None:\n",
" \"\"\"When you have returned a stock price, call this tool.\"\"\"\n",
" print(\"Stock lookup is complete\")\n",
" state[\"current_speaker\"] = None\n",
" state[\"just_finished\"] = True\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=lookup_stock_price),\n",
" FunctionTool.from_defaults(fn=search_for_stock_symbol),\n",
" FunctionTool.from_defaults(fn=done),\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" You are a helpful assistant that is looking up stock prices.\n",
" The user may not know the stock symbol of the company they're interested in,\n",
" so you can help them look it up by the name of the company.\n",
" You can only look up stock symbols given to you by the search_for_stock_symbol tool, don't make them up. Trust the output of the search_for_stock_symbol tool even if it doesn't make sense to you.\n",
" The current user state is:\n",
" {pprint.pformat(state, indent=4)}\n",
" Once you have supplied a stock price, you must call the tool \"done\" to signal that you are done.\n",
" If the user asks to do anything other than look up a stock symbol or price, call the tool \"done\" to signal some other agent should help.\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\"),\n",
" system_prompt=system_prompt,\n",
" )\n"
],
"metadata": {
"id": "U2Mxv9bjawJj"
},
"id": "U2Mxv9bjawJj",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Auth Agent\n",
"This section defines the Authentication Agent, responsible for securely logging in users by storing their username and validating their password to generate a session token. The agent includes tools to record the username, handle the login process, check authentication status, and signal the completion of the authentication task. It guides users through the login process and ensures that only authenticated users can proceed with subsequent actions. Upon successful authentication, the agent concludes its task and hands off control to other agents if needed."
],
"metadata": {
"id": "Jjh2Z9-sdQI_"
},
"id": "Jjh2Z9-sdQI_"
},
{
"cell_type": "code",
"source": [
"# Auth Agent\n",
"def auth_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def store_username(username: str) -> None:\n",
" \"\"\"Adds the username to the user state.\"\"\"\n",
" print(\"Recording username\")\n",
" state[\"username\"] = username\n",
"\n",
" def login(password: str) -> None:\n",
" \"\"\"Given a password, logs in and stores a session token in the user state.\"\"\"\n",
" print(f\"Logging in {state['username']}\")\n",
" # todo: actually check the password\n",
" session_token = \"output_of_login_function_goes_here\"\n",
" state[\"session_token\"] = session_token\n",
"\n",
" def is_authenticated() -> bool:\n",
" \"\"\"Checks if the user has a session token.\"\"\"\n",
" print(\"Checking if authenticated\")\n",
" if state[\"session_token\"] is not None:\n",
" return True\n",
"\n",
" def done() -> None:\n",
" \"\"\"When you complete your task, call this tool.\"\"\"\n",
" print(\"Authentication is complete\")\n",
" state[\"current_speaker\"] = None\n",
" state[\"just_finished\"] = True\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=store_username),\n",
" FunctionTool.from_defaults(fn=login),\n",
" FunctionTool.from_defaults(fn=is_authenticated),\n",
" FunctionTool.from_defaults(fn=done),\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" You are a helpful assistant that is authenticating a user.\n",
" Your task is to get a valid session token stored in the user state.\n",
" To do this, the user must supply you with a username and a valid password. You can ask them to supply these.\n",
" If the user supplies a username and password, call the tool \"login\" to log them in.\n",
" The current user state is:\n",
" {pprint.pformat(state, indent=4)}\n",
" When you have authenticated, call the tool \"done\" to signal that you are done.\n",
" If the user asks to do anything other than authenticate, call the tool \"done\" to signal some other agent should help.\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\"),\n",
" system_prompt=system_prompt,\n",
" )\n"
],
"metadata": {
"id": "pYxSwuH3azer"
},
"id": "pYxSwuH3azer",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Account balance agent\n",
"This section defines the Account Balance Agent, responsible for retrieving and providing account balances to authenticated users. The agent can assist users in looking up their account ID and then checking the balance of that account. It includes tools for verifying user authentication, retrieving account details, and completing the balance inquiry task. The agent is designed to ensure that users are authenticated before accessing balance information and to guide them through the process of checking their account balance. Once the task is completed, the agent hands off control to other agents if further assistance is required."
],
"metadata": {
"id": "4h7ij6JvdSOf"
},
"id": "4h7ij6JvdSOf"
},
{
"cell_type": "code",
"source": [
"# Account balance agent\n",
"def account_balance_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def get_account_id(account_name: str) -> str:\n",
" \"\"\"Useful for looking up an account ID.\"\"\"\n",
" print(f\"Looking up account ID for {account_name}\")\n",
" account_id = \"1234567890\"\n",
" state[\"account_id\"] = account_id\n",
" return f\"Account id is {account_id}\"\n",
"\n",
" def get_account_balance(account_id: str) -> str:\n",
" \"\"\"Useful for looking up an account balance.\"\"\"\n",
" print(f\"Looking up account balance for {account_id}\")\n",
" state[\"account_balance\"] = 1000\n",
" return f\"Account {account_id} has a balance of ${state['account_balance']}\"\n",
"\n",
" def is_authenticated() -> bool:\n",
" \"\"\"Checks if the user has a session token.\"\"\"\n",
" print(\"Account balance agent is checking if authenticated\")\n",
" if state[\"session_token\"] is not None:\n",
" return True\n",
"\n",
" def done() -> None:\n",
" \"\"\"When you complete your task, call this tool.\"\"\"\n",
" print(\"Account balance lookup is complete\")\n",
" state[\"current_speaker\"] = None\n",
" state[\"just_finished\"] = True\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=get_account_id),\n",
" FunctionTool.from_defaults(fn=get_account_balance),\n",
" FunctionTool.from_defaults(fn=is_authenticated),\n",
" FunctionTool.from_defaults(fn=done),\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" You are a helpful assistant that is looking up account balances.\n",
" The user may not know the account ID of the account they're interested in,\n",
" so you can help them look it up by the name of the account.\n",
" The user can only do this if they are authenticated, which you can check with the is_authenticated tool.\n",
" If they aren't authenticated, tell them to authenticate\n",
" If they're trying to transfer money, they have to check their account balance first, which you can help with.\n",
" The current user state is:\n",
" {pprint.pformat(state, indent=4)}\n",
" Once you have supplied an account balance, you must call the tool \"done\" to signal that you are done.\n",
" If the user asks to do anything other than look up an account balance, call the tool \"done\" to signal some other agent should help.\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\"),\n",
" system_prompt=system_prompt,\n",
" )\n"
],
"metadata": {
"id": "U6BG3zK9a0hh"
},
"id": "U6BG3zK9a0hh",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Money Transfer Agent\n",
"This section defines the Money Transfer Agent, which is responsible for facilitating secure money transfers between user accounts. The agent ensures that the user is authenticated and that sufficient funds are available in the source account before proceeding with the transfer. It includes tools to verify account balance, confirm user authentication, and execute the transfer. The agent guides users through the money transfer process, ensuring all necessary conditions are met before completing the transaction. Once the transfer is successful, the agent signals completion, allowing other agents to take over if further assistance is needed."
],
"metadata": {
"id": "JGiwoRgJdiCP"
},
"id": "JGiwoRgJdiCP"
},
{
"cell_type": "code",
"source": [
"# Money Transfer Agent\n",
"\n",
"def transfer_money_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def transfer_money(from_account_id: str, to_account_id: str, amount: int) -> None:\n",
" \"\"\"Useful for transferring money between accounts.\"\"\"\n",
" print(f\"Transferring {amount} from {from_account_id} account {to_account_id}\")\n",
" return f\"Transferred {amount} to account {to_account_id}\"\n",
"\n",
" def balance_sufficient(account_id: str, amount: int) -> bool:\n",
" \"\"\"Useful for checking if an account has enough money to transfer.\"\"\"\n",
" # todo: actually check they've selected the right account ID\n",
" print(\"Checking if balance is sufficient\")\n",
" if state['account_balance'] >= amount:\n",
" return True\n",
"\n",
" def has_balance() -> bool:\n",
" \"\"\"Useful for checking if an account has a balance.\"\"\"\n",
" print(\"Checking if account has a balance\")\n",
" if state[\"account_balance\"] is not None:\n",
" return True\n",
"\n",
" def is_authenticated() -> bool:\n",
" \"\"\"Checks if the user has a session token.\"\"\"\n",
" print(\"Transfer money agent is checking if authenticated\")\n",
" if state[\"session_token\"] is not None:\n",
" return True\n",
"\n",
" def done() -> None:\n",
" \"\"\"When you complete your task, call this tool.\"\"\"\n",
" print(\"Money transfer is complete\")\n",
" state[\"current_speaker\"] = None\n",
" state[\"just_finished\"] = True\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=transfer_money),\n",
" FunctionTool.from_defaults(fn=balance_sufficient),\n",
" FunctionTool.from_defaults(fn=has_balance),\n",
" FunctionTool.from_defaults(fn=is_authenticated),\n",
" FunctionTool.from_defaults(fn=done),\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" You are a helpful assistant that transfers money between accounts.\n",
" The user can only do this if they are authenticated, which you can check with the is_authenticated tool.\n",
" If they aren't authenticated, tell them to authenticate first.\n",
" The user must also have looked up their account balance already, which you can check with the has_balance tool.\n",
" If they haven't already, tell them to look up their account balance first.\n",
" The current user state is:\n",
" {pprint.pformat(state, indent=4)}\n",
" Once you have transferred the money, you can call the tool \"done\" to signal that you are done.\n",
" If the user asks to do anything other than transfer money, call the tool \"done\" to signal some other agent should help.\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\"),\n",
" system_prompt=system_prompt,\n",
" )"
],
"metadata": {
"id": "JcqGLyVDa8Ce"
},
"id": "JcqGLyVDa8Ce",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Concierge agent\n",
"This section defines the Concierge Agent, which acts as a guide to help users navigate the financial system. The agent's primary role is to understand the user's needs by asking relevant questions and directing them to the appropriate actions. It can guide users through tasks such as looking up stock prices, authenticating their identity, checking account balances, and transferring money between accounts. The Concierge Agent uses a placeholder tool that performs no action but serves as a framework for interaction, ensuring that users are directed to the correct agent or task based on their requests and the current state of their session."
],
"metadata": {
"id": "DJE6PfpydmtX"
},
"id": "DJE6PfpydmtX"
},
{
"cell_type": "code",
"source": [
"# Concierge agent\n",
"def concierge_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def dummy_tool() -> bool:\n",
" \"\"\"A tool that does nothing.\"\"\"\n",
" print(\"Doing nothing.\")\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=dummy_tool)\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" You are a helpful assistant that is helping a user navigate a financial system.\n",
" Your job is to ask the user questions to figure out what they want to do, and give them the available things they can do.\n",
" That includes\n",
" * looking up a stock price\n",
" * authenticating the user\n",
" * checking an account balance (requires authentication first)\n",
" * transferring money between accounts (requires authentication and checking an account balance first)\n",
"\n",
" The current state of the user is:\n",
" {pprint.pformat(state, indent=4)}\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\"),\n",
" system_prompt=system_prompt,\n",
" )"
],
"metadata": {
"id": "9yf_1c05a-IF"
},
"id": "9yf_1c05a-IF",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Continuation agent\n",
"This section defines the Continuation Agent, which is designed to assess the current state of the user and determine the next steps in their interaction with the financial system. The agent's role is to evaluate the user's progress and decide if further actions are needed, or if the task is complete. Although it includes a placeholder tool that performs no action, the Continuation Agent is crucial for maintaining the flow of the conversation, ensuring that the user’s journey continues smoothly based on their prior interactions and the current state. This agent operates with a lower temperature setting to prioritize consistent and logical decision-making."
],
"metadata": {
"id": "h1CES2tWdp9o"
},
"id": "h1CES2tWdp9o"
},
{
"cell_type": "code",
"source": [
"# Continuation agent\n",
"def continuation_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def dummy_tool() -> bool:\n",
" \"\"\"A tool that does nothing.\"\"\"\n",
" print(\"Doing nothing.\")\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=dummy_tool)\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" The current state of the user is:\n",
" {pprint.pformat(state, indent=4)}\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\",temperature=0.4),\n",
" system_prompt=system_prompt,\n",
" )\n"
],
"metadata": {
"id": "eAT4BaPabAAJ"
},
"id": "eAT4BaPabAAJ",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"###Orchestration agent\n",
"This section defines the Orchestration Agent, which is responsible for determining the next action in a user's interaction with the financial system by selecting the appropriate agent to handle the user's request. The Orchestration Agent evaluates the current state and chat history, using tools to check authentication status and account balance availability. Based on these factors, it returns the name of the next agent to run, ensuring a logical progression through tasks such as stock lookup, authentication, account balance inquiry, or money transfer. The agent operates strictly within predefined rules, providing only the necessary information to guide the flow without adding conversational elements, ensuring a seamless and focused user experience."
],
"metadata": {
"id": "pq8pk6sodrh7"
},
"id": "pq8pk6sodrh7"
},
{
"cell_type": "code",
"source": [
"\n",
"# Orchestration agent\n",
"def orchestration_agent_factory(state: dict) -> OpenAIAgent:\n",
"\n",
" def has_balance() -> bool:\n",
" \"\"\"Useful for checking if an account has a balance.\"\"\"\n",
" print(\"Orchestrator checking if account has a balance\")\n",
" return (state[\"account_balance\"] is not None)\n",
"\n",
" def is_authenticated() -> bool:\n",
" \"\"\"Checks if the user has a session token.\"\"\"\n",
" print(\"Orchestrator is checking if authenticated\")\n",
" return (state[\"session_token\"] is not None)\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=has_balance),\n",
" FunctionTool.from_defaults(fn=is_authenticated),\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" You are on orchestration agent.\n",
" Your job is to decide which agent to run based on the current state of the user and what they've asked to do. Agents are identified by short strings.\n",
" What you do is return the name of the agent to run next. You do not do anything else.\n",
"\n",
" The current state of the user is:\n",
" {pprint.pformat(state, indent=4)}\n",
"\n",
" If a current_speaker is already selected in the state, simply output that value.\n",
"\n",
" If there is no current_speaker value, look at the chat history and the current state and you MUST return one of these strings identifying an agent to run:\n",
" * \"{Speaker.STOCK_LOOKUP.value}\" - if they user wants to look up a stock price (does not require authentication)\n",
" * \"{Speaker.AUTHENTICATE.value}\" - if the user needs to authenticate\n",
" * \"{Speaker.ACCOUNT_BALANCE.value}\" - if the user wants to look up an account balance\n",
" * If they want to look up an account balance, but they haven't authenticated yet, return \"{Speaker.AUTHENTICATE.value}\" instead\n",
" * \"{Speaker.TRANSFER_MONEY.value}\" - if the user wants to transfer money between accounts (requires authentication and checking an account balance first)\n",
" * If they want to transfer money, but is_authenticated returns false, return \"{Speaker.AUTHENTICATE.value}\" instead\n",
" * If they want to transfer money, but has_balance returns false, return \"{Speaker.ACCOUNT_BALANCE.value}\" instead\n",
" * \"{Speaker.CONCIERGE.value}\" - if the user wants to do something else, or hasn't said what they want to do, or you can't figure out what they want to do. Choose this by default.\n",
"\n",
" Output one of these strings and ONLY these strings, without quotes.\n",
" NEVER respond with anything other than one of the above five strings. DO NOT be helpful or conversational.\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=\"gpt-4o-mini\",temperature=0.4),\n",
" system_prompt=system_prompt,\n",
" )\n"
],
"metadata": {
"id": "zGc3xA7fbCug"
},
"id": "zGc3xA7fbCug",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"### Agent Initialization and Main Loop\n",
"This section outlines the initialization and execution loop of the multi-agent conversational system. The get_initial_state function initializes the user state, setting up key variables such as username, session_token, and account_balance. The run function drives the main interaction loop, guiding the user through various tasks by determining which agent should handle the current request.\n",
"\n",
"It manages the conversation flow, handles user input, and leverages different agents like the Orchestration Agent to decide the next steps. The loop also maintains and updates the conversation history, ensuring that each agent has the context needed to perform its role effectively. This framework allows for dynamic and context-aware interactions within a financial system, ensuring that users receive the appropriate assistance based on their current needs and state."
],
"metadata": {
"id": "0i-nIisZd2uN"
},
"id": "0i-nIisZd2uN"
},
{
"cell_type": "code",
"source": [
"# @title Main Loop\n",
"def get_initial_state() -> dict:\n",
" return {\n",
" \"username\": None,\n",
" \"session_token\": None,\n",
" \"account_id\": None,\n",
" \"account_balance\": None,\n",
" \"current_speaker\": None,\n",
" \"just_finished\": False,\n",
" }\n",
"\n",
"def run() -> None:\n",
" state = get_initial_state()\n",
"\n",
" root_memory = ChatMemoryBuffer.from_defaults(token_limit=8000)\n",
"\n",
" first_run = True\n",
" is_retry = False\n",
"\n",
" while True:\n",
" if first_run:\n",
" # if this is the first run, start the conversation\n",
" user_msg_str = \"Hello\"\n",
" first_run = False\n",
" elif is_retry == True:\n",
" user_msg_str = \"That's not right, try again. Pick one agent.\"\n",
" is_retry = False\n",
" elif state[\"just_finished\"] == True:\n",
" print(\"Asking the continuation agent to decide what to do next\")\n",
" user_msg_str = str(continuation_agent_factory(state).chat(\"\"\"\n",
" Look at the chat history to date and figure out what the user was originally trying to do.\n",
" They might have had to do some sub-tasks to complete that task, but what we want is the original thing they started out trying to do.\n",
" Formulate a sentence as if written by the user that asks to continue that task.\n",
" If it seems like the user really completed their task, output \"no_further_task\" only.\n",
" \"\"\", chat_history=current_history))\n",
" print(f\"Continuation agent said {user_msg_str}\")\n",
" if user_msg_str == \"no_further_task\":\n",
" user_msg_str = input(\">> \").strip()\n",
" state[\"just_finished\"] = False\n",
" else:\n",
" # any other time, get user input\n",
" user_msg_str = input(\"> \").strip()\n",
"\n",
" current_history = root_memory.get()\n",
"\n",
" # who should speak next?\n",
" if (state[\"current_speaker\"]):\n",
" print(f\"There's already a speaker: {state['current_speaker']}\")\n",
" next_speaker = state[\"current_speaker\"]\n",
" else:\n",
" print(\"No current speaker, asking orchestration agent to decide\")\n",
" orchestration_response = orchestration_agent_factory(state).chat(user_msg_str, chat_history=current_history)\n",
" next_speaker = str(orchestration_response).strip()\n",
"\n",
" #print(f\"Next speaker: {next_speaker}\")\n",
"\n",
" if next_speaker == Speaker.STOCK_LOOKUP:\n",
" print(\"Stock lookup agent selected\")\n",
" current_speaker = stock_lookup_agent_factory(state)\n",
" state[\"current_speaker\"] = next_speaker\n",
" elif next_speaker == Speaker.AUTHENTICATE:\n",
" print(\"Auth agent selected\")\n",
" current_speaker = auth_agent_factory(state)\n",
" state[\"current_speaker\"] = next_speaker\n",
" elif next_speaker == Speaker.ACCOUNT_BALANCE:\n",
" print(\"Account balance agent selected\")\n",
" current_speaker = account_balance_agent_factory(state)\n",
" state[\"current_speaker\"] = next_speaker\n",
" elif next_speaker == Speaker.TRANSFER_MONEY:\n",
" print(\"Transfer money agent selected\")\n",
" current_speaker = transfer_money_agent_factory(state)\n",
" state[\"current_speaker\"] = next_speaker\n",
" elif next_speaker == Speaker.CONCIERGE:\n",
" print(\"Concierge agent selected\")\n",
" current_speaker = concierge_agent_factory(state)\n",
" else:\n",
" print(\"Orchestration agent failed to return a valid speaker; ask it to try again\")\n",
" is_retry = True\n",
" continue\n",
"\n",
" pretty_state = pprint.pformat(state, indent=4)\n",
" #print(f\"State: {pretty_state}\")\n",
"\n",
" # chat with the current speaker\n",
" response = current_speaker.chat(user_msg_str, chat_history=current_history)\n",
" print(Fore.MAGENTA + str(response) + Style.RESET_ALL)\n",
"\n",
" # update chat history\n",
" new_history = current_speaker.memory.get_all()\n",
" root_memory.set(new_history)"
],
"metadata": {
"id": "Z0KmCRnUbI-6"
},
"id": "Z0KmCRnUbI-6",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Run Agents"
],
"metadata": {
"id": "O5tU8nXClecT"
},
"id": "O5tU8nXClecT"
},
{
"cell_type": "code",
"source": [
"if __name__ == \"__main__\":\n",
" try:\n",
" run()\n",
" except KeyboardInterrupt:\n",
" print(\"\\nExiting gracefully. Goodbye!\")\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "X47pyfddlZ5L",
"outputId": "5cdf0ac1-3493-460f-8adf-fd340734d695"
},
"id": "X47pyfddlZ5L",
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"No current speaker, asking orchestration agent to decide\n",
"Concierge agent selected\n",
"\n",
"Exiting gracefully. Goodbye!\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# Advanced Usages - Adding custom Agents"
],
"metadata": {
"id": "r7BhXPxHftuN"
},
"id": "r7BhXPxHftuN"
},
{
"cell_type": "markdown",
"source": [
"\n",
"#### Advanced Usage and Customization:\n",
"\n",
"The system is designed to be highly flexible, allowing for extensive customization and expansion:\n",
"\n",
"- **Adding New Agents**:\n",
" - The architecture supports the easy addition of new agents. Developers can define new agent behaviors, tools, and system prompts, then integrate them into the orchestration logic.\n",
" - Example: You might add an agent for handling investment portfolio analysis, with tools for calculating portfolio performance, assessing risk, and providing recommendations.\n",
"\n",
"- **Customization of Agent Behaviors**:\n",
" - Each agent's behavior can be customized through the use of configurable parameters. For instance, you can adjust the stock lookup agent to fetch real-time data from different financial APIs, or modify the authentication agent to use multi-factor authentication.\n",
" - Configuration UI: Google Colab’s inline UI elements can be used to create interactive forms that allow users to customize agent parameters dynamically before running the notebook.\n",
"\n",
"- **Interactive Configuration**:\n",
" - Users can define and modify agent labels, task descriptions, tool parameters, and even the system prompt directly from the Colab interface, without altering the core codebase.\n",
" - This feature is particularly useful for experimenting with different configurations or adapting the system to different financial environments.\n",
"\n",
"- **Error Handling and Graceful Exit**:\n",
" - The system is built with robust error handling to manage unexpected inputs and user interruptions.\n",
" - For example, the main loop includes a mechanism to gracefully handle user-initiated exits (e.g., via `Ctrl+C`), ensuring that the notebook exits cleanly without displaying unnecessary error messages.\n"
],
"metadata": {
"id": "BwknHzpvnqYY"
},
"id": "BwknHzpvnqYY"
},
{
"cell_type": "markdown",
"source": [
"### Adding More Agents to the Notebook\n",
"\n",
"To extend this notebook with additional agents, follow these advanced steps:"
],
"metadata": {
"id": "JLt-ACkyfzRz"
},
"id": "JLt-ACkyfzRz"
},
{
"cell_type": "markdown",
"source": [
"#### **Define the New Agent**\n",
" - Create a new function similar to the existing agent factories (e.g., `stock_lookup_agent_factory`, `auth_agent_factory`) for the agent you want to add. The new function should:\n",
" - Define any necessary tools (functions) the agent will use.\n",
" - Set up the system prompt to instruct the agent on how to behave.\n",
" - Return an instance of `OpenAIAgent` configured with the tools and prompt."
],
"metadata": {
"id": "cXcREeSokUP-"
},
"id": "cXcREeSokUP-"
},
{
"cell_type": "markdown",
"source": [
"#### **Register the New Agent in the `Speaker` Enum**\n",
" - Add an entry for your new agent in the `Speaker` Enum to ensure it is recognized by the orchestration logic."
],
"metadata": {
"id": "PDdCFRHkgnYm"
},
"id": "PDdCFRHkgnYm"
},
{
"cell_type": "code",
"source": [
"# @title Customize Speaker Enum\n",
"\n",
"# Assign text fields for each agent's label\n",
"stock_lookup_label = \"stock_lookup\" # @param {type:\"string\"}\n",
"authenticate_label = \"authenticate\" # @param {type:\"string\"}\n",
"account_balance_label = \"account_balance\" # @param {type:\"string\"}\n",
"transfer_money_label = \"transfer_money\" # @param {type:\"string\"}\n",
"concierge_label = \"concierge\" # @param {type:\"string\"}\n",
"orchestrator_label = \"orchestrator\" # @param {type:\"string\"}\n",
"new_agent_label = \"new_agent\" # @param {type:\"string\"}\n",
"\n",
"class Speaker(str, Enum):\n",
" STOCK_LOOKUP = stock_lookup_label.lower()\n",
" AUTHENTICATE = authenticate_label.lower()\n",
" ACCOUNT_BALANCE = account_balance_label.lower()\n",
" TRANSFER_MONEY = transfer_money_label.lower()\n",
" CONCIERGE = concierge_label.lower()\n",
" ORCHESTRATOR = orchestrator_label.lower()\n",
" NEW_AGENT = new_agent_label.lower()\n"
],
"metadata": {
"id": "gBlkXEOZgq-W"
},
"id": "gBlkXEOZgq-W",
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# @title Configure New Agent\n",
"\n",
"# Assigning values to variables with inline comments for Colab to create UI elements\n",
"task_description = \"Perform a custom task\" # @param {type:\"string\"}\n",
"tool_param_label = \"Parameter for tool\" # @param {type:\"string\"}\n",
"tool_action_response = \"Action result\" # @param {type:\"string\"}\n",
"model_name = \"gpt-4o-mini\" # @param {type:\"string\"}\n",
"system_prompt_intro = \"You are an assistant designed to perform tasks related to\" # @param {type:\"string\"}\n",
"\n",
"def new_agent_factory(state: dict) -> OpenAIAgent:\n",
" def new_tool_example(param: str) -> str:\n",
" \"\"\"Description of what this tool does.\"\"\"\n",
" print(f\"Performing action with {param}\")\n",
" return f\"{tool_action_response} for {param}\"\n",
"\n",
" tools = [\n",
" FunctionTool.from_defaults(fn=new_tool_example),\n",
" # Add more tools if needed\n",
" ]\n",
"\n",
" system_prompt = (f\"\"\"\n",
" {system_prompt_intro} {task_description}.\n",
" The current user state is:\n",
" {pprint.pformat(state, indent=4)}\n",
" Follow the instructions provided to complete your task.\n",
" \"\"\")\n",
"\n",
" return OpenAIAgent.from_tools(\n",
" tools,\n",
" llm=OpenAI(model=model_name),\n",
" system_prompt=system_prompt,\n",
" )\n"
],
"metadata": {
"id": "oUuG7Ut4f5J4"
},
"id": "oUuG7Ut4f5J4",
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"#### **Integrate the New Agent into the Orchestration Logic**\n",
" - Modify the `orchestration_agent_factory` to include logic for your new agent. This involves:\n",
" - Updating the system prompt to account for the new agent’s capabilities.\n",
" - Adding logic to return the identifier for the new agent based on the user’s input and the current state.\n"
],
"metadata": {
"id": "sQSqF2ZhgecM"
},
"id": "sQSqF2ZhgecM"
},
{
"cell_type": "code",
"source": [
"# Define the system prompt for the orchestration agent\n",
"system_prompt = (f\"\"\"\n",
" You are an orchestration agent. Based on the user's state and input, decide which agent to run next.\n",
" ...\n",
" * \"{Speaker.NEW_AGENT.value}\" - if the user wants to perform a task related to {Speaker.NEW_AGENT.value}\n",
" ...\n",
"\"\"\")\n",
"\n",
"# Example of how to set the next speaker using the orchestration agent\n",
"if state.get(\"current_speaker\"):\n",
" print(f\"There's already a speaker: {state['current_speaker']}\")\n",
" next_speaker = state[\"current_speaker\"]\n",
"else:\n",
" print(\"No current speaker, asking orchestration agent to decide\")\n",
" current_history = root_memory.get() # Retrieve the current conversation history\n",
" orchestration_response = orchestration_agent_factory(state).chat(user_msg_str, chat_history=current_history)\n",
" next_speaker = str(orchestration_response).strip()\n",
"\n",
"# Ensure the orchestration logic handles this new agent identifier:\n",
"if next_speaker == Speaker.NEW_AGENT:\n",
" print(f\"{Speaker.NEW_AGENT.value.capitalize()} agent selected\")\n",
" current_speaker = new_agent_factory(state)\n",
" state[\"current_speaker\"] = next_speaker\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "C35tBJb6gfRm",
"outputId": "6ef28ba4-32b9-4260-d191-4f42a9061414"
},
"id": "C35tBJb6gfRm",
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"No current speaker, asking orchestration agent to decide\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"\n",
"#### 4. **Test the New Agent**\n",
" - Run the notebook and interact with the new agent to ensure it behaves as expected. Pay particular attention to the agent's interaction with the orchestration logic and other agents.\n",
"\n",
"#### 5. **Advanced Use Cases**\n",
" - **Conditional Logic:** Add more sophisticated logic to the orchestration agent or within individual agents to handle complex decision-making based on multiple state variables.\n",
" - **Memory Sharing:** Use shared memory buffers between agents to maintain context or track long-term goals across multiple user interactions.\n",
" - **Error Handling:** Implement retry mechanisms or fallback logic within agents to gracefully handle unexpected inputs or failed actions.\n",
"\n",
"By following these steps, you can expand the capabilities of your conversational system to handle a broader range of tasks and user interactions. This modular approach ensures that adding new functionality is straightforward and integrates smoothly with the existing architecture."
],
"metadata": {
"id": "PgGRkucigxZT"
},
"id": "PgGRkucigxZT"
},
{
"cell_type": "code",
"source": [
"if __name__ == \"__main__\":\n",
" try:\n",
" run()\n",
" except KeyboardInterrupt:\n",
" print(\"\\nExiting gracefully. Goodbye!\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "9wEpjxGWg8AU",
"outputId": "ae7fdaba-a214-4013-f586-e56822f933ae"
},
"id": "9wEpjxGWg8AU",
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"No current speaker, asking orchestration agent to decide\n",
"\n",
"Exiting gracefully. Goodbye!\n"
]
}
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
},
"colab": {
"provenance": [],
"include_colab_link": true
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment