From db25e16fdee13fd29cbf7c999135cc39a3e474ba Mon Sep 17 00:00:00 2001 From: Cole Medin <47287758+coleam00@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:48:21 -0500 Subject: [PATCH] Ollama + OpenAI Swarm --- .gitignore | 1 + local-swarm-agent/.env.example | 6 + .../ai-news-complete-mock-data.sql | 89 ++++++++++++++ local-swarm-agent/ai-news-complete-tables.sql | 97 +++++++++++++++ local-swarm-agent/load_sql_data.py | 50 ++++++++ local-swarm-agent/requirements.txt | 53 +++++++++ local-swarm-agent/run.py | 96 +++++++++++++++ local-swarm-agent/sql_agents.py | 110 ++++++++++++++++++ 8 files changed, 502 insertions(+) create mode 100644 local-swarm-agent/.env.example create mode 100644 local-swarm-agent/ai-news-complete-mock-data.sql create mode 100644 local-swarm-agent/ai-news-complete-tables.sql create mode 100644 local-swarm-agent/load_sql_data.py create mode 100644 local-swarm-agent/requirements.txt create mode 100644 local-swarm-agent/run.py create mode 100644 local-swarm-agent/sql_agents.py diff --git a/.gitignore b/.gitignore index ae5af116..02618bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ credentials.json token.json node_modules venv +venv2 *.db \ No newline at end of file diff --git a/local-swarm-agent/.env.example b/local-swarm-agent/.env.example new file mode 100644 index 00000000..27b42519 --- /dev/null +++ b/local-swarm-agent/.env.example @@ -0,0 +1,6 @@ +# Rename this file to .env once you have filled in the below environment variables! + +# See all Ollama models you can use here with tool calling - +# https://ollama.com/search?c=tools +# A good default to go with here is a Qwen model +LLM_MODEL=qwen2.5-coder:7b \ No newline at end of file diff --git a/local-swarm-agent/ai-news-complete-mock-data.sql b/local-swarm-agent/ai-news-complete-mock-data.sql new file mode 100644 index 00000000..da754c33 --- /dev/null +++ b/local-swarm-agent/ai-news-complete-mock-data.sql @@ -0,0 +1,89 @@ +-- Insert mock data into rss_feeds table +INSERT OR IGNORE INTO rss_feeds (name, url, description, site_link, language) VALUES +('AI Daily', 'https://ai-daily.com/feed', 'Daily AI news and updates', 'https://ai-daily.com', 'en'), +('ML Weekly', 'https://mlweekly.com/rss', 'Weekly roundup of machine learning news', 'https://mlweekly.com', 'en'), +('IA Nouvelles', 'https://ia-nouvelles.fr/flux', 'Actualités sur l''intelligence artificielle en français', 'https://ia-nouvelles.fr', 'fr'), +('Data Science Digest', 'https://datasciencedigest.com/feed', 'Comprehensive coverage of data science topics', 'https://datasciencedigest.com', 'en'), +('AI Ethics Blog', 'https://aiethicsblog.org/rss', 'Exploring ethical implications of AI', 'https://aiethicsblog.org', 'en'); + +-- Insert mock data into categories table +INSERT OR IGNORE INTO categories (name, description) VALUES +('Machine Learning', 'News related to machine learning algorithms and techniques'), +('Natural Language Processing', 'Updates on NLP research and applications'), +('Computer Vision', 'Advancements in image and video processing using AI'), +('Ethics in AI', 'Discussions on ethical considerations in AI development and deployment'), +('Robotics', 'News about AI in robotics and automation'), +('AI in Healthcare', 'Applications of AI in medicine and healthcare'), +('Deep Learning', 'Focused on deep neural networks and related technologies'); + +-- Insert mock data into rss_items table +INSERT OR IGNORE INTO rss_items (rss_feed_id, title, link, description, content, published_date, author) VALUES +(1, 'New breakthrough in reinforcement learning', 'https://ai-daily.com/articles/reinforcement-learning-breakthrough', 'Researchers achieve significant progress in RL algorithms', 'Full content of the article...', '2023-04-15 09:30:00', 'Jane Doe'), +(2, 'GPT-4 shows impressive results in medical diagnosis', 'https://mlweekly.com/news/gpt4-medical-diagnosis', 'OpenAI''s latest language model demonstrates potential in healthcare', 'Detailed article content...', '2023-04-14 14:45:00', 'John Smith'), +(3, 'L''IA générative révolutionne la création artistique', 'https://ia-nouvelles.fr/articles/ia-generative-art', 'Comment l''IA transforme le processus créatif des artistes', 'Contenu complet de l''article...', '2023-04-13 11:15:00', 'Marie Dupont'), +(4, 'Advancements in Computer Vision for Autonomous Vehicles', 'https://datasciencedigest.com/articles/cv-autonomous-vehicles', 'Recent developments in CV improving self-driving car capabilities', 'Full article content...', '2023-04-16 10:00:00', 'Alex Johnson'), +(5, 'The Ethics of AI in Hiring Processes', 'https://aiethicsblog.org/posts/ai-in-hiring', 'Examining the implications of using AI for job candidate selection', 'Detailed blog post content...', '2023-04-17 13:20:00', 'Samantha Lee'); + +-- Insert mock data into rss_item_categories junction table +INSERT OR IGNORE INTO rss_item_categories (rss_item_id, category_id) VALUES +(1, 1), (1, 7), -- Reinforcement learning article tagged with Machine Learning and Deep Learning +(2, 2), (2, 6), -- GPT-4 article tagged with NLP and AI in Healthcare +(3, 1), (3, 4), -- Generative AI article tagged with Machine Learning and Ethics in AI +(4, 3), (4, 5), -- Computer Vision article tagged with Computer Vision and Robotics +(5, 4), (5, 6); -- AI Ethics article tagged with Ethics in AI and AI in Healthcare + +-- Insert mock data into users table +INSERT OR IGNORE INTO users (username, email, password_hash, created_at, last_login) VALUES +('alice_ai', 'alice@example.com', 'hashed_password_1', '2023-01-01 10:00:00', '2023-04-15 14:30:00'), +('bob_ml', 'bob@example.com', 'hashed_password_2', '2023-02-15 11:30:00', '2023-04-14 09:15:00'), +('charlie_nlp', 'charlie@example.com', 'hashed_password_3', '2023-03-20 09:45:00', '2023-04-13 16:45:00'), +('dana_cv', 'dana@example.com', 'hashed_password_4', '2023-03-25 14:00:00', '2023-04-16 11:30:00'), +('evan_ethics', 'evan@example.com', 'hashed_password_5', '2023-04-01 08:30:00', '2023-04-17 10:45:00'); + +-- Insert mock data into user_category_preferences table +INSERT OR IGNORE INTO user_category_preferences (user_id, category_id) VALUES +(1, 1), (1, 2), (1, 7), -- Alice is interested in Machine Learning, NLP, and Deep Learning +(2, 1), (2, 3), (2, 5), -- Bob is interested in Machine Learning, Computer Vision, and Robotics +(3, 2), (3, 4), (3, 6), -- Charlie is interested in NLP, Ethics in AI, and AI in Healthcare +(4, 3), (4, 5), (4, 7), -- Dana is interested in Computer Vision, Robotics, and Deep Learning +(5, 4), (5, 6), (5, 1); -- Evan is interested in Ethics in AI, AI in Healthcare, and Machine Learning + +-- Insert mock data into user_feed_preferences table +INSERT OR IGNORE INTO user_feed_preferences (user_id, rss_feed_id) VALUES +(1, 1), (1, 2), (1, 4), -- Alice follows AI Daily, ML Weekly, and Data Science Digest +(2, 2), (2, 3), (2, 4), -- Bob follows ML Weekly, IA Nouvelles, and Data Science Digest +(3, 1), (3, 3), (3, 5), -- Charlie follows AI Daily, IA Nouvelles, and AI Ethics Blog +(4, 1), (4, 2), (4, 4), -- Dana follows AI Daily, ML Weekly, and Data Science Digest +(5, 3), (5, 4), (5, 5); -- Evan follows IA Nouvelles, Data Science Digest, and AI Ethics Blog + +-- Insert mock data into article_interactions table +INSERT OR IGNORE INTO article_interactions (user_id, rss_item_id, interaction_type, interaction_time) VALUES +(1, 1, 'view', '2023-04-15 10:15:00'), +(1, 1, 'like', '2023-04-15 10:20:00'), +(1, 2, 'view', '2023-04-15 10:30:00'), +(2, 2, 'view', '2023-04-14 15:00:00'), +(2, 2, 'like', '2023-04-14 15:05:00'), +(2, 2, 'share', '2023-04-14 15:10:00'), +(3, 3, 'view', '2023-04-13 17:00:00'), +(3, 3, 'like', '2023-04-13 17:10:00'), +(4, 4, 'view', '2023-04-16 11:45:00'), +(4, 4, 'share', '2023-04-16 11:50:00'), +(5, 5, 'view', '2023-04-17 14:00:00'), +(5, 5, 'like', '2023-04-17 14:15:00'); + +-- Insert mock data into feed_views table +INSERT OR IGNORE INTO feed_views (user_id, rss_feed_id, viewed_at) VALUES +(1, 1, '2023-04-15 10:00:00'), +(1, 2, '2023-04-15 10:25:00'), +(2, 2, '2023-04-14 14:55:00'), +(3, 3, '2023-04-13 16:50:00'), +(4, 4, '2023-04-16 11:40:00'), +(5, 5, '2023-04-17 13:55:00'); + +-- Insert mock data into user_sessions table +INSERT OR IGNORE INTO user_sessions (user_id, session_token, created_at, expires_at) VALUES +(1, 'token_alice_1', '2023-04-15 14:30:00', '2023-04-16 14:30:00'), +(2, 'token_bob_1', '2023-04-14 09:15:00', '2023-04-15 09:15:00'), +(3, 'token_charlie_1', '2023-04-13 16:45:00', '2023-04-14 16:45:00'), +(4, 'token_dana_1', '2023-04-16 11:30:00', '2023-04-17 11:30:00'), +(5, 'token_evan_1', '2023-04-17 10:45:00', '2023-04-18 10:45:00'); diff --git a/local-swarm-agent/ai-news-complete-tables.sql b/local-swarm-agent/ai-news-complete-tables.sql new file mode 100644 index 00000000..1d185d21 --- /dev/null +++ b/local-swarm-agent/ai-news-complete-tables.sql @@ -0,0 +1,97 @@ +-- Create the RSS feeds table +CREATE TABLE IF NOT EXISTS rss_feeds ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL UNIQUE, + description TEXT, + site_link VARCHAR(255), + language VARCHAR(50) +); + +-- Create the categories table +CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL UNIQUE, + description TEXT +); + +-- Create the RSS news feed items table +CREATE TABLE IF NOT EXISTS rss_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + rss_feed_id INTEGER NOT NULL, + title VARCHAR(255) NOT NULL, + link VARCHAR(255) NOT NULL, + description TEXT, + content TEXT, + published_date DATETIME, + author VARCHAR(255), + FOREIGN KEY (rss_feed_id) REFERENCES rss_feeds(id) +); + +-- Create a junction table for the many-to-many relationship between rss_items and categories +CREATE TABLE IF NOT EXISTS rss_item_categories ( + rss_item_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + PRIMARY KEY (rss_item_id, category_id), + FOREIGN KEY (rss_item_id) REFERENCES rss_items(id), + FOREIGN KEY (category_id) REFERENCES categories(id) +); + +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + last_login DATETIME +); + +-- Create user preferences table for categories +CREATE TABLE IF NOT EXISTS user_category_preferences ( + user_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + PRIMARY KEY (user_id, category_id), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (category_id) REFERENCES categories(id) +); + +-- Create user preferences table for RSS feeds +CREATE TABLE IF NOT EXISTS user_feed_preferences ( + user_id INTEGER NOT NULL, + rss_feed_id INTEGER NOT NULL, + PRIMARY KEY (user_id, rss_feed_id), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (rss_feed_id) REFERENCES rss_feeds(id) +); + +-- Create table for tracking article interactions +CREATE TABLE IF NOT EXISTS article_interactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + rss_item_id INTEGER NOT NULL, + interaction_type VARCHAR(20) NOT NULL, -- 'view', 'like', 'share', etc. + interaction_time DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (rss_item_id) REFERENCES rss_items(id) +); + +-- Create table for tracking feed views +CREATE TABLE IF NOT EXISTS feed_views ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + rss_feed_id INTEGER NOT NULL, + viewed_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (rss_feed_id) REFERENCES rss_feeds(id) +); + +-- Create table for user sessions +CREATE TABLE IF NOT EXISTS user_sessions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + session_token VARCHAR(255) NOT NULL UNIQUE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + expires_at DATETIME NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) +); diff --git a/local-swarm-agent/load_sql_data.py b/local-swarm-agent/load_sql_data.py new file mode 100644 index 00000000..ea6eaf82 --- /dev/null +++ b/local-swarm-agent/load_sql_data.py @@ -0,0 +1,50 @@ +import sqlite3 + +""" +This script is used to create a SQLlite database, add tables +to it, and insert mock data, all for the OpenAI Swarm demonstration +with the other Python script in this directory. + +Simply run this script with the command: + +python load_sql_data.py + +And then you will have a database loaded and ready to use +with the agent swarm! +""" + +def execute_sql_script(cursor, script_file): + # Opens the .sql file given as script_file + with open(script_file, 'r') as sql_file: + sql_script = sql_file.read() + + # Gets all the sql commands and executes them one at a time + statements = sql_script.split(';') + for statement in statements: + if statement.strip(): + cursor.execute(statement) + +def main(): + # Connect to the database + conn = sqlite3.connect('rss-feed-database.db') + cursor = conn.cursor() + + # Execute SQL script to create tables for the AI RSS Feed system + execute_sql_script(cursor, 'ai-news-complete-tables.sql') + conn.commit() + + # Execute SQL script to insert mock data for the AI RSS Feed system + execute_sql_script(cursor, 'ai-news-complete-mock-data.sql') + conn.commit() + + # Query table to make sure things are looking good + cursor.execute("SELECT * FROM rss_feeds") + feeds = cursor.fetchall() + for feed in feeds: + print(feed) + + # Close the connection + conn.close() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/local-swarm-agent/requirements.txt b/local-swarm-agent/requirements.txt new file mode 100644 index 00000000..74ffd6cb --- /dev/null +++ b/local-swarm-agent/requirements.txt @@ -0,0 +1,53 @@ +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +certifi==2024.8.30 +cfgv==3.4.0 +charset-normalizer==3.4.0 +click==8.1.7 +colorama==0.4.6 +distlib==0.3.9 +distro==1.9.0 +docstring_parser==0.16 +filelock==3.16.1 +frozenlist==1.4.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +identify==2.6.1 +idna==3.10 +iniconfig==2.0.0 +instructor==1.5.2 +jiter==0.5.0 +markdown-it-py==3.0.0 +mdurl==0.1.2 +multidict==6.1.0 +nodeenv==1.9.1 +numpy==2.1.2 +openai==1.51.2 +packaging==24.1 +platformdirs==4.3.6 +pluggy==1.5.0 +pre_commit==4.0.1 +propcache==0.2.0 +pydantic==2.9.2 +pydantic_core==2.23.4 +Pygments==2.18.0 +pytest==8.3.3 +python-dotenv==1.0.1 +PyYAML==6.0.2 +requests==2.32.3 +rich==13.9.2 +shellingham==1.5.4 +sniffio==1.3.1 +swarm @ git+https://github.com/openai/swarm.git +tenacity==8.5.0 +tqdm==4.66.5 +typer==0.12.5 +typing_extensions==4.12.2 +urllib3==2.2.3 +virtualenv==20.26.6 +yarl==1.15.2 diff --git a/local-swarm-agent/run.py b/local-swarm-agent/run.py new file mode 100644 index 00000000..ce7f3544 --- /dev/null +++ b/local-swarm-agent/run.py @@ -0,0 +1,96 @@ +from dotenv import load_dotenv +from openai import OpenAI +from swarm import Swarm +import json + +from sql_agents import sql_router_agent + +ollama_client = OpenAI( + base_url="http://localhost:11434/v1", + api_key="ollama" +) + +def process_and_print_streaming_response(response): + content = "" + last_sender = "" + + for chunk in response: + if "sender" in chunk: + last_sender = chunk["sender"] + + if "content" in chunk and chunk["content"] is not None: + if not content and last_sender: + print(f"\033[94m{last_sender}:\033[0m", end=" ", flush=True) + last_sender = "" + print(chunk["content"], end="", flush=True) + content += chunk["content"] + + if "tool_calls" in chunk and chunk["tool_calls"] is not None: + for tool_call in chunk["tool_calls"]: + f = tool_call["function"] + name = f["name"] + if not name: + continue + print(f"\033[94m{last_sender}: \033[95m{name}\033[0m()") + + if "delim" in chunk and chunk["delim"] == "end" and content: + print() # End of response message + content = "" + + if "response" in chunk: + return chunk["response"] + + +def pretty_print_messages(messages) -> None: + for message in messages: + if message["role"] != "assistant": + continue + + # print agent name in blue + print(f"\033[94m{message['sender']}\033[0m:", end=" ") + + # print response, if any + if message["content"]: + print(message["content"]) + + # print tool calls in purple, if any + tool_calls = message.get("tool_calls") or [] + if len(tool_calls) > 1: + print() + for tool_call in tool_calls: + f = tool_call["function"] + name, args = f["name"], f["arguments"] + arg_str = json.dumps(json.loads(args)).replace(":", "=") + print(f"\033[95m{name}\033[0m({arg_str[1:-1]})") + +def run_demo_loop( + starting_agent, context_variables=None, stream=False, debug=False +) -> None: + client = Swarm(client=ollama_client) + print("Starting Ollama Swarm CLI 🐝") + + messages = [] + agent = starting_agent + + while True: + user_input = input("\033[90mUser\033[0m: ") + messages.append({"role": "user", "content": user_input}) + + response = client.run( + agent=agent, + messages=messages, + context_variables=context_variables or {}, + stream=stream, + debug=debug, + ) + + if stream: + response = process_and_print_streaming_response(response) + else: + pretty_print_messages(response.messages) + + messages.extend(response.messages) + agent = response.agent + +if __name__ == "__main__": + run_demo_loop(sql_router_agent) \ No newline at end of file diff --git a/local-swarm-agent/sql_agents.py b/local-swarm-agent/sql_agents.py new file mode 100644 index 00000000..4954b5be --- /dev/null +++ b/local-swarm-agent/sql_agents.py @@ -0,0 +1,110 @@ +from dotenv import load_dotenv +from swarm import Agent +import sqlite3 +import os + +load_dotenv() +model = os.getenv('LLM_MODEL', 'qwen2.5-coder:7b') + +conn = sqlite3.connect('rss-feed-database.db') +cursor = conn.cursor() + +with open("ai-news-complete-tables.sql", "r") as table_schema_file: + table_schemas = table_schema_file.read() + +def run_sql_select_statement(sql_statement): + """Executes a SQL SELECT statement and returns the results of running the SELECT. Make sure you have a full SQL SELECT query created before calling this function.""" + print(f"Executing SQL statement: {sql_statement}") + cursor.execute(sql_statement) + records = cursor.fetchall() + + if not records: + return "No results found." + + # Get column names + column_names = [description[0] for description in cursor.description] + + # Calculate column widths + col_widths = [len(name) for name in column_names] + for row in records: + for i, value in enumerate(row): + col_widths[i] = max(col_widths[i], len(str(value))) + + # Format the results + result_str = "" + + # Add header + header = " | ".join(name.ljust(width) for name, width in zip(column_names, col_widths)) + result_str += header + "\n" + result_str += "-" * len(header) + "\n" + + # Add rows + for row in records: + row_str = " | ".join(str(value).ljust(width) for value, width in zip(row, col_widths)) + result_str += row_str + "\n" + + return result_str + +def get_sql_router_agent_instructions(): + return """You are an orchestrator of different SQL data experts and it is your job to + determine which of the agent is best suited to handle the user's request, + and transfer the conversation to that agent.""" + +def get_sql_agent_instructions(): + return f"""You are a SQL expert who takes in a request from a user for information + they want to retrieve from the DB, creates a SELECT statement to retrieve the + necessary information, and then invoke the function to run the query and + get the results back to then report to the user the information they wanted to know. + + Here are the table schemas for the DB you can query: + + {table_schemas} + + Write all of your SQL SELECT statements to work 100% with these schemas and nothing else. + You are always willing to create and execute the SQL statements to answer the user's question. + """ + + +sql_router_agent = Agent( + name="Router Agent", + instructions=get_sql_router_agent_instructions(), + model="qwen2.5:3b" +) +rss_feed_agent = Agent( + name="RSS Feed Agent", + instructions=get_sql_agent_instructions() + "\n\nHelp the user with data related to RSS feeds. Be super enthusiastic about how many great RSS feeds there are in every one of your responses.", + functions=[run_sql_select_statement], + model=model +) +user_agent = Agent( + name="User Agent", + instructions=get_sql_agent_instructions() + "\n\nHelp the user with data related to users.", + functions=[run_sql_select_statement], + model=model +) +analytics_agent = Agent( + name="Analytics Agent", + instructions=get_sql_agent_instructions() + "\n\nHelp the user gain insights from the data with analytics. Be super accurate in reporting numbers and citing sources.", + functions=[run_sql_select_statement], + model=model +) + + +def transfer_back_to_router_agent(): + """Call this function if a user is asking about data that is not handled by the current agent.""" + return sql_router_agent + +def transfer_to_rss_feeds_agent(): + return rss_feed_agent + +def transfer_to_user_agent(): + return user_agent + +def transfer_to_analytics_agent(): + return analytics_agent + + +sql_router_agent.functions = [transfer_to_rss_feeds_agent, transfer_to_user_agent, transfer_to_analytics_agent] +rss_feed_agent.functions.append(transfer_back_to_router_agent) +user_agent.functions.append(transfer_back_to_router_agent) +analytics_agent.functions.append(transfer_back_to_router_agent) \ No newline at end of file