├── .env ├── Multi_AI_Agents_with_Azure_PostgreSQL v1.ipynb ├── README.md ├── SQL_Queries.sql └── requirements.txt /.env: -------------------------------------------------------------------------------- 1 | # Azure OpenAI 2 | AZURE_OPENAI_KEY=your_openai_api_key 3 | AZURE_OPENAI_ENDPOINT=https://your-openai-endpoint 4 | AZURE_OPENAI_DEPLOYMENT=gpt-4 5 | 6 | # PostgreSQL Database 7 | POSTGRES_USER=your_username 8 | POSTGRES_PASSWORD=your_password 9 | POSTGRES_HOST=your_postgresql_host 10 | POSTGRES_PORT=5432 11 | POSTGRES_DB=your_database_name # usually postgres -------------------------------------------------------------------------------- /Multi_AI_Agents_with_Azure_PostgreSQL v1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "# Do not use it if you have already installed the requirements file\n", 10 | "# !pip install langchain langchain_openai langchain-community langchain_experimental gradio psycopg2 python-dotenv ag2\n", 11 | "# !pip uninstall pydantic\n", 12 | "# !pip install pydantic==2.9.2\n", 13 | "# !pip install autogen-agentchat~=0.2" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import autogen\n", 23 | "from langchain_community.utilities.sql_database import SQLDatabase\n", 24 | "from langchain_experimental.sql import SQLDatabaseChain\n", 25 | "from langchain_openai import AzureChatOpenAI\n", 26 | "import os\n", 27 | "from dotenv import load_dotenv\n", 28 | "import gradio as gr\n", 29 | "import asyncio\n", 30 | "import psycopg2\n", 31 | "from autogen import AssistantAgent, UserProxyAgent, config_list_from_json\n", 32 | "from langchain.cache import InMemoryCache\n", 33 | "from langchain_experimental.sql.base import SQLDatabaseChain" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "# Load environment variables from the .env file from the same directory as notebook \n", 43 | "load_dotenv()\n", 44 | "\n", 45 | "# Retrieve environment variables\n", 46 | "POSTGRES_USER = os.getenv('POSTGRES_USER')\n", 47 | "POSTGRES_PASSWORD = os.getenv('POSTGRES_PASSWORD')\n", 48 | "POSTGRES_HOST = os.getenv('POSTGRES_HOST')\n", 49 | "POSTGRES_PORT = os.getenv('POSTGRES_PORT')\n", 50 | "POSTGRES_DB = os.getenv('POSTGRES_DB')\n", 51 | "AZURE_OPENAI_KEY = os.getenv('AZURE_OPENAI_KEY')\n", 52 | "AZURE_OPENAI_ENDPOINT = os.getenv('AZURE_OPENAI_ENDPOINT')\n", 53 | "AZURE_OPENAI_DEPLOYMENT = os.getenv('AZURE_OPENAI_DEPLOYMENT')" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "# Construct the database URI\n", 63 | "shipment_db_uri = f\"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}\"\n", 64 | "crm_db_uri = f\"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}\"\n", 65 | "\n", 66 | "# Establish database connections\n", 67 | "shipment_db = SQLDatabase.from_uri(shipment_db_uri)\n", 68 | "crm_db = SQLDatabase.from_uri(crm_db_uri)" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 4, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "# Initialize the Azure OpenAI language model\n", 78 | "azure_llm = AzureChatOpenAI(\n", 79 | " azure_endpoint = AZURE_OPENAI_ENDPOINT,\n", 80 | " api_key=AZURE_OPENAI_KEY,\n", 81 | " api_version=\"2024-10-21\",\n", 82 | " deployment_name=AZURE_OPENAI_DEPLOYMENT,\n", 83 | ")" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 5, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "# Query functions for each database\n", 93 | "def query_shipment(query):\n", 94 | " return shipment_chain.invoke(query)\n", 95 | "\n", 96 | "def query_crm(query):\n", 97 | " return crm_chain.invoke(query)\n", 98 | "\n", 99 | "# Function to retrieve database schema information\n", 100 | "def get_schema_info():\n", 101 | " from sqlalchemy import text\n", 102 | " import json # Import json module to convert data to JSON string\n", 103 | " with shipment_db._engine.connect() as connection:\n", 104 | " query = text(\"\"\"\n", 105 | " SELECT\n", 106 | " cols.table_schema,\n", 107 | " cols.table_name,\n", 108 | " cols.column_name,\n", 109 | " cols.data_type,\n", 110 | " cols.is_nullable,\n", 111 | " cons.constraint_type,\n", 112 | " cons.constraint_name,\n", 113 | " fk.references_table AS referenced_table,\n", 114 | " fk.references_column AS referenced_column\n", 115 | " FROM information_schema.columns cols\n", 116 | " LEFT JOIN information_schema.key_column_usage kcu\n", 117 | " ON cols.table_schema = kcu.table_schema\n", 118 | " AND cols.table_name = kcu.table_name\n", 119 | " AND cols.column_name = kcu.column_name\n", 120 | " LEFT JOIN information_schema.table_constraints cons\n", 121 | " ON kcu.table_schema = cons.table_schema\n", 122 | " AND kcu.table_name = cons.table_name\n", 123 | " AND kcu.constraint_name = cons.constraint_name\n", 124 | " LEFT JOIN (\n", 125 | " SELECT\n", 126 | " rc.constraint_name,\n", 127 | " kcu.table_name AS references_table,\n", 128 | " kcu.column_name AS references_column\n", 129 | " FROM information_schema.referential_constraints rc\n", 130 | " JOIN information_schema.key_column_usage kcu\n", 131 | " ON rc.unique_constraint_name = kcu.constraint_name\n", 132 | " ) fk\n", 133 | " ON cons.constraint_name = fk.constraint_name\n", 134 | " WHERE cols.table_schema = 'public'\n", 135 | " ORDER BY cols.table_schema, cols.table_name, cols.ordinal_position;\n", 136 | " \"\"\")\n", 137 | " result = connection.execute(query)\n", 138 | " columns = result.keys()\n", 139 | " rows = result.fetchall()\n", 140 | " # Convert the result to a list of dictionaries\n", 141 | " schema_info = [dict(zip(columns, row)) for row in rows]\n", 142 | " return json.dumps(schema_info, indent=2)\n", 143 | "\n", 144 | "# Function to share schema information between agents\n", 145 | "def get_shared_schema_info():\n", 146 | " if schema_agent.schema_info is None:\n", 147 | " schema_agent.retrieve_and_store_schema()\n", 148 | " return schema_agent.schema_info\n", 149 | "\n", 150 | "# Method to retrieve and store schema information\n", 151 | "def retrieve_and_store_schema(agent):\n", 152 | " schema_info = get_schema_info()\n", 153 | " agent.schema_info = schema_info\n", 154 | " return \"Schema information retrieved and stored.\"\n", 155 | "\n", 156 | "# Method to add a new customer to the CRM database\n", 157 | "def add_customer(procedure_name, parameters):\n", 158 | " from sqlalchemy import text\n", 159 | " with crm_db._engine.connect() as connection:\n", 160 | " trans = connection.begin() # Begin a transaction\n", 161 | " try:\n", 162 | " # Prepare the parameter placeholders\n", 163 | " param_placeholders = ', '.join([f\":{k}\" for k in parameters.keys()])\n", 164 | " # Construct the SQL command to execute the stored procedure\n", 165 | " sql_command = text(f\"CALL {procedure_name}({param_placeholders})\")\n", 166 | " # Pass parameters as a dictionary\n", 167 | " result = connection.execute(sql_command, parameters)\n", 168 | " # Commit the transaction\n", 169 | " trans.commit()\n", 170 | " # Return a success message\n", 171 | " return \"Customer added successfully.\"\n", 172 | " except Exception as e:\n", 173 | " trans.rollback()\n", 174 | " return f\"An error occurred while executing the stored procedure: {e}\"\n", 175 | "\n", 176 | "# Method to create a new shipment in the shipment database\n", 177 | "def send_shipment(procedure_name, parameters):\n", 178 | " from sqlalchemy import text\n", 179 | " import json\n", 180 | " with shipment_db._engine.connect() as connection:\n", 181 | " trans = connection.begin() # Begin a transaction\n", 182 | " try:\n", 183 | " # If 'items' is a list, convert it to JSON string\n", 184 | " if isinstance(parameters.get('items'), list):\n", 185 | " parameters['items'] = json.dumps(parameters['items'])\n", 186 | " # Prepare the parameter placeholders\n", 187 | " param_placeholders = ', '.join([f\":{k}\" for k in parameters.keys()])\n", 188 | " # Construct the SQL command\n", 189 | " sql_command = text(f\"CALL {procedure_name}({param_placeholders})\")\n", 190 | " # Execute the stored procedure\n", 191 | " result = connection.execute(sql_command, parameters)\n", 192 | " # Commit the transaction\n", 193 | " trans.commit()\n", 194 | " return \"Shipment sent successfully.\"\n", 195 | " except Exception as e:\n", 196 | " trans.rollback()\n", 197 | " return f\"An error occurred while executing the stored procedure: {e}\"" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [ 206 | "# Language model configuration with functions\n", 207 | "llm_config = {\n", 208 | " \"config_list\": [\n", 209 | " {\n", 210 | " \"model\": AZURE_OPENAI_DEPLOYMENT,\n", 211 | " \"temperature\": 0.7,\n", 212 | " \"api_key\": AZURE_OPENAI_KEY,\n", 213 | " \"azure_endpoint\": AZURE_OPENAI_ENDPOINT,\n", 214 | " \"api_type\": \"azure\",\n", 215 | " \"api_version\": \"2024-10-21\"\n", 216 | " }\n", 217 | " ],\n", 218 | " \"seed\": 42,\n", 219 | " \"functions\": [\n", 220 | " {\n", 221 | " \"name\": \"query_shipment\",\n", 222 | " \"description\": \"Queries the Shipment database based on the provided query\",\n", 223 | " \"parameters\": {\n", 224 | " \"type\": \"object\",\n", 225 | " \"properties\": {\n", 226 | " \"query\": {\"type\": \"string\", \"description\": \"The SQL query to execute on the shipment database\"}\n", 227 | " },\n", 228 | " \"required\": [\"query\"]\n", 229 | " }\n", 230 | " },\n", 231 | " {\n", 232 | " \"name\": \"query_crm\",\n", 233 | " \"description\": \"Queries the CRM database based on the provided query\",\n", 234 | " \"parameters\": {\n", 235 | " \"type\": \"object\",\n", 236 | " \"properties\": {\n", 237 | " \"query\": {\"type\": \"string\", \"description\": \"The SQL query to execute on the CRM database\"}\n", 238 | " },\n", 239 | " \"required\": [\"query\"]\n", 240 | " }\n", 241 | " },\n", 242 | " {\n", 243 | " \"name\": \"get_schema_info\",\n", 244 | " \"description\": \"Retrieves the database schema and referential integrity information. Only use 'get_schema_info' to retrieve schema information and store it. Do not do anything else\",\n", 245 | " \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []}\n", 246 | " },\n", 247 | " {\n", 248 | " \"name\": \"get_shared_schema_info\",\n", 249 | " \"description\": \"Provides the stored schema information to other agents.\",\n", 250 | " \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []}\n", 251 | " },\n", 252 | " {\n", 253 | " \"name\": \"add_customer\",\n", 254 | " \"description\": \"Adds a customer to the CRM database by executing the 'add_customer' stored procedure with the provided parameters.\",\n", 255 | " \"parameters\": {\n", 256 | " \"type\": \"object\",\n", 257 | " \"properties\": {\n", 258 | " \"procedure_name\": {\n", 259 | " \"type\": \"string\",\n", 260 | " \"description\": \"The name of the stored procedure to execute (should be 'add_customer')\"\n", 261 | " },\n", 262 | " \"parameters\": {\n", 263 | " \"type\": \"object\",\n", 264 | " \"description\": \"A dictionary of parameters to pass to the stored procedure, including 'name', 'email', 'phone', and 'address'.\",\n", 265 | " \"properties\": {\n", 266 | " \"name\": {\"type\": \"string\"},\n", 267 | " \"email\": {\"type\": \"string\"},\n", 268 | " \"phone\": {\"type\": \"string\"},\n", 269 | " \"address\": {\"type\": \"string\"}\n", 270 | " },\n", 271 | " \"required\": [\"name\", \"email\", \"phone\", \"address\"]\n", 272 | " }\n", 273 | " },\n", 274 | " \"required\": [\"procedure_name\", \"parameters\"]\n", 275 | " }\n", 276 | " },\n", 277 | " {\n", 278 | " \"name\": \"send_shipment\",\n", 279 | " \"description\": \"Sends a shipment by executing the 'send_shipment' stored procedure with the provided parameters.\",\n", 280 | " \"parameters\": {\n", 281 | " \"type\": \"object\",\n", 282 | " \"properties\": {\n", 283 | " \"procedure_name\": {\n", 284 | " \"type\": \"string\",\n", 285 | " \"description\": \"The name of the stored procedure to execute (should be 'send_shipment')\"\n", 286 | " },\n", 287 | " \"parameters\": {\n", 288 | " \"type\": \"object\",\n", 289 | " \"description\": \"Parameters for the stored procedure, including 'customer_id', 'origin_id', 'destination_id', 'shipment_date', 'items', 'status', 'tracking_status', and 'location_id'.\",\n", 290 | " \"properties\": {\n", 291 | " \"customer_id\": {\"type\": \"integer\"},\n", 292 | " \"origin_id\": {\"type\": \"integer\"},\n", 293 | " \"destination_id\": {\"type\": \"integer\"},\n", 294 | " \"shipment_date\": {\"type\": \"string\", \"format\": \"date\"},\n", 295 | " \"items\": {\n", 296 | " \"type\": \"array\",\n", 297 | " \"items\": {\n", 298 | " \"type\": \"object\",\n", 299 | " \"properties\": {\n", 300 | " \"product_id\": {\"type\": \"integer\"},\n", 301 | " \"quantity\": {\"type\": \"integer\"}\n", 302 | " },\n", 303 | " \"required\": [\"product_id\", \"quantity\"]\n", 304 | " }\n", 305 | " },\n", 306 | " \"status\": {\"type\": \"string\"},\n", 307 | " \"tracking_status\": {\"type\": \"string\"},\n", 308 | " \"location_id\": {\"type\": \"integer\"}\n", 309 | " },\n", 310 | " \"required\": [\"customer_id\", \"origin_id\", \"destination_id\", \"shipment_date\", \"items\", \"status\", \"tracking_status\", \"location_id\"]\n", 311 | " }\n", 312 | " },\n", 313 | " \"required\": [\"procedure_name\", \"parameters\"]\n", 314 | " }\n", 315 | " }\n", 316 | " ]\n", 317 | "}\n", 318 | "\n", 319 | "\n", 320 | "# Initialize the database chains\n", 321 | "shipment_chain = SQLDatabaseChain(llm=azure_llm, database=shipment_db, verbose=True)\n", 322 | "crm_chain = SQLDatabaseChain(llm=azure_llm, database=crm_db, verbose=True)\n", 323 | "\n", 324 | "# Create assistant agents\n", 325 | "shipment_agent = autogen.ConversableAgent(\n", 326 | " name=\"ShipmentAgent\",\n", 327 | " llm_config=llm_config,\n", 328 | " description=\"Manage shipments in the main database.\",\n", 329 | " system_message=(\n", 330 | " \"Your role is to query the main database using 'query_shipment'. \"\n", 331 | " \"Focus on the shipments tables and ensure that all shipments are tracked correctly. You can make SELECT using PostgreSQL queries. Use the 'add_customer' function to call the appropriate stored procedure for adding new customers.\"\n", 332 | " \"For Insert, Update, and Delete operations, have human to validate the operation before making it.\"\n", 333 | " \"Here is an example on how to call add_customer stored procedure: CALL add_customer('marcre@contoso.com', 'marcre@contoso.com', '+1 123 456 7890','1 Main Street, Redmond');\"\n", 334 | " \"Use 'get_shared_schema_info' from SchemaAgent to retrieve schema information.\"\n", 335 | " ),\n", 336 | ")\n", 337 | "\n", 338 | "crm_agent = autogen.ConversableAgent(\n", 339 | " name=\"CRMAgent\",\n", 340 | " llm_config=llm_config,\n", 341 | " description=\"Manages customer and product information in the second database.\",\n", 342 | " system_message=(\n", 343 | " \"Your role is to query the second database using 'query_crm'. \"\n", 344 | " \"Focus on maintaining the customers and product tables. You can make SELECT using PostgreSQL queries. Use the 'send_shipment' function to call the appropriate stored procedure for creating shipments.\"\n", 345 | " \"For Insert, Update, and Delete operations, have human to validate the operation before making it.\"\n", 346 | " \"Here is an example on how to call 'send_shipment' stored procedure:\"\n", 347 | " \"CALL send_shipment(\"\n", 348 | " \" customer_id := 1,\"\n", 349 | " \" origin_id := 3,\"\n", 350 | " \" destination_id := 2,\"\n", 351 | " \" shipment_date := '2023-10-01',\"\n", 352 | " \" items := '[\"\n", 353 | " \" {'product_id': 1, 'quantity': 5},\"\n", 354 | " \" {'product_id': 2, 'quantity': 3}\"\n", 355 | " \" ]'::jsonb,\"\n", 356 | " \" status := 'In Transit',\"\n", 357 | " \" tracking_status := 'Departed Origin',\"\n", 358 | " \" location_id := 3\"\n", 359 | " \");\"\n", 360 | " \"Use 'get_shared_schema_info' from SchemaAgent to retrieve schema information. \"\n", 361 | " ),\n", 362 | ")\n", 363 | "\n", 364 | "schema_agent = autogen.ConversableAgent(\n", 365 | " name=\"SchemaAgent\",\n", 366 | " llm_config=llm_config,\n", 367 | " description=\"Understands and shares database schema information.\",\n", 368 | " system_message=(\n", 369 | " \"Your role is to retrieve and understand the database schema and referential integrity constraints.\"\n", 370 | " \"Only use 'get_schema_info' to retrieve schema information and store it. Do not do anything else. And always provide schema information when you start first.\"\n", 371 | " ),\n", 372 | ")\n", 373 | "\n", 374 | "# Register functions with the agents\n", 375 | "shipment_agent.register_function(function_map={\n", 376 | " \"query_shipment\": query_shipment,\n", 377 | " \"send_shipment\": send_shipment\n", 378 | "})\n", 379 | "crm_agent.register_function(function_map={\"query_crm\": query_crm,\n", 380 | " \"add_customer\": add_customer})\n", 381 | "schema_agent.register_function(\n", 382 | " function_map={\n", 383 | " \"get_schema_info\": get_schema_info,\n", 384 | " \"get_shared_schema_info\": get_shared_schema_info,\n", 385 | " }\n", 386 | ")\n", 387 | "\n", 388 | "# Add schema_info attribute and bind method to schema_agent\n", 389 | "import types\n", 390 | "schema_agent.schema_info = None\n", 391 | "schema_agent.retrieve_and_store_schema = types.MethodType(retrieve_and_store_schema, schema_agent)" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 7, 397 | "metadata": {}, 398 | "outputs": [], 399 | "source": [ 400 | "# Create a user proxy agent\n", 401 | "user_proxy = autogen.UserProxyAgent(\n", 402 | " name=\"User_proxy\",\n", 403 | " system_message=\"A human admin.\",\n", 404 | " code_execution_config={\n", 405 | " \"last_n_messages\": 4,\n", 406 | " \"work_dir\": \"groupchat\",\n", 407 | " \"use_docker\": False,\n", 408 | " },\n", 409 | " human_input_mode=\"ALWAYS\", #Using this mode to give input to agents\n", 410 | ")\n", 411 | "\n", 412 | "# Set up the group chat and manager\n", 413 | "groupchat = autogen.GroupChat(\n", 414 | " agents=[user_proxy, schema_agent, shipment_agent, crm_agent],\n", 415 | " messages=[],\n", 416 | " max_round=30 # Maximum number of rounds in the conversation\n", 417 | ")\n", 418 | "\n", 419 | "manager = autogen.GroupChatManager(groupchat=groupchat)" 420 | ] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "execution_count": null, 425 | "metadata": {}, 426 | "outputs": [], 427 | "source": [ 428 | "# Chat with your data use case\n", 429 | "\n", 430 | "user_proxy.initiate_chat(manager, message=\"Which products with names are currently tracking in transit?\") " 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "execution_count": null, 436 | "metadata": {}, 437 | "outputs": [], 438 | "source": [ 439 | "# See messages in the groupchat\n", 440 | "print(\"Current messages in the groupchat:\")\n", 441 | "for idx, msg in enumerate(manager.groupchat.messages):\n", 442 | " print(f\"Message {idx}: {msg}\")" 443 | ] 444 | }, 445 | { 446 | "cell_type": "code", 447 | "execution_count": null, 448 | "metadata": {}, 449 | "outputs": [], 450 | "source": [ 451 | "# Chat with your data use case\n", 452 | "\n", 453 | "user_proxy.initiate_chat(\n", 454 | " manager,\n", 455 | " message=(\n", 456 | " \"Is Alice Johnson a Customer?\"\n", 457 | " )\n", 458 | ")" 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [ 467 | "# Database Development use case\n", 468 | "\n", 469 | "user_proxy.initiate_chat(manager, message=\"I need to create a Stored Procedure to send shipments. It spans across shipments, shipment_items and shipment_tracking? Shipment_items might have multiple items and can vary. What stored procedure would you propose?\") " 470 | ] 471 | }, 472 | { 473 | "cell_type": "code", 474 | "execution_count": null, 475 | "metadata": {}, 476 | "outputs": [], 477 | "source": [ 478 | "\n", 479 | "user_proxy.initiate_chat(\n", 480 | " manager,\n", 481 | " message=(\n", 482 | " \"I would like to create a stored orodcedure to delete customers. What would be the best way to do this?\"\n", 483 | " )\n", 484 | ")" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": null, 490 | "metadata": {}, 491 | "outputs": [], 492 | "source": [ 493 | "# Act on Data use case: add a customer name\n", 494 | "\n", 495 | "user_proxy.initiate_chat(\n", 496 | " manager,\n", 497 | " message=(\n", 498 | " \"Can you add Marc with email address marcr@contoso.com, phone number +1 123 456 7890 and address in 1 Main Street, Redmond?\"\n", 499 | " )\n", 500 | ")" 501 | ] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "execution_count": null, 506 | "metadata": {}, 507 | "outputs": [], 508 | "source": [ 509 | "# Act on Data use case: add a customer name with incomplete information\n", 510 | "\n", 511 | "user_proxy.initiate_chat(\n", 512 | " manager,\n", 513 | " message=(\n", 514 | " \"Can you add Marc with email address marcre@contoso.com, phone number +1 123 456 7890?\"\n", 515 | " )\n", 516 | ")" 517 | ] 518 | }, 519 | { 520 | "cell_type": "code", 521 | "execution_count": null, 522 | "metadata": {}, 523 | "outputs": [], 524 | "source": [ 525 | "# Act on Data use case: Create a shipment using stored procedure send_shipment writing across 3 tables\n", 526 | "\n", 527 | "user_proxy.initiate_chat(\n", 528 | " manager,\n", 529 | " message=(\n", 530 | " \"Can you create a new shipment of 1 Laptop and 1 Smartphone to Cathy Lee and ensure shipment is updated to Departed Origin from the location in New York and towards Los Angeles date is today? Ask questions if you have doubts.\"\n", 531 | " )\n", 532 | ")" 533 | ] 534 | }, 535 | { 536 | "cell_type": "code", 537 | "execution_count": null, 538 | "metadata": {}, 539 | "outputs": [], 540 | "source": [ 541 | "# Act on Data use case: Create a shipment using stored procedure send_shipment writing across 3 tables\n", 542 | "\n", 543 | "user_proxy.initiate_chat(\n", 544 | " manager,\n", 545 | " message=(\n", 546 | " \"Can you create a new shipment of 1 Laptop and 1 Smartphone to Cathy Lee and ensure shipment is updated to Departed Origin towards Los Angeles date is today? Ask questions if you miss information.\"\n", 547 | " )\n", 548 | ")" 549 | ] 550 | }, 551 | { 552 | "cell_type": "code", 553 | "execution_count": 10, 554 | "metadata": {}, 555 | "outputs": [], 556 | "source": [ 557 | "import asyncio\n", 558 | "\n", 559 | "# Initialize chat history\n", 560 | "chat_history = []\n", 561 | "\n", 562 | "async def process_user_input(user_message, chat_history):\n", 563 | " # Append your message with role 'assistant' (to appear on the left)\n", 564 | " chat_history.append({\"role\": \"assistant\", \"content\": user_message})\n", 565 | "\n", 566 | " # Append a placeholder for the agent's response with role 'user' (to appear on the right)\n", 567 | " placeholder_index = len(chat_history)\n", 568 | " chat_history.append({\"role\": \"user\", \"content\": \"Processing...\"})\n", 569 | "\n", 570 | " # Return the updated chat history immediately\n", 571 | " yield chat_history, chat_history\n", 572 | "\n", 573 | " # Now process the agent's response\n", 574 | " # Use the user_proxy agent to process the message\n", 575 | " await asyncio.to_thread(user_proxy.initiate_chat, manager, message=user_message)\n", 576 | "\n", 577 | " # Collect messages from the agents\n", 578 | " agent_messages = [\n", 579 | " msg for msg in manager.groupchat.messages if msg.get(\"role\", \"\") != \"System\"\n", 580 | " ]\n", 581 | "\n", 582 | " # Remove the placeholder\n", 583 | " if placeholder_index < len(chat_history):\n", 584 | " chat_history.pop(placeholder_index)\n", 585 | "\n", 586 | " # Append each agent's message to the chat history\n", 587 | " for msg in agent_messages:\n", 588 | " name = msg.get(\"name\", \"Agent\")\n", 589 | " content = msg.get(\"content\", \"\")\n", 590 | " role = \"user\" # Agents' messages will appear on the right\n", 591 | "\n", 592 | " # Include the agent's name in the content\n", 593 | " content_with_name = f\"**{name}**: {content}\"\n", 594 | "\n", 595 | " # Append to chat history\n", 596 | " chat_history.append({\"role\": role, \"content\": content_with_name})\n", 597 | "\n", 598 | " # Yield after each agent's message to update the UI\n", 599 | " yield chat_history, chat_history\n", 600 | "\n", 601 | " # Return the final chat history\n", 602 | " yield chat_history, chat_history\n", 603 | "\n", 604 | "def gradio_chat_interface():\n", 605 | " with gr.Blocks() as demo:\n", 606 | " chat_history_state = gr.State([])\n", 607 | "\n", 608 | " gr.Markdown(\"# Multi-Agent Chat Interface\")\n", 609 | "\n", 610 | " with gr.Row():\n", 611 | " chatbot = gr.Chatbot(type=\"messages\") # Use 'messages' format\n", 612 | " with gr.Row():\n", 613 | " user_input = gr.Textbox(\n", 614 | " placeholder=\"Type your message here...\",\n", 615 | " show_label=False\n", 616 | " )\n", 617 | " send_button = gr.Button(\"Send\")\n", 618 | " clear_button = gr.Button(\"Clear Chat\")\n", 619 | "\n", 620 | " async def on_user_message(user_message, chat_history):\n", 621 | " if user_message:\n", 622 | " # Process the user input and get the updated chat history\n", 623 | " # Use a generator to handle incremental updates\n", 624 | " response = process_user_input(user_message, chat_history)\n", 625 | " async for chat_history_update, chat_history_state_update in response:\n", 626 | " await asyncio.sleep(0)\n", 627 | " yield gr.update(value=chat_history_update), chat_history_state_update\n", 628 | "\n", 629 | " send_button.click(\n", 630 | " on_user_message,\n", 631 | " inputs=[user_input, chat_history_state],\n", 632 | " outputs=[chatbot, chat_history_state]\n", 633 | " )\n", 634 | "\n", 635 | " user_input.submit(\n", 636 | " on_user_message,\n", 637 | " inputs=[user_input, chat_history_state],\n", 638 | " outputs=[chatbot, chat_history_state]\n", 639 | " )\n", 640 | "\n", 641 | " clear_button.click(\n", 642 | " lambda: ([], []),\n", 643 | " None,\n", 644 | " [chatbot, chat_history_state],\n", 645 | " queue=False\n", 646 | " )\n", 647 | "\n", 648 | " return demo" 649 | ] 650 | }, 651 | { 652 | "cell_type": "code", 653 | "execution_count": null, 654 | "metadata": {}, 655 | "outputs": [], 656 | "source": [ 657 | "# Run the Gradio interface\n", 658 | "demo = gradio_chat_interface()\n", 659 | "demo.launch()" 660 | ] 661 | } 662 | ], 663 | "metadata": { 664 | "kernelspec": { 665 | "display_name": "test_notebook", 666 | "language": "python", 667 | "name": "python3" 668 | }, 669 | "language_info": { 670 | "codemirror_mode": { 671 | "name": "ipython", 672 | "version": 3 673 | }, 674 | "file_extension": ".py", 675 | "mimetype": "text/x-python", 676 | "name": "python", 677 | "nbconvert_exporter": "python", 678 | "pygments_lexer": "ipython3", 679 | "version": "3.13.0" 680 | } 681 | }, 682 | "nbformat": 4, 683 | "nbformat_minor": 2 684 | } 685 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 💜 IMPORTANT: This is the latest and recommended repository for using PostgreSQL with AutoGen 0.4.

3 | 👉 Click here to explore the repo 4 |
5 | 6 | # **Multi-Agent AI System with LangChain, AutoGen, Azure OpenAI GPT-4, and Azure PostgreSQL** 7 | 8 |
9 | Architecture 10 |
11 | 12 | This repository demonstrates how to build a **multi-agent AI system** using: 13 | - **LangChain** for natural language to SQL translation. 14 | - **AutoGen** for coordinating AI agents in collaborative workflows. 15 | - **Azure OpenAI GPT-4** for intelligent language understanding and generation of SQL queries in PostgreSQL. 16 | - **Azure Database for PostgreSQL** for data storage and querying. 17 | 18 | The application showcases a shipping company where agents manage shipments, customers and product informations. The main goal of this repository is to illustrate how easy it is to have agents not just reading data but also acting on them. It extends the "Chat With Your Data" to "Chat and Act on Your Data". ** We welcome contributions to help make those agents more reliable and under guardrails. Feel free to contribute to more agents as well! ** 19 | 20 | ## **Features** 21 | 22 | - 🌐 **Gradio UI**: User-friendly interface for natural language interactions. 23 | - 🤖 **AutoGen Multi-Agent System**: Agents collaborate to handle specific tasks: 24 | - **SchemaAgent**: Manages database schema retrieval and sharing. 25 | - **ShipmentAgent**: Handles shipment-related queries and updates. It can use the stored procedure *send_shipment* to create shipments. 26 | - **CRMAgent**: Manages customer and product-related data. It can use the stored procedure *add_customer* to create new customers. 27 | - 🧠 **Azure OpenAI GPT-4**: Generates SQL queries and natural language responses. 28 | - 🛢️ **Azure PostgreSQL**: Stores shipment, customer, and product data. 29 | 30 | ## **Getting Started** 31 | 32 | ### **1. Prerequisites** 33 | 34 | - Python 3.9+ and <3.13 35 | - An Azure account with: 36 | - **Azure OpenAI Service** (GPT-4 deployed). **Note: I am looking to see how we can use other GPT models but they keep adding ``` sql which returns an error** 37 | - **Azure Database for PostgreSQL** (configured with necessary tables). 38 | - Environment setup: 39 | - `python-dotenv` for environment variables. 40 | - PostgreSQL client library (`psycopg2` or similar). 41 | 42 | --- 43 | 44 | ### **2. Setup Instructions** 45 | 46 | #### **Clone the Repository** 47 | 48 | ```bash 49 | git clone https://github.com/Azure-Samples/azure-postgresql-openai-langchain-autogen-demo.git 50 | ``` 51 | ```bash 52 | cd azure-postgresql-openai-langchain-autogen-demo 53 | ``` 54 | 55 | #### **Create Tables, Add Data and Create Stored Procedures** 56 | - Use the file **SQL_Queries.sql** to set your database. 57 | 58 | #### **Configure .env File** 59 | 60 | Create a **.env** file in the root directory to store sensitive credentials. Use the following template: 61 | 62 | ```ini 63 | # Azure OpenAI 64 | AZURE_OPENAI_KEY=your_openai_api_key 65 | AZURE_OPENAI_ENDPOINT=https://your-openai-endpoint 66 | AZURE_OPENAI_DEPLOYMENT=gpt-4 67 | 68 | # PostgreSQL Database 69 | POSTGRES_USER=your_username 70 | POSTGRES_PASSWORD=your_password 71 | POSTGRES_HOST=your_postgresql_host 72 | POSTGRES_PORT=5432 73 | POSTGRES_DB=your_database_name 74 | ``` 75 | 76 | Replace the placeholder values with your actual credentials. The Jupyter Notebook is configured with .env been located in the same root folder in my machine. 77 | 78 | If you use Google Collab and you want to upload .env file, you will have to add the following code: 79 | 80 | ```python 81 | from google.colab import files 82 | files.upload() # Upload your .env file 83 | ``` 84 | 85 | #### Install libraries 86 | - I recommend using Jupyter Notebook in VS Code 87 | - You should create a virtual environment to avoid any library conflicts: 88 | 1. Go to your root folder where you have cloned the files 89 | 2. Create a virtual environment named `.venv` in your current directory: 90 | ```python 91 | python3 -m venv .venv 92 | ``` 93 | 3. Activate: 94 | 95 | a. **On Windows:** 96 | ```bash 97 | .venv\Scripts\activate 98 | ``` 99 | 100 | b. **On Mac/Linux:** 101 | ```bash 102 | source .venv/bin/activate 103 | ``` 104 | 4. Install the Requirement File: 105 | ```python 106 | pip install -r requirements.txt 107 | ``` 108 | 109 | #### **Usage - example of questions that you can ask:** 110 | 111 | ##### **Chat with your Data Examples**: 112 | - Which products with names are currently tracking in transit? 113 | - Is Alice Johnson a Customer? 114 | 115 | ##### **Multi-agents to help develop on the database example question**: 116 | - I would like to create a stored Procedure to delete customers. What would be the best way to do this? 117 | 118 | ##### **Act on Your Data example**: 119 | - Can you add Marc with email address marcr@contoso.com, phone number +1 123 456 7890 and address in 1 Main Street, Redmond? 120 | - Can you add Marc with email address marcre@contoso.com, phone number +1 123 456 7890? **Note: the information is incomplete and the agents should not perfom an operation** 121 | - Can you create a new shipment of 1 Laptop and 1 Smartphone to Marc and ensure shipment is updated to Departed Origin from the location in New York and towards Los Angeles date is today? 122 | -------------------------------------------------------------------------------- /SQL_Queries.sql: -------------------------------------------------------------------------------- 1 | -- Create Tables 2 | 3 | CREATE TABLE shipments ( shipment_id SERIAL PRIMARY KEY, shipment_date DATE NOT NULL, status VARCHAR(50), origin_id INTEGER REFERENCES locations(location_id), destination_id INTEGER REFERENCES locations(location_id), customer_id INTEGER REFERENCES customers(customer_id) ); 4 | CREATE TABLE shipment_items ( item_id SERIAL PRIMARY KEY, shipment_id INTEGER REFERENCES shipments(shipment_id), product_id INTEGER REFERENCES products(product_id), quantity INTEGER NOT NULL, weight NUMERIC(10, 2) ); 5 | CREATE TABLE locations ( location_id SERIAL PRIMARY KEY, city VARCHAR(100), state VARCHAR(100), country VARCHAR(100), zip_code VARCHAR(20) ); 6 | CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, name VARCHAR(150) NOT NULL, email VARCHAR(150), phone VARCHAR(50), address VARCHAR(250) ); 7 | CREATE TABLE products ( product_id SERIAL PRIMARY KEY, name VARCHAR(150) NOT NULL, description TEXT, price NUMERIC(10, 2), weight NUMERIC(10, 2) ); 8 | CREATE TABLE shipment_tracking ( tracking_id SERIAL PRIMARY KEY, shipment_id INTEGER REFERENCES shipments(shipment_id), status VARCHAR(50) NOT NULL, location_id INTEGER REFERENCES locations(location_id), timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); 9 | 10 | -- Add Data 11 | INSERT INTO locations (city, state, country, zip_code) VALUES ('New York', 'NY', 'USA', '10001'), ('Los Angeles', 'CA', 'USA', '90001'), ('Chicago', 'IL', 'USA', '60601'), ('Houston', 'TX', 'USA', '77001'); 12 | INSERT INTO customers (name, email, phone, address) VALUES ('Alice Johnson', 'alice@example.com', '555-1234', '123 Maple St, New York, NY'), ('Bob Smith', 'bob@example.com', '555-5678', '456 Oak Ave, Los Angeles, CA'), ('Cathy Lee', 'cathy@example.com', '555-8765', '789 Pine Rd, Chicago, IL'); 13 | INSERT INTO products (name, description, price, weight) VALUES ('Laptop', '15-inch screen, 8GB RAM, 256GB SSD', 1200.00, 2.5), ('Smartphone', '128GB storage, 6GB RAM', 800.00, 0.4), ('Headphones', 'Noise-cancelling, wireless', 150.00, 0.3); 14 | INSERT INTO shipments (shipment_date, status, origin_id, destination_id, customer_id) VALUES ('2024-11-10', 'In Transit', 1, 2, 1), ('2024-11-11', 'Delivered', 3, 4, 2), ('2024-11-12', 'In Transit', 2, 3, 3); 15 | INSERT INTO shipment_items (shipment_id, product_id, quantity, weight) VALUES (1, 1, 2, 5.0), (1, 3, 1, 0.3), (2, 2, 1, 0.4), (3, 1, 1, 2.5), (3, 3, 2, 0.6); 16 | INSERT INTO shipment_tracking (shipment_id, status, location_id, timestamp) VALUES (1, 'Departed Origin', 1, '2024-11-10 08:00:00'), (1, 'In Transit', 2, '2024-11-11 12:30:00'), (2, 'Departed Origin', 3, '2024-11-11 09:00:00'), (2, 'Delivered', 4, '2024-11-12 15:00:00'), (3, 'Departed Origin', 2, '2024-11-12 10:00:00'), (3, 'In Transit', 3, '2024-11-13 13:45:00'); 17 | 18 | -- Create Store Procedures add_customer and send_shipment 19 | 20 | CREATE OR REPLACE PROCEDURE add_customer( 21 | p_name VARCHAR, 22 | p_email VARCHAR, 23 | p_phone VARCHAR, 24 | p_address VARCHAR 25 | ) 26 | LANGUAGE plpgsql 27 | AS $$ 28 | BEGIN 29 | INSERT INTO customers (name, email, phone, address) 30 | VALUES (p_name, p_email, p_phone, p_address); 31 | END; 32 | $$; 33 | 34 | CREATE OR REPLACE PROCEDURE send_shipment( 35 | customer_id INTEGER, 36 | origin_id INTEGER, 37 | destination_id INTEGER, 38 | shipment_date DATE, 39 | items JSONB, -- Assume JSONB format for items array 40 | status VARCHAR, 41 | tracking_status VARCHAR, 42 | location_id INTEGER 43 | ) 44 | LANGUAGE plpgsql 45 | AS $$ 46 | DECLARE 47 | shipment_id INTEGER; 48 | item JSONB; 49 | BEGIN 50 | -- Insert into shipments table 51 | INSERT INTO shipments (customer_id, origin_id, destination_id, shipment_date, status) 52 | VALUES (customer_id, origin_id, destination_id, shipment_date, status) 53 | RETURNING shipments.shipment_id INTO shipment_id; 54 | 55 | -- Insert into shipment_items table 56 | FOR item IN SELECT * FROM jsonb_array_elements(items) 57 | LOOP 58 | INSERT INTO shipment_items (shipment_id, product_id, quantity) 59 | VALUES ( 60 | shipment_id, 61 | (item->>'product_id')::INTEGER, 62 | (item->>'quantity')::INTEGER 63 | ); 64 | END LOOP; 65 | 66 | -- Insert into shipment_tracking table 67 | INSERT INTO shipment_tracking (shipment_id, status, location_id, "timestamp") 68 | VALUES (shipment_id, tracking_status, location_id, NOW()); 69 | END; 70 | $$; -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ag2==0.6.0 2 | aiofiles==23.2.1 3 | aiohappyeyeballs==2.4.4 4 | aiohttp==3.11.11 5 | aiosignal==1.3.2 6 | annotated-types==0.7.0 7 | anyio==4.7.0 8 | appnope==0.1.4 9 | asttokens==3.0.0 10 | asyncer==0.0.8 11 | attrs==24.3.0 12 | autogen-agentchat==0.2.40 13 | certifi==2024.12.14 14 | charset-normalizer==3.4.0 15 | click==8.1.7 16 | comm==0.2.2 17 | dataclasses-json==0.6.7 18 | debugpy==1.8.11 19 | decorator==5.1.1 20 | diskcache==5.6.3 21 | distro==1.9.0 22 | docker==7.1.0 23 | executing==2.1.0 24 | fastapi==0.115.6 25 | ffmpy==0.5.0 26 | filelock==3.16.1 27 | FLAML==2.3.3 28 | frozenlist==1.5.0 29 | fsspec==2024.12.0 30 | gradio==5.9.1 31 | gradio_client==1.5.2 32 | h11==0.14.0 33 | httpcore==1.0.7 34 | httpx==0.28.1 35 | httpx-sse==0.4.0 36 | huggingface-hub==0.27.0 37 | idna==3.10 38 | ipykernel==6.29.5 39 | ipython==8.31.0 40 | jedi==0.19.2 41 | Jinja2==3.1.4 42 | jiter==0.8.2 43 | jsonpatch==1.33 44 | jsonpointer==3.0.0 45 | jupyter_client==8.6.3 46 | jupyter_core==5.7.2 47 | langchain==0.3.13 48 | langchain-community==0.3.13 49 | langchain-core==0.3.28 50 | langchain-experimental==0.3.4 51 | langchain-openai==0.2.14 52 | langchain-text-splitters==0.3.4 53 | langsmith==0.2.4 54 | markdown-it-py==3.0.0 55 | MarkupSafe==2.1.5 56 | marshmallow==3.23.2 57 | matplotlib-inline==0.1.7 58 | mdurl==0.1.2 59 | multidict==6.1.0 60 | mypy-extensions==1.0.0 61 | nest-asyncio==1.6.0 62 | numpy==1.26.4 63 | openai==1.58.1 64 | orjson==3.10.12 65 | packaging==24.2 66 | pandas==2.2.3 67 | parso==0.8.4 68 | pexpect==4.9.0 69 | pillow==11.0.0 70 | platformdirs==4.3.6 71 | prompt_toolkit==3.0.48 72 | propcache==0.2.1 73 | psutil==6.1.1 74 | psycopg2==2.9.10 75 | ptyprocess==0.7.0 76 | pure_eval==0.2.3 77 | pyautogen==0.6.0 78 | pydantic==2.9.2 79 | pydantic-settings==2.7.0 80 | pydantic_core==2.23.4 81 | pydub==0.25.1 82 | Pygments==2.18.0 83 | python-dateutil==2.9.0.post0 84 | python-dotenv==1.0.1 85 | python-multipart==0.0.20 86 | pytz==2024.2 87 | PyYAML==6.0.2 88 | pyzmq==26.2.0 89 | regex==2024.11.6 90 | requests==2.32.3 91 | requests-toolbelt==1.0.0 92 | rich==13.9.4 93 | ruff==0.8.4 94 | safehttpx==0.1.6 95 | semantic-version==2.10.0 96 | shellingham==1.5.4 97 | six==1.17.0 98 | sniffio==1.3.1 99 | SQLAlchemy==2.0.36 100 | stack-data==0.6.3 101 | starlette==0.41.3 102 | tenacity==9.0.0 103 | termcolor==2.5.0 104 | tiktoken==0.8.0 105 | tomlkit==0.13.2 106 | tornado==6.4.2 107 | tqdm==4.67.1 108 | traitlets==5.14.3 109 | typer==0.15.1 110 | typing-inspect==0.9.0 111 | typing_extensions==4.12.2 112 | tzdata==2024.2 113 | urllib3==2.2.3 114 | uvicorn==0.34.0 115 | wcwidth==0.2.13 116 | websockets==14.1 117 | yarl==1.18.3 118 | --------------------------------------------------------------------------------