diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ + diff --git a/README.md b/README.md index 5d64132..aeb610f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Unwind AI + Unwind AI

@@ -16,89 +16,122 @@
# ๐ŸŒŸ Awesome LLM Apps -A curated collection of awesome LLM apps built with RAG and AI agents. This repository features LLM apps that use models from OpenAI, Anthropic, Google, and even open-source models like LLaMA that you can run locally on your computer. -Shubhamsaboo%2Fawesome-llm-apps | Trendshift +A curated collection of awesome LLM apps built with RAG and AI agents. This repository features LLM apps that use models from OpenAI, Anthropic, Google, and open-source models like DeepSeek, Qwen or Llama that you can run locally on your computer. -## ๐Ÿ“‘ Table of Contents - -- [๐Ÿค” Why Awesome LLM Apps?](#-why-awesome-llm-apps) -- [๐Ÿ“‚ Featured Projects](#-featured-projects) - - **AI Agents** - - [๐Ÿ’ผ AI Customer Support Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_customer_support_agent) - - [๐Ÿ“ˆ AI Investment Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_investment_agent) - - [๐Ÿ—ž๏ธ AI Journalist Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_journalist_agent) - - [๐Ÿ“Š AI Finance Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_finance_agent_team) - - [๐Ÿ’ฐ AI Personal Finance Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_personal_finance_agent) - - [๐Ÿ›ซ AI Travel Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_travel_agent) - - [๐ŸŽฌ AI Movie Production Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_movie_production_agent) - - [๐Ÿ“ฐ Multi-Agent AI Researcher](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/multi_agent_researcher) - - [๐Ÿ“‘ AI Meeting Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_meeting_agent) - - [๐ŸŒ Local News Agent OpenAI Swarm](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/local_news_agent_openai_swarm) - - **RAG (Retrieval Augmented Generation)** - - [๐Ÿ” Autonomous RAG](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/autonomous_rag) - - [๐Ÿ”— Agentic RAG](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/agentic_rag) - - [๐Ÿ”„ Llama3.1 Local RAG](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/llama3.1_local_rag) - - [๐Ÿงฉ RAG-as-a-Service](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/rag-as-a-service) - - **LLM Apps with Memory** - - [๐Ÿ’พ AI Arxiv Agent with Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory) - - [๐Ÿ“ LLM App with Personalized Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/llm_app_personalized_memory) - - [๐Ÿ›ฉ๏ธ AI Travel Agent with Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/ai_travel_agent_memory) - - [๐Ÿ—„๏ธ Local ChatGPT with Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/local_chatgpt_with_memory) - - **Chat with X** - - [๐Ÿ’ฌ Chat with GitHub Repo](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_github) - - [๐Ÿ“จ Chat with Gmail](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_gmail) - - [๐Ÿ“„ Chat with PDF](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_pdf) - - [๐Ÿ“š Chat with Research Papers](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_research_papers) - - [๐Ÿ“ Chat with Substack Newsletter](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_substack) - - [๐Ÿ“ฝ๏ธ Chat with YouTube Videos](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_youtube_videos) - - **LLM Finetuning** - - [๐ŸŒ Llama3.2 Finetuning](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_finetuning_tutorials/llama3.2_finetuning) - - **Advanced Tools and Frameworks** - - [๐Ÿงช Gemini Multimodal Chatbot](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/gemini_multimodal_chatbot) - - [๐Ÿ”„ Mixture of Agents](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/mixture_of_agents) - - [๐ŸŒ MultiLLM Chat Playground](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/multillm_chat_playground) - - [๐Ÿ”— LLM Router App](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/llm_router_app) - - [๐Ÿ’ฌ Local ChatGPT Clone](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/local_chatgpt_clone) - - [๐ŸŒ Web Scraping AI Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/web_scrapping_ai_agent) - - [๐Ÿ” Web Search AI Assistant](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/web_search_ai_assistant) - - [๐Ÿงช Cursor AI Experiments](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/cursor_ai_experiments) - -- [๐Ÿš€ Getting Started](#-getting-started) -- [๐Ÿค Contributing to Opensource](#-contributing-to-opensource) +

+ + Shubhamsaboo%2Fawesome-llm-apps | Trendshift + +

## ๐Ÿค” Why Awesome LLM Apps? + - ๐Ÿ’ก Discover practical and creative ways LLMs can be applied across different domains, from code repositories to email inboxes and more. - ๐Ÿ”ฅ Explore apps that combine LLMs from OpenAI, Anthropic, Gemini, and open-source alternatives with RAG and AI Agents. - ๐ŸŽ“ Learn from well-documented projects and contribute to the growing open-source ecosystem of LLM-powered applications. +## ๐Ÿ“‚ Featured AI Projects + +### AI Agents +- [๐Ÿ’ผ AI Customer Support Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_customer_support_agent) +- [๐Ÿ“ˆ AI Investment Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_investment_agent) +- [๐Ÿ‘จโ€โš–๏ธ AI Legal Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_legal_agent_team) +- [๐Ÿ’ผ AI Recruitment Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_recruitment_agent_team) +- [๐Ÿ‘จโ€๐Ÿ’ผ AI Services Agency](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_services_agency) +- [๐Ÿงฒ AI Competitor Intelligence Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_competitor_intelligence_agent_team) +- [๐Ÿ‹๏ธโ€โ™‚๏ธ AI Health & Fitness Planner Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_health_fitness_agent) +- [๐Ÿ“ˆ AI Startup Trend Analysis Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_startup_trend_analysis_agent) +- [๐Ÿ—ž๏ธ AI Journalist Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_journalist_agent) +- [๐Ÿ’ฒ AI Finance Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_finance_agent_team) +- [๐Ÿงฒ AI Competitor Intelligence Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_competitor_intelligence_agent_team) +- [๐ŸŽฏ AI Lead Generation Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_lead_generation_agent) +- [๐Ÿ’ฐ AI Personal Finance Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_personal_finance_agent) +- [๐Ÿฉป AI Medical Scan Diagnosis Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_medical_imaging_agent) +- [๐Ÿ‘จโ€๐Ÿซ AI Teaching Agent Team](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_teaching_agent_team) +- [๐Ÿ›ซ AI Travel Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_travel_agent) +- [๐ŸŽฌ AI Movie Production Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_movie_production_agent) +- [๐Ÿ“ฐ Multi-Agent AI Researcher](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/multi_agent_researcher) +- [๐Ÿ’ป Multimodal AI Coding Agent Team with o3-mini and Gemini](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_coding_agent_o3-mini) +- [๐Ÿ“‘ AI Meeting Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_meeting_agent) +- [โ™œ AI Chess Agent Game](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_chess_agent) +- [๐Ÿ  AI Real Estate Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_real_estate_agent) +- [๐ŸŒ Local News Agent OpenAI Swarm](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/local_news_agent_openai_swarm) +- [๐Ÿ“Š AI Finance Agent with xAI Grok](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/xai_finance_agent) +- [๐ŸŽฎ AI 3D PyGame Visualizer with DeepSeek R1](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_3dpygame_r1) +- [๐Ÿง  AI Reasoning Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/ai_reasoning_agent) +- [๐Ÿงฌ Multimodal AI Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/ai_agent_tutorials/multimodal_ai_agent) + +### RAG (Retrieval Augmented Generation) +- [๐Ÿ” Autonomous RAG](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/autonomous_rag) +- [๐Ÿ”— Agentic RAG](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/agentic_rag) +- [๐Ÿค” Agentic RAG with Gemini Flash Thinking](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/gemini_agentic_rag) +- [๐Ÿ‹ Deepseek Local RAG Reasoning Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/deepseek_local_rag_agent) +- [๐Ÿ”„ Llama3.1 Local RAG](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/llama3.1_local_rag) +- [๐Ÿงฉ RAG-as-a-Service](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/rag-as-a-service) +- [๐Ÿฆ™ Local RAG Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/local_rag_agent) +- [๐Ÿ‘€ RAG App with Hybrid Search](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/hybrid_search_rag) +- [๐Ÿ–ฅ๏ธ Local RAG App with Hybrid Search](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/local_hybrid_search_rag) +- [๐Ÿ“  RAG Agent with Database Routing](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/rag_database_routing) +- [๐Ÿ”„ Corrective RAG Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/rag_tutorials/corrective_rag) + +### MCP AI Agents +- [๐Ÿ™ MCP GitHub Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/mcp_ai_agents/github_mcp_agent) + +### LLM Apps with Memory +- [๐Ÿ’พ AI Arxiv Agent with Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory) +- [๐Ÿ“ LLM App with Personalized Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/llm_app_personalized_memory) +- [๐Ÿ›ฉ๏ธ AI Travel Agent with Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/ai_travel_agent_memory) +- [๐Ÿ—„๏ธ Local ChatGPT with Memory](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_apps_with_memory_tutorials/local_chatgpt_with_memory) + +### Chat with X +- [๐Ÿ’ฌ Chat with GitHub Repo](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_github) +- [๐Ÿ“จ Chat with Gmail](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_gmail) +- [๐Ÿ“„ Chat with PDF](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_pdf) +- [๐Ÿ“š Chat with Research Papers](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_research_papers) +- [๐Ÿ“ Chat with Substack Newsletter](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_substack) +- [๐Ÿ“ฝ๏ธ Chat with YouTube Videos](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/chat_with_X_tutorials/chat_with_youtube_videos) + +### LLM Finetuning +- [๐ŸŒ Llama3.2 Finetuning](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/llm_finetuning_tutorials/llama3.2_finetuning) + +### Advanced Tools and Frameworks +- [๐Ÿงช Gemini Multimodal Chatbot](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/gemini_multimodal_chatbot) +- [๐Ÿ”„ Mixture of Agents](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/mixture_of_agents) +- [๐ŸŒ MultiLLM Chat Playground](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/multillm_chat_playground) +- [๐Ÿ”— LLM Router App](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/llm_router_app) +- [๐Ÿ’ฌ Local ChatGPT Clone](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/local_chatgpt_clone) +- [๐ŸŒ Web Scraping AI Agent](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/web_scrapping_ai_agent) +- [๐Ÿ” Web Search AI Assistant](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/web_search_ai_assistant) +- [๐Ÿงช Cursor AI Experiments](https://github.com/Shubhamsaboo/awesome-llm-apps/tree/main/advanced_tools_frameworks/cursor_ai_experiments) ## ๐Ÿš€ Getting Started -1. Clone the repository +1. **Clone the repository** ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git ``` -2. Navigate to the desired project directory +2. **Navigate to the desired project directory** ```bash - cd awesome-llm-apps/chat_with_gmail + cd awesome-llm-apps/chat_with_X_tutorials/chat_with_gmail ``` -3. Install the required dependencies +3. **Install the required dependencies** ```bash pip install -r requirements.txt ``` -4. Follow the project-specific instructions in each project's README.md file to set up and run the app. +4. **Follow the project-specific instructions** in each project's `README.md` file to set up and run the app. -## ๐Ÿค Contributing to Opensource -Contributions are welcome! If you have any ideas, improvements, or new apps to add, please create a new [GitHub Issue](https://github.com/Shubhamsaboo/awesome-llm-apps/issues) or submit a pull request. Make sure to follow the existing project structure and include a detailed README.md for each new app. +## ๐Ÿค Contributing to Open Source -### Thank you community for the support ๐Ÿ™ +Contributions are welcome! If you have any ideas, improvements, or new apps to add, please create a new [GitHub Issue](https://github.com/Shubhamsaboo/awesome-llm-apps/issues) or submit a pull request. Make sure to follow the existing project structure and include a detailed `README.md` for each new app. + +### Thank You, Community, for the Support! ๐Ÿ™ [![Star History Chart](https://api.star-history.com/svg?repos=Shubhamsaboo/awesome-llm-apps&type=Date)](https://star-history.com/#Shubhamsaboo/awesome-llm-apps&Date) diff --git a/advanced_tools_frameworks/cursor_ai_experiments/multi_agent_researcher.py b/advanced_tools_frameworks/cursor_ai_experiments/multi_agent_researcher.py index 6d54dae..ba7e6e4 100644 --- a/advanced_tools_frameworks/cursor_ai_experiments/multi_agent_researcher.py +++ b/advanced_tools_frameworks/cursor_ai_experiments/multi_agent_researcher.py @@ -7,6 +7,17 @@ import os gpt4_model = None def create_article_crew(topic): + """Creates a team of agents to research, write, and edit an article on a given topic. + + This function sets up a crew consisting of three agents: a researcher, a writer, and an editor. + Each agent is assigned a specific task to ensure the production of a well-researched, + well-written, and polished article. The article is formatted using markdown standards. + + Args: + topic (str): The subject matter on which the article will be based. + + Returns: + Crew: A crew object that contains the agents and tasks necessary to complete the article.""" # Create agents researcher = Agent( role='Researcher', diff --git a/advanced_tools_frameworks/cursor_ai_experiments/requirements.txt b/advanced_tools_frameworks/cursor_ai_experiments/requirements.txt new file mode 100644 index 0000000..ee215a2 --- /dev/null +++ b/advanced_tools_frameworks/cursor_ai_experiments/requirements.txt @@ -0,0 +1,7 @@ +scrapegraphai +playwright +langchain-community +streamlit-chat +streamlit +crewai +ollama \ No newline at end of file diff --git a/advanced_tools_frameworks/gemini_multimodal_chatbot/README.md b/advanced_tools_frameworks/gemini_multimodal_chatbot/README.md index eef3674..b918907 100644 --- a/advanced_tools_frameworks/gemini_multimodal_chatbot/README.md +++ b/advanced_tools_frameworks/gemini_multimodal_chatbot/README.md @@ -12,6 +12,7 @@ This repository contains a Streamlit application that demonstrates a multimodal ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/advanced_tools_frameworks/gemini_multimodal_chatbot ``` 2. Install the required dependencies: diff --git a/advanced_tools_frameworks/llm_router_app/README.md b/advanced_tools_frameworks/llm_router_app/README.md index d2169ab..5a304df 100644 --- a/advanced_tools_frameworks/llm_router_app/README.md +++ b/advanced_tools_frameworks/llm_router_app/README.md @@ -16,6 +16,7 @@ This Streamlit application demonstrates the use of RouteLLM, a system that intel ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/advanced_tools_frameworks/llm_router_app ``` 2. Install the required dependencies: diff --git a/advanced_tools_frameworks/llm_router_app/requirements.txt b/advanced_tools_frameworks/llm_router_app/requirements.txt index a8f8745..e3defc0 100644 --- a/advanced_tools_frameworks/llm_router_app/requirements.txt +++ b/advanced_tools_frameworks/llm_router_app/requirements.txt @@ -1,2 +1,3 @@ streamlit -"routellm[serve,eval]" \ No newline at end of file +"routellm[serve,eval]" +routellm \ No newline at end of file diff --git a/advanced_tools_frameworks/local_chatgpt_clone/README.md b/advanced_tools_frameworks/local_chatgpt_clone/README.md index f3701b7..1aa5a0e 100644 --- a/advanced_tools_frameworks/local_chatgpt_clone/README.md +++ b/advanced_tools_frameworks/local_chatgpt_clone/README.md @@ -12,6 +12,7 @@ This project demonstrates how to build a ChatGPT clone using the Llama-3 model r ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/advanced_tools_frameworks/local_chatgpt_clone ``` 2. Install the required dependencies: diff --git a/advanced_tools_frameworks/local_llama3.1_tool_use/README.md b/advanced_tools_frameworks/local_llama3.1_tool_use/README.md index 19ef874..1c6ac2a 100644 --- a/advanced_tools_frameworks/local_llama3.1_tool_use/README.md +++ b/advanced_tools_frameworks/local_llama3.1_tool_use/README.md @@ -13,7 +13,7 @@ This Streamlit app demonstrates function calling with the local Llama3 model usi ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git -cd local-llama3-tool-use +cd awesome-llm-apps/advanced_tools_frameworks/local_llama3.1_tool_use ``` 2. Install the required dependencies: diff --git a/advanced_tools_frameworks/local_llama3.1_tool_use/llama3_tool_use.py b/advanced_tools_frameworks/local_llama3.1_tool_use/llama3_tool_use.py index f42872c..f52d356 100644 --- a/advanced_tools_frameworks/local_llama3.1_tool_use/llama3_tool_use.py +++ b/advanced_tools_frameworks/local_llama3.1_tool_use/llama3_tool_use.py @@ -1,9 +1,9 @@ import streamlit as st import os -from phi.assistant import Assistant -from phi.llm.ollama import Ollama -from phi.tools.yfinance import YFinanceTools -from phi.tools.serpapi_tools import SerpApiTools +from agno.agent import Agent +from agno.models.ollama import Ollama +from agno.tools.yfinance import YFinanceTools +from agno.tools.serpapi import SerpApiTools st.set_page_config(page_title="Llama-3 Tool Use", page_icon="๐Ÿฆ™") @@ -13,9 +13,21 @@ if 'SERPAPI_API_KEY' not in os.environ: st.stop() def get_assistant(tools): - return Assistant( + """Creates and returns a configured assistant agent. + + This function initializes an assistant agent with a specific model and toolset. + The assistant is capable of accessing tools selected by the user and includes + additional features such as showing tool call details, running in debug mode, + and appending the current datetime to its instructions. + + Args: + tools (list): A list of tools that the assistant can access. + + Returns: + Agent: A configured assistant agent with specified capabilities and settings.""" + return Agent( name="llama3_assistant", - llm=Ollama(model="llama3"), + model=Ollama(id="llama3.1:8b"), tools=tools, description="You are a helpful assistant that can access specific tools based on user selection.", show_tool_calls=True, @@ -25,7 +37,7 @@ def get_assistant(tools): ) -st.title("๐Ÿฆ™ Local Llama-3 Tool Use") +st.title("๐Ÿฆ™ Local Llama-3.1 Tool Use") st.markdown(""" This app demonstrates function calling with the local Llama3 model using Ollama. Select tools in the sidebar and ask relevant questions! diff --git a/advanced_tools_frameworks/local_llama3.1_tool_use/requirements.txt b/advanced_tools_frameworks/local_llama3.1_tool_use/requirements.txt index f3bea12..c103f25 100644 --- a/advanced_tools_frameworks/local_llama3.1_tool_use/requirements.txt +++ b/advanced_tools_frameworks/local_llama3.1_tool_use/requirements.txt @@ -1,3 +1,3 @@ streamlit ollama -phidata \ No newline at end of file +agno \ No newline at end of file diff --git a/advanced_tools_frameworks/web_scrapping_ai_agent/README.md b/advanced_tools_frameworks/web_scrapping_ai_agent/README.md index 4932fe4..c73dacc 100644 --- a/advanced_tools_frameworks/web_scrapping_ai_agent/README.md +++ b/advanced_tools_frameworks/web_scrapping_ai_agent/README.md @@ -12,6 +12,7 @@ This Streamlit app allows you to scrape a website using OpenAI API and the scrap ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/advanced_tools_frameworks/web_scrapping_ai_agent ``` 2. Install the required dependencies: diff --git a/advanced_tools_frameworks/web_search_ai_assistant/README.md b/advanced_tools_frameworks/web_search_ai_assistant/README.md index b31e0e7..24fee18 100644 --- a/advanced_tools_frameworks/web_search_ai_assistant/README.md +++ b/advanced_tools_frameworks/web_search_ai_assistant/README.md @@ -12,6 +12,7 @@ This Streamlit app combines the power of search engines and LLMs to provide you ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/advanced_tools_frameworks/web_search_ai_assistant ``` 2. Install the required dependencies: diff --git a/advanced_tools_frameworks/web_search_ai_assistant/claude_websearch.py b/advanced_tools_frameworks/web_search_ai_assistant/claude_websearch.py index 9d6a506..6dd9e63 100644 --- a/advanced_tools_frameworks/web_search_ai_assistant/claude_websearch.py +++ b/advanced_tools_frameworks/web_search_ai_assistant/claude_websearch.py @@ -1,8 +1,8 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.tools.duckduckgo import DuckDuckGo -from phi.llm.anthropic import Claude +from agno.agent import Agent +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.models.anthropic import Claude # Set up the Streamlit app st.title("Claude Sonnet + AI Web Search ๐Ÿค–") @@ -13,12 +13,12 @@ anthropic_api_key = st.text_input("Anthropic's Claude API Key", type="password") # If Anthropic API key is provided, create an instance of Assistant if anthropic_api_key: - assistant = Assistant( - llm=Claude( - model="claude-3-5-sonnet-20240620", + assistant = Agent( + model=Claude( + id="claude-3-5-sonnet-20240620", max_tokens=1024, - temperature=0.9, - api_key=anthropic_api_key) , tools=[DuckDuckGo()], show_tool_calls=True + temperature=0.3, + api_key=anthropic_api_key) , tools=[DuckDuckGoTools()], show_tool_calls=True ) # Get the search query from the user query= st.text_input("Enter the Search Query", type="default") @@ -26,4 +26,4 @@ if anthropic_api_key: if query: # Search the web using the AI Assistant response = assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/advanced_tools_frameworks/web_search_ai_assistant/gpt4_websearch.py b/advanced_tools_frameworks/web_search_ai_assistant/gpt4_websearch.py index aab2563..d805671 100644 --- a/advanced_tools_frameworks/web_search_ai_assistant/gpt4_websearch.py +++ b/advanced_tools_frameworks/web_search_ai_assistant/gpt4_websearch.py @@ -1,8 +1,8 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.tools.duckduckgo import DuckDuckGo -from phi.llm.openai import OpenAIChat +from agno.agent import Agent +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.models.openai import OpenAIChat # Set up the Streamlit app st.title("AI Web Search Assistant ๐Ÿค–") @@ -14,12 +14,12 @@ openai_access_token = st.text_input("OpenAI API Key", type="password") # If OpenAI API key is provided, create an instance of Assistant if openai_access_token: # Create an instance of the Assistant - assistant = Assistant( - llm=OpenAIChat( - model="gpt-4o", + assistant = Agent( + model=OpenAIChat( + id="gpt-4o", max_tokens=1024, temperature=0.9, - api_key=openai_access_token) , tools=[DuckDuckGo()], show_tool_calls=True + api_key=openai_access_token) , tools=[DuckDuckGoTools()], show_tool_calls=True ) # Get the search query from the user @@ -28,4 +28,4 @@ if openai_access_token: if query: # Search the web using the AI Assistant response = assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/advanced_tools_frameworks/web_search_ai_assistant/requirements.txt b/advanced_tools_frameworks/web_search_ai_assistant/requirements.txt index be21749..dd38cb3 100644 --- a/advanced_tools_frameworks/web_search_ai_assistant/requirements.txt +++ b/advanced_tools_frameworks/web_search_ai_assistant/requirements.txt @@ -1,4 +1,4 @@ streamlit openai -phidata +agno duckduckgo-search \ No newline at end of file diff --git a/ai_agent_tutorials/ai_3dpygame_r1/README.md b/ai_agent_tutorials/ai_3dpygame_r1/README.md new file mode 100644 index 0000000..d4e452c --- /dev/null +++ b/ai_agent_tutorials/ai_3dpygame_r1/README.md @@ -0,0 +1,50 @@ +# ๐ŸŽฎ AI 3D PyGame Visualizer with DeepSeek R1 +This Project demonstrates R1's code capabilities with a PyGame code generator and visualizer with browser use. The system uses DeepSeek for reasoning, OpenAI for code extraction, and browser automation agents to visualize the code on Trinket.io. + +### Features + +- Generates PyGame code from natural language descriptions +- Uses DeepSeek Reasoner for code logic and explanation +- Extracts clean code using OpenAI GPT-4o +- Automates code visualization on Trinket.io using browser agents +- Provides a streamlined Streamlit interface +- Multi-agent system for handling different tasks (navigation, coding, execution, viewing) + +### How to get Started? + +1. Clone the GitHub repository +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_3dpygame_r1 +``` + +2. Install the required dependencies: +```bash +pip install -r requirements.txt +``` + +3. Get your API Keys +- Sign up for [DeepSeek](https://platform.deepseek.com/) and obtain your API key +- Sign up for [OpenAI](https://platform.openai.com/) and obtain your API key + +4. Run the AI PyGame Visualizer +```bash +streamlit run ai_3dpygame_r1.py +``` + +5. Browser use automatically opens your web browser and navigate to the URL provided in the console output to interact with the PyGame generator. + +### How it works? + +1. **Query Processing:** User enters a natural language description of the desired PyGame visualization. +2. **Code Generation:** + - DeepSeek Reasoner analyzes the query and provides detailed reasoning with code + - OpenAI agent extracts clean, executable code from the reasoning +3. **Visualization:** + - Browser agents automate the process of running code on Trinket.io + - Multiple specialized agents handle different tasks: + - Navigation to Trinket.io + - Code input + - Execution + - Visualization viewing +4. **User Interface:** Streamlit provides an intuitive interface for entering queries, viewing code, and managing the visualization process. diff --git a/ai_agent_tutorials/ai_3dpygame_r1/ai_3dpygame_r1.py b/ai_agent_tutorials/ai_3dpygame_r1/ai_3dpygame_r1.py new file mode 100644 index 0000000..bea26d5 --- /dev/null +++ b/ai_agent_tutorials/ai_3dpygame_r1/ai_3dpygame_r1.py @@ -0,0 +1,173 @@ +import streamlit as st +from openai import OpenAI +from agno.agent import Agent as AgnoAgent +from agno.models.openai import OpenAIChat as AgnoOpenAIChat +from langchain_openai import ChatOpenAI +import asyncio +from browser_use import Browser + +st.set_page_config(page_title="PyGame Code Generator", layout="wide") + +# Initialize session state +if "api_keys" not in st.session_state: + st.session_state.api_keys = { + "deepseek": "", + "openai": "" + } + +# Streamlit sidebar for API keys +with st.sidebar: + st.title("API Keys Configuration") + st.session_state.api_keys["deepseek"] = st.text_input( + "DeepSeek API Key", + type="password", + value=st.session_state.api_keys["deepseek"] + ) + st.session_state.api_keys["openai"] = st.text_input( + "OpenAI API Key", + type="password", + value=st.session_state.api_keys["openai"] + ) + + st.markdown("---") + st.info(""" + ๐Ÿ“ How to use: + 1. Enter your API keys above + 2. Write your PyGame visualization query + 3. Click 'Generate Code' to get the code + 4. Click 'Generate Visualization' to: + - Open Trinket.io PyGame editor + - Copy and paste the generated code + - Watch it run automatically + """) + +# Main UI +st.title("๐ŸŽฎ AI 3D Visualizer with DeepSeek R1") +example_query = "Create a particle system simulation where 100 particles emit from the mouse position and respond to keyboard-controlled wind forces" +query = st.text_area( + "Enter your PyGame query:", + height=70, + placeholder=f"e.g.: {example_query}" +) + +# Split the buttons into columns +col1, col2 = st.columns(2) +generate_code_btn = col1.button("Generate Code") +generate_vis_btn = col2.button("Generate Visualization") + +if generate_code_btn and query: + if not st.session_state.api_keys["deepseek"] or not st.session_state.api_keys["openai"]: + st.error("Please provide both API keys in the sidebar") + st.stop() + + # Initialize Deepseek client + deepseek_client = OpenAI( + api_key=st.session_state.api_keys["deepseek"], + base_url="https://api.deepseek.com" + ) + + system_prompt = """You are a Pygame and Python Expert that specializes in making games and visualisation through pygame and python programming. + During your reasoning and thinking, include clear, concise, and well-formatted Python code in your reasoning. + Always include explanations for the code you provide.""" + + try: + # Get reasoning from Deepseek + with st.spinner("Generating solution..."): + deepseek_response = deepseek_client.chat.completions.create( + model="deepseek-reasoner", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": query} + ], + max_tokens=1 + ) + + reasoning_content = deepseek_response.choices[0].message.reasoning_content + print("\nDeepseek Reasoning:\n", reasoning_content) + with st.expander("R1's Reasoning"): + st.write(reasoning_content) + + # Initialize Claude agent (using PhiAgent) + openai_agent = AgnoAgent( + model=AgnoOpenAIChat( + id="gpt-4o", + api_key=st.session_state.api_keys["openai"] + ), + show_tool_calls=True, + markdown=True + ) + + # Extract code + extraction_prompt = f"""Extract ONLY the Python code from the following content which is reasoning of a particular query to make a pygame script. + Return nothing but the raw code without any explanations, or markdown backticks: + {reasoning_content}""" + + with st.spinner("Extracting code..."): + code_response = openai_agent.run(extraction_prompt) + extracted_code = code_response.content + + # Store the generated code in session state + st.session_state.generated_code = extracted_code + + # Display the code + with st.expander("Generated PyGame Code", expanded=True): + st.code(extracted_code, language="python") + + st.success("Code generated successfully! Click 'Generate Visualization' to run it.") + + except Exception as e: + st.error(f"An error occurred: {str(e)}") + +elif generate_vis_btn: + if "generated_code" not in st.session_state: + st.warning("Please generate code first before visualization") + else: + async def run_pygame_on_trinket(code: str) -> None: + browser = Browser() + from browser_use import Agent + async with await browser.new_context() as context: + model = ChatOpenAI( + model="gpt-4o", + api_key=st.session_state.api_keys["openai"] + ) + + agent1 = Agent( + task='Go to https://trinket.io/features/pygame, thats your only job.', + llm=model, + browser_context=context, + ) + + executor = Agent( + task='Executor. Execute the code written by the User by clicking on the run button on the right. ', + llm=model, + browser_context=context + ) + + coder = Agent( + task='Coder. Your job is to wait for the user for 10 seconds to write the code in the code editor.', + llm=model, + browser_context=context + ) + + viewer = Agent( + task='Viewer. Your job is to just view the pygame window for 10 seconds.', + llm=model, + browser_context=context, + ) + + with st.spinner("Running code on Trinket..."): + try: + await agent1.run() + await coder.run() + await executor.run() + await viewer.run() + st.success("Code is running on Trinket!") + except Exception as e: + st.error(f"Error running code on Trinket: {str(e)}") + st.info("You can still copy the code above and run it manually on Trinket") + + # Run the async function with the stored code + asyncio.run(run_pygame_on_trinket(st.session_state.generated_code)) + +elif generate_code_btn and not query: + st.warning("Please enter a query before generating code") \ No newline at end of file diff --git a/ai_agent_tutorials/ai_3dpygame_r1/requirements.txt b/ai_agent_tutorials/ai_3dpygame_r1/requirements.txt new file mode 100644 index 0000000..fe7ed8a --- /dev/null +++ b/ai_agent_tutorials/ai_3dpygame_r1/requirements.txt @@ -0,0 +1,4 @@ +agno +langchain-openai +browser-use +streamlit \ No newline at end of file diff --git a/ai_agent_tutorials/ai_aqi_analysis_agent/README.md b/ai_agent_tutorials/ai_aqi_analysis_agent/README.md new file mode 100644 index 0000000..cae38eb --- /dev/null +++ b/ai_agent_tutorials/ai_aqi_analysis_agent/README.md @@ -0,0 +1,81 @@ +# ๐ŸŒ AQI Analysis Agent + +The AQI Analysis Agent is a powerful air quality monitoring and health recommendation tool powered by Firecrawl and Agno's AI Agent framework. This app helps users make informed decisions about outdoor activities by analyzing real-time air quality data and providing personalized health recommendations. + +## Features + +- **Multi-Agent System** + - **AQI Analyzer**: Fetches and processes real-time air quality data + - **Health Recommendation Agent**: Generates personalized health advice + +- **Air Quality Metrics**: + - Overall Air Quality Index (AQI) + - Particulate Matter (PM2.5 and PM10) + - Carbon Monoxide (CO) levels + - Temperature + - Humidity + - Wind Speed + +- **Comprehensive Analysis**: + - Real-time data visualization + - Health impact assessment + - Activity safety recommendations + - Best time suggestions for outdoor activities + - Weather condition correlations + +- **Interactive Features**: + - Location-based analysis + - Medical condition considerations + - Activity-specific recommendations + - Downloadable reports + - Example queries for quick testing + +## How to Run + +Follow these steps to set up and run the application: + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_aqi_analysis_agent + ``` + +2. **Install the dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Set up your API keys**: + - Get an OpenAI API key from: https://platform.openai.com/api-keys + - Get a Firecrawl API key from: [Firecrawl website](https://www.firecrawl.dev/app/api-keys) + +4. **Run the Gradio app**: + ```bash + python ai_aqi_analysis_agent.py + ``` + +5. **Access the Web Interface**: + - The terminal will display two URLs: + - Local URL: `http://127.0.0.1:7860` (for local access) + - Public URL: `https://xxx-xxx-xxx.gradio.live` (for temporary public access) + - Click on either URL to open the web interface in your browser + +## Usage + +1. Enter your API keys in the API Configuration section +2. Input location details: + - City name + - State (optional for Union Territories/US cities) + - Country +3. Provide personal information: + - Medical conditions (optional) + - Planned outdoor activity +4. Click "Analyze & Get Recommendations" to receive: + - Current air quality data + - Health impact analysis + - Activity safety recommendations +5. Try the example queries for quick testing + +## Note + +The air quality data is fetched using Firecrawl's web scraping capabilities. Due to caching and rate limiting, the data might not always match real-time values on the website. For the most accurate real-time data, consider checking the source website directly. diff --git a/ai_agent_tutorials/ai_aqi_analysis_agent/ai_aqi_analysis_agent_gradio.py b/ai_agent_tutorials/ai_aqi_analysis_agent/ai_aqi_analysis_agent_gradio.py new file mode 100644 index 0000000..609d027 --- /dev/null +++ b/ai_agent_tutorials/ai_aqi_analysis_agent/ai_aqi_analysis_agent_gradio.py @@ -0,0 +1,272 @@ +from typing import Dict, Optional +from dataclasses import dataclass +from pydantic import BaseModel, Field +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from firecrawl import FirecrawlApp +import gradio as gr +import json + +class AQIResponse(BaseModel): + success: bool + data: Dict[str, float] + status: str + expiresAt: str + +class ExtractSchema(BaseModel): + aqi: float = Field(description="Air Quality Index") + temperature: float = Field(description="Temperature in degrees Celsius") + humidity: float = Field(description="Humidity percentage") + wind_speed: float = Field(description="Wind speed in kilometers per hour") + pm25: float = Field(description="Particulate Matter 2.5 micrometers") + pm10: float = Field(description="Particulate Matter 10 micrometers") + co: float = Field(description="Carbon Monoxide level") + +@dataclass +class UserInput: + city: str + state: str + country: str + medical_conditions: Optional[str] + planned_activity: str + +class AQIAnalyzer: + + def __init__(self, firecrawl_key: str) -> None: + self.firecrawl = FirecrawlApp(api_key=firecrawl_key) + + def _format_url(self, country: str, state: str, city: str) -> str: + """Format URL based on location, handling cases with and without state""" + country_clean = country.lower().replace(' ', '-') + city_clean = city.lower().replace(' ', '-') + + if not state or state.lower() == 'none': + return f"https://www.aqi.in/dashboard/{country_clean}/{city_clean}" + + state_clean = state.lower().replace(' ', '-') + return f"https://www.aqi.in/dashboard/{country_clean}/{state_clean}/{city_clean}" + + def fetch_aqi_data(self, city: str, state: str, country: str) -> tuple[Dict[str, float], str]: + """Fetch AQI data using Firecrawl""" + try: + url = self._format_url(country, state, city) + info_msg = f"Accessing URL: {url}" + + response = self.firecrawl.extract( + urls=[f"{url}/*"], + params={ + 'prompt': 'Extract the current real-time AQI, temperature, humidity, wind speed, PM2.5, PM10, and CO levels from the page. Also extract the timestamp of the data.', + 'schema': ExtractSchema.model_json_schema() + } + ) + + aqi_response = AQIResponse(**response) + if not aqi_response.success: + raise ValueError(f"Failed to fetch AQI data: {aqi_response.status}") + + return aqi_response.data, info_msg + + except Exception as e: + error_msg = f"Error fetching AQI data: {str(e)}" + return { + 'aqi': 0, + 'temperature': 0, + 'humidity': 0, + 'wind_speed': 0, + 'pm25': 0, + 'pm10': 0, + 'co': 0 + }, error_msg + +class HealthRecommendationAgent: + + def __init__(self, openai_key: str) -> None: + self.agent = Agent( + model=OpenAIChat( + id="gpt-4o", + name="Health Recommendation Agent", + api_key=openai_key + ) + ) + + def get_recommendations( + self, + aqi_data: Dict[str, float], + user_input: UserInput + ) -> str: + prompt = self._create_prompt(aqi_data, user_input) + response = self.agent.run(prompt) + return response.content + + def _create_prompt(self, aqi_data: Dict[str, float], user_input: UserInput) -> str: + return f""" + Based on the following air quality conditions in {user_input.city}, {user_input.state}, {user_input.country}: + - Overall AQI: {aqi_data['aqi']} + - PM2.5 Level: {aqi_data['pm25']} ยตg/mยณ + - PM10 Level: {aqi_data['pm10']} ยตg/mยณ + - CO Level: {aqi_data['co']} ppb + + Weather conditions: + - Temperature: {aqi_data['temperature']}ยฐC + - Humidity: {aqi_data['humidity']}% + - Wind Speed: {aqi_data['wind_speed']} km/h + + User's Context: + - Medical Conditions: {user_input.medical_conditions or 'None'} + - Planned Activity: {user_input.planned_activity} + **Comprehensive Health Recommendations:** + 1. **Impact of Current Air Quality on Health:** + 2. **Necessary Safety Precautions for Planned Activity:** + 3. **Advisability of Planned Activity:** + 4. **Best Time to Conduct the Activity:** + """ + +def analyze_conditions( + city: str, + state: str, + country: str, + medical_conditions: str, + planned_activity: str, + firecrawl_key: str, + openai_key: str +) -> tuple[str, str, str, str]: + """Analyze conditions and return AQI data, recommendations, and status messages""" + try: + # Initialize analyzers + aqi_analyzer = AQIAnalyzer(firecrawl_key=firecrawl_key) + health_agent = HealthRecommendationAgent(openai_key=openai_key) + + # Create user input + user_input = UserInput( + city=city, + state=state, + country=country, + medical_conditions=medical_conditions, + planned_activity=planned_activity + ) + + # Get AQI data + aqi_data, info_msg = aqi_analyzer.fetch_aqi_data( + city=user_input.city, + state=user_input.state, + country=user_input.country + ) + + # Format AQI data for display + aqi_json = json.dumps({ + "Air Quality Index (AQI)": aqi_data['aqi'], + "PM2.5": f"{aqi_data['pm25']} ยตg/mยณ", + "PM10": f"{aqi_data['pm10']} ยตg/mยณ", + "Carbon Monoxide (CO)": f"{aqi_data['co']} ppb", + "Temperature": f"{aqi_data['temperature']}ยฐC", + "Humidity": f"{aqi_data['humidity']}%", + "Wind Speed": f"{aqi_data['wind_speed']} km/h" + }, indent=2) + + # Get recommendations + recommendations = health_agent.get_recommendations(aqi_data, user_input) + + warning_msg = """ + โš ๏ธ Note: The data shown may not match real-time values on the website. + This could be due to: + - Cached data in Firecrawl + - Rate limiting + - Website updates not being captured + + Consider refreshing or checking the website directly for real-time values. + """ + + return aqi_json, recommendations, info_msg, warning_msg + + except Exception as e: + error_msg = f"Error occurred: {str(e)}" + return "", "Analysis failed", error_msg, "" + +def create_demo() -> gr.Blocks: + """Create and configure the Gradio interface""" + with gr.Blocks(title="AQI Analysis Agent") as demo: + gr.Markdown( + """ + # ๐ŸŒ AQI Analysis Agent + Get personalized health recommendations based on air quality conditions. + """ + ) + + # API Configuration + with gr.Accordion("API Configuration", open=False): + firecrawl_key = gr.Textbox( + label="Firecrawl API Key", + type="password", + placeholder="Enter your Firecrawl API key" + ) + openai_key = gr.Textbox( + label="OpenAI API Key", + type="password", + placeholder="Enter your OpenAI API key" + ) + + # Location Details + with gr.Row(): + with gr.Column(): + city = gr.Textbox(label="City", placeholder="e.g., Mumbai") + state = gr.Textbox( + label="State", + placeholder="Leave blank for Union Territories or US cities", + value="" + ) + country = gr.Textbox(label="Country", value="India") + + # Personal Details + with gr.Row(): + with gr.Column(): + medical_conditions = gr.Textbox( + label="Medical Conditions (optional)", + placeholder="e.g., asthma, allergies", + lines=2 + ) + planned_activity = gr.Textbox( + label="Planned Activity", + placeholder="e.g., morning jog for 2 hours", + lines=2 + ) + + # Status Messages + info_box = gr.Textbox(label="โ„น๏ธ Status", interactive=False) + warning_box = gr.Textbox(label="โš ๏ธ Warning", interactive=False) + + # Output Areas + aqi_data_json = gr.JSON(label="๐Ÿ“Š Current Air Quality Data") + recommendations = gr.Markdown(label="๐Ÿฅ Health Recommendations") + + # Analyze Button + analyze_btn = gr.Button("๐Ÿ” Analyze & Get Recommendations", variant="primary") + analyze_btn.click( + fn=analyze_conditions, + inputs=[ + city, + state, + country, + medical_conditions, + planned_activity, + firecrawl_key, + openai_key + ], + outputs=[aqi_data_json, recommendations, info_box, warning_box] + ) + + # Examples + gr.Examples( + examples=[ + ["Mumbai", "Maharashtra", "India", "asthma", "morning walk for 30 minutes"], + ["Delhi", "", "India", "", "outdoor yoga session"], + ["New York", "", "United States", "allergies", "afternoon run"], + ["Kakinada", "Andhra Pradesh", "India", "none", "Tennis for 2 hours"] + ], + inputs=[city, state, country, medical_conditions, planned_activity] + ) + + return demo + +if __name__ == "__main__": + demo = create_demo() + demo.launch(share=True) diff --git a/ai_agent_tutorials/ai_aqi_analysis_agent/ai_aqi_analysis_agent_streamlit.py b/ai_agent_tutorials/ai_aqi_analysis_agent/ai_aqi_analysis_agent_streamlit.py new file mode 100644 index 0000000..b8acaeb --- /dev/null +++ b/ai_agent_tutorials/ai_aqi_analysis_agent/ai_aqi_analysis_agent_streamlit.py @@ -0,0 +1,265 @@ +from typing import Dict, Optional +from dataclasses import dataclass +from pydantic import BaseModel, Field +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from firecrawl import FirecrawlApp +import streamlit as st + +class AQIResponse(BaseModel): + success: bool + data: Dict[str, float] + status: str + expiresAt: str + +class ExtractSchema(BaseModel): + aqi: float = Field(description="Air Quality Index") + temperature: float = Field(description="Temperature in degrees Celsius") + humidity: float = Field(description="Humidity percentage") + wind_speed: float = Field(description="Wind speed in kilometers per hour") + pm25: float = Field(description="Particulate Matter 2.5 micrometers") + pm10: float = Field(description="Particulate Matter 10 micrometers") + co: float = Field(description="Carbon Monoxide level") + +@dataclass +class UserInput: + city: str + state: str + country: str + medical_conditions: Optional[str] + planned_activity: str + +class AQIAnalyzer: + + def __init__(self, firecrawl_key: str) -> None: + self.firecrawl = FirecrawlApp(api_key=firecrawl_key) + + def _format_url(self, country: str, state: str, city: str) -> str: + """Format URL based on location, handling cases with and without state""" + country_clean = country.lower().replace(' ', '-') + city_clean = city.lower().replace(' ', '-') + + if not state or state.lower() == 'none': + return f"https://www.aqi.in/dashboard/{country_clean}/{city_clean}" + + state_clean = state.lower().replace(' ', '-') + return f"https://www.aqi.in/dashboard/{country_clean}/{state_clean}/{city_clean}" + + def fetch_aqi_data(self, city: str, state: str, country: str) -> Dict[str, float]: + """Fetch AQI data using Firecrawl""" + try: + url = self._format_url(country, state, city) + st.info(f"Accessing URL: {url}") # Display URL being accessed + + response = self.firecrawl.extract( + urls=[f"{url}/*"], + params={ + 'prompt': 'Extract the current real-time AQI, temperature, humidity, wind speed, PM2.5, PM10, and CO levels from the page. Also extract the timestamp of the data.', + 'schema': ExtractSchema.model_json_schema() + } + ) + + aqi_response = AQIResponse(**response) + if not aqi_response.success: + raise ValueError(f"Failed to fetch AQI data: {aqi_response.status}") + + with st.expander("๐Ÿ“ฆ Raw AQI Data", expanded=True): + st.json({ + "url_accessed": url, + "timestamp": aqi_response.expiresAt, + "data": aqi_response.data + }) + + st.warning(""" + โš ๏ธ Note: The data shown may not match real-time values on the website. + This could be due to: + - Cached data in Firecrawl + - Rate limiting + - Website updates not being captured + + Consider refreshing or checking the website directly for real-time values. + """) + + return aqi_response.data + + except Exception as e: + st.error(f"Error fetching AQI data: {str(e)}") + return { + 'aqi': 0, + 'temperature': 0, + 'humidity': 0, + 'wind_speed': 0, + 'pm25': 0, + 'pm10': 0, + 'co': 0 + } + +class HealthRecommendationAgent: + + def __init__(self, openai_key: str) -> None: + self.agent = Agent( + model=OpenAIChat( + id="gpt-4o", + name="Health Recommendation Agent", + api_key=openai_key + ) + ) + + def get_recommendations( + self, + aqi_data: Dict[str, float], + user_input: UserInput + ) -> str: + prompt = self._create_prompt(aqi_data, user_input) + response = self.agent.run(prompt) + return response.content + + def _create_prompt(self, aqi_data: Dict[str, float], user_input: UserInput) -> str: + return f""" + Based on the following air quality conditions in {user_input.city}, {user_input.state}, {user_input.country}: + - Overall AQI: {aqi_data['aqi']} + - PM2.5 Level: {aqi_data['pm25']} ยตg/mยณ + - PM10 Level: {aqi_data['pm10']} ยตg/mยณ + - CO Level: {aqi_data['co']} ppb + + Weather conditions: + - Temperature: {aqi_data['temperature']}ยฐC + - Humidity: {aqi_data['humidity']}% + - Wind Speed: {aqi_data['wind_speed']} km/h + + User's Context: + - Medical Conditions: {user_input.medical_conditions or 'None'} + - Planned Activity: {user_input.planned_activity} + **Comprehensive Health Recommendations:** + 1. **Impact of Current Air Quality on Health:** + 2. **Necessary Safety Precautions for Planned Activity:** + 3. **Advisability of Planned Activity:** + 4. **Best Time to Conduct the Activity:** + """ + +def analyze_conditions( + user_input: UserInput, + api_keys: Dict[str, str] +) -> str: + aqi_analyzer = AQIAnalyzer(firecrawl_key=api_keys['firecrawl']) + health_agent = HealthRecommendationAgent(openai_key=api_keys['openai']) + + aqi_data = aqi_analyzer.fetch_aqi_data( + city=user_input.city, + state=user_input.state, + country=user_input.country + ) + + return health_agent.get_recommendations(aqi_data, user_input) + +def initialize_session_state(): + if 'api_keys' not in st.session_state: + st.session_state.api_keys = { + 'firecrawl': '', + 'openai': '' + } + +def setup_page(): + st.set_page_config( + page_title="AQI Analysis Agent", + page_icon="๐ŸŒ", + layout="wide" + ) + + st.title("๐ŸŒ AQI Analysis Agent") + st.info("Get personalized health recommendations based on air quality conditions.") + +def render_sidebar(): + """Render sidebar with API configuration""" + with st.sidebar: + st.header("๐Ÿ”‘ API Configuration") + + new_firecrawl_key = st.text_input( + "Firecrawl API Key", + type="password", + value=st.session_state.api_keys['firecrawl'], + help="Enter your Firecrawl API key" + ) + new_openai_key = st.text_input( + "OpenAI API Key", + type="password", + value=st.session_state.api_keys['openai'], + help="Enter your OpenAI API key" + ) + + if (new_firecrawl_key and new_openai_key and + (new_firecrawl_key != st.session_state.api_keys['firecrawl'] or + new_openai_key != st.session_state.api_keys['openai'])): + st.session_state.api_keys.update({ + 'firecrawl': new_firecrawl_key, + 'openai': new_openai_key + }) + st.success("โœ… API keys updated!") + +def render_main_content(): + st.header("๐Ÿ“ Location Details") + col1, col2 = st.columns(2) + + with col1: + city = st.text_input("City", placeholder="e.g., Mumbai") + state = st.text_input("State", placeholder="If it's a Union Territory or a city in the US, leave it blank") + country = st.text_input("Country", value="India", placeholder="United States") + + with col2: + st.header("๐Ÿ‘ค Personal Details") + medical_conditions = st.text_area( + "Medical Conditions (optional)", + placeholder="e.g., asthma, allergies" + ) + planned_activity = st.text_area( + "Planned Activity", + placeholder="e.g., morning jog for 2 hours" + ) + + return UserInput( + city=city, + state=state, + country=country, + medical_conditions=medical_conditions, + planned_activity=planned_activity + ) + +def main(): + """Main application entry point""" + initialize_session_state() + setup_page() + render_sidebar() + user_input = render_main_content() + + result = None + + if st.button("๐Ÿ” Analyze & Get Recommendations"): + if not all([user_input.city, user_input.planned_activity]): + st.error("Please fill in all required fields (state and medical conditions are optional)") + elif not all(st.session_state.api_keys.values()): + st.error("Please provide both API keys in the sidebar") + else: + try: + with st.spinner("๐Ÿ”„ Analyzing conditions..."): + result = analyze_conditions( + user_input=user_input, + api_keys=st.session_state.api_keys + ) + st.success("โœ… Analysis completed!") + + except Exception as e: + st.error(f"โŒ Error: {str(e)}") + + if result: + st.markdown("### ๐Ÿ“ฆ Recommendations") + st.markdown(result) + + st.download_button( + "๐Ÿ’พ Download Recommendations", + data=result, + file_name=f"aqi_recommendations_{user_input.city}_{user_input.state}.txt", + mime="text/plain" + ) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_aqi_analysis_agent/requirements.txt b/ai_agent_tutorials/ai_aqi_analysis_agent/requirements.txt new file mode 100644 index 0000000..034b309 --- /dev/null +++ b/ai_agent_tutorials/ai_aqi_analysis_agent/requirements.txt @@ -0,0 +1,6 @@ +agno +openai +firecrawl-py==1.9.0 +gradio==5.9.1 +pydantic +dataclasses diff --git a/ai_agent_tutorials/ai_chess_agent/README.md b/ai_agent_tutorials/ai_chess_agent/README.md new file mode 100644 index 0000000..731db80 --- /dev/null +++ b/ai_agent_tutorials/ai_chess_agent/README.md @@ -0,0 +1,46 @@ +# โ™œ Agent White vs Agent Black: Chess Game + +An advanced Chess game system where two AI agents play chess against each other using Autogen in a streamlit app. It is built with robust move validation and game state management. + +## Features + +### Multi-Agent Architecture +- Player White: OpenAI-powered strategic decision maker +- Player Black: OpenAI-powered tactical opponent +- Board Proxy: Validation agent for move legality and game state + +### Safety & Validation +- Robust move verification system +- Illegal move prevention +- Real-time board state monitoring +- Secure game progression control + +### Strategic Gameplay +- AI-powered position evaluation +- Deep tactical analysis +- Dynamic strategy adaptation +- Complete chess ruleset implementation + + +### How to get Started? + +1. Clone the GitHub repository + +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd ai_agent_tutorials/ai_chess_game +``` +2. Install the required dependencies: + +```bash +pip install -r requirements.txt +``` +3. Get your OpenAI API Key + +- Sign up for an [OpenAI account](https://platform.openai.com/) (or the LLM provider of your choice) and obtain your API key. + +4. Run the Streamlit App +```bash +streamlit run ai_chess_agent.py +``` + diff --git a/ai_agent_tutorials/ai_chess_agent/ai_chess_agent.py b/ai_agent_tutorials/ai_chess_agent/ai_chess_agent.py new file mode 100644 index 0000000..912d49b --- /dev/null +++ b/ai_agent_tutorials/ai_chess_agent/ai_chess_agent.py @@ -0,0 +1,249 @@ +import chess +import chess.svg +import streamlit as st +from autogen import ConversableAgent, register_function + +if "openai_api_key" not in st.session_state: + st.session_state.openai_api_key = None +if "board" not in st.session_state: + st.session_state.board = chess.Board() +if "made_move" not in st.session_state: + st.session_state.made_move = False +if "board_svg" not in st.session_state: + st.session_state.board_svg = None +if "move_history" not in st.session_state: + st.session_state.move_history = [] +if "max_turns" not in st.session_state: + st.session_state.max_turns = 5 + +st.sidebar.title("Chess Agent Configuration") +openai_api_key = st.sidebar.text_input("Enter your OpenAI API key:", type="password") +if openai_api_key: + st.session_state.openai_api_key = openai_api_key + st.sidebar.success("API key saved!") + +st.sidebar.info(""" +For a complete chess game with potential checkmate, it would take max_turns > 200 approximately. +However, this will consume significant API credits and a lot of time. +For demo purposes, using 5-10 turns is recommended. +""") + +max_turns_input = st.sidebar.number_input( + "Enter the number of turns (max_turns):", + min_value=1, + max_value=1000, + value=st.session_state.max_turns, + step=1 +) + +if max_turns_input: + st.session_state.max_turns = max_turns_input + st.sidebar.success(f"Max turns of total chess moves set to {st.session_state.max_turns}!") + +st.title("Chess with AutoGen Agents") + +def available_moves() -> str: + available_moves = [str(move) for move in st.session_state.board.legal_moves] + return "Available moves are: " + ",".join(available_moves) + +def execute_move(move: str) -> str: + try: + chess_move = chess.Move.from_uci(move) + if chess_move not in st.session_state.board.legal_moves: + return f"Invalid move: {move}. Please call available_moves() to see valid moves." + + # Update board state + st.session_state.board.push(chess_move) + st.session_state.made_move = True + + # Generate and store board visualization + board_svg = chess.svg.board(st.session_state.board, + arrows=[(chess_move.from_square, chess_move.to_square)], + fill={chess_move.from_square: "gray"}, + size=400) + st.session_state.board_svg = board_svg + st.session_state.move_history.append(board_svg) + + # Get piece information + moved_piece = st.session_state.board.piece_at(chess_move.to_square) + piece_unicode = moved_piece.unicode_symbol() + piece_type_name = chess.piece_name(moved_piece.piece_type) + piece_name = piece_type_name.capitalize() if piece_unicode.isupper() else piece_type_name + + # Generate move description + from_square = chess.SQUARE_NAMES[chess_move.from_square] + to_square = chess.SQUARE_NAMES[chess_move.to_square] + move_desc = f"Moved {piece_name} ({piece_unicode}) from {from_square} to {to_square}." + if st.session_state.board.is_checkmate(): + winner = 'White' if st.session_state.board.turn == chess.BLACK else 'Black' + move_desc += f"\nCheckmate! {winner} wins!" + elif st.session_state.board.is_stalemate(): + move_desc += "\nGame ended in stalemate!" + elif st.session_state.board.is_insufficient_material(): + move_desc += "\nGame ended - insufficient material to checkmate!" + elif st.session_state.board.is_check(): + move_desc += "\nCheck!" + + return move_desc + except ValueError: + return f"Invalid move format: {move}. Please use UCI format (e.g., 'e2e4')." + +def check_made_move(msg): + if st.session_state.made_move: + st.session_state.made_move = False + return True + else: + return False + +if st.session_state.openai_api_key: + try: + agent_white_config_list = [ + { + "model": "gpt-4o-mini", + "api_key": st.session_state.openai_api_key, + }, + ] + + agent_black_config_list = [ + { + "model": "gpt-4o-mini", + "api_key": st.session_state.openai_api_key, + }, + ] + + agent_white = ConversableAgent( + name="Agent_White", + system_message="You are a professional chess player and you play as white. " + "First call available_moves() first, to get list of legal available moves. " + "Then call execute_move(move) to make a move.", + llm_config={"config_list": agent_white_config_list, "cache_seed": None}, + ) + + agent_black = ConversableAgent( + name="Agent_Black", + system_message="You are a professional chess player and you play as black. " + "First call available_moves() first, to get list of legal available moves. " + "Then call execute_move(move) to make a move.", + llm_config={"config_list": agent_black_config_list, "cache_seed": None}, + ) + + game_master = ConversableAgent( + name="Game_Master", + llm_config=False, + is_termination_msg=check_made_move, + default_auto_reply="Please make a move.", + human_input_mode="NEVER", + ) + + register_function( + execute_move, + caller=agent_white, + executor=game_master, + name="execute_move", + description="Call this tool to make a move.", + ) + + register_function( + available_moves, + caller=agent_white, + executor=game_master, + name="available_moves", + description="Get legal moves.", + ) + + register_function( + execute_move, + caller=agent_black, + executor=game_master, + name="execute_move", + description="Call this tool to make a move.", + ) + + register_function( + available_moves, + caller=agent_black, + executor=game_master, + name="available_moves", + description="Get legal moves.", + ) + + agent_white.register_nested_chats( + trigger=agent_black, + chat_queue=[ + { + "sender": game_master, + "recipient": agent_white, + "summary_method": "last_msg", + } + ], + ) + + agent_black.register_nested_chats( + trigger=agent_white, + chat_queue=[ + { + "sender": game_master, + "recipient": agent_black, + "summary_method": "last_msg", + } + ], + ) + + st.info(""" +This chess game is played between two AG2 AI agents: +- **Agent White**: A GPT-4o-mini powered chess player controlling white pieces +- **Agent Black**: A GPT-4o-mini powered chess player controlling black pieces + +The game is managed by a **Game Master** that: +- Validates all moves +- Updates the chess board +- Manages turn-taking between players +- Provides legal move information +""") + + initial_board_svg = chess.svg.board(st.session_state.board, size=300) + st.subheader("Initial Board") + st.image(initial_board_svg) + + if st.button("Start Game"): + st.session_state.board.reset() + st.session_state.made_move = False + st.session_state.move_history = [] + st.session_state.board_svg = chess.svg.board(st.session_state.board, size=300) + st.info("The AI agents will now play against each other. Each agent will analyze the board, " + "request legal moves from the Game Master (proxy agent), and make strategic decisions.") + st.success("You can view the interaction between the agents in the terminal output, after the turns between agents end, you get view all the chess board moves displayed below!") + st.write("Game started! White's turn.") + + chat_result = agent_black.initiate_chat( + recipient=agent_white, + message="Let's play chess! You go first, its your move.", + max_turns=st.session_state.max_turns, + summary_method="reflection_with_llm" + ) + st.markdown(chat_result.summary) + + # Display the move history (boards for each move) + st.subheader("Move History") + for i, move_svg in enumerate(st.session_state.move_history): + # Determine which agent made the move + if i % 2 == 0: + move_by = "Agent White" # Even-indexed moves are by White + else: + move_by = "Agent Black" # Odd-indexed moves are by Black + + st.write(f"Move {i + 1} by {move_by}") + st.image(move_svg) + + if st.button("Reset Game"): + st.session_state.board.reset() + st.session_state.made_move = False + st.session_state.move_history = [] + st.session_state.board_svg = None + st.write("Game reset! Click 'Start Game' to begin a new game.") + + except Exception as e: + st.error(f"An error occurred: {e}. Please check your API key and try again.") + +else: + st.warning("Please enter your OpenAI API key in the sidebar to start the game.") \ No newline at end of file diff --git a/ai_agent_tutorials/ai_chess_agent/requirements.txt b/ai_agent_tutorials/ai_chess_agent/requirements.txt new file mode 100644 index 0000000..7f38e3d --- /dev/null +++ b/ai_agent_tutorials/ai_chess_agent/requirements.txt @@ -0,0 +1,5 @@ +streamlit +chess==1.11.1 +autogen==0.6.1 +cairosvg +pillow diff --git a/ai_agent_tutorials/ai_coding_agent_o3-mini/README.md b/ai_agent_tutorials/ai_coding_agent_o3-mini/README.md new file mode 100644 index 0000000..8e355e2 --- /dev/null +++ b/ai_agent_tutorials/ai_coding_agent_o3-mini/README.md @@ -0,0 +1,61 @@ +# ๐Ÿ’ป Multimodal AI Coding Agent Team with o3-mini and Gemini +An AI Powered Streamlit application that serves as your personal coding assistant, powered by multiple Agents built on the new o3-mini model. You can also upload an image of a coding problem or describe it in text, and the AI agent will analyze, generate an optimal solution, and execute it in a sandbox environment. + +## Features +#### Multi-Modal Problem Input +- Upload images of coding problems (supports PNG, JPG, JPEG) +- Type problems in natural language +- Automatic problem extraction from images +- Interactive problem processing + +#### Intelligent Code Generation +- Optimal solution generation with best time/space complexity +- Clean, documented Python code output +- Type hints and proper documentation +- Edge case handling + +#### Secure Code Execution +- Sandboxed code execution environment +- Real-time execution results +- Error handling and explanations +- 30-second execution timeout protection + +#### Multi-Agent Architecture +- Vision Agent (Gemini-2.0-flash) for image processing +- Coding Agent (OpenAI- o3-mini) for solution generation +- Execution Agent (OpenAI) for code running and result analysis +- E2B Sandbox for secure code execution + +## How to Run + +Follow the steps below to set up and run the application: +- Get an OpenAI API key from: https://platform.openai.com/ +- Get a Google (Gemini) API key from: https://makersuite.google.com/app/apikey +- Get an E2B API key from: https://e2b.dev/docs/getting-started/api-key + +1. **Clone the Repository** + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_coding_agent_o3-mini + ``` + +2. **Install the dependencies** + ```bash + pip install -r requirements.txt + ``` + +3. **Run the Streamlit app** + ```bash + streamlit run ai_coding_agent_o3.py + ``` + +4. **Configure API Keys** + - Enter your API keys in the sidebar + - All three keys (OpenAI, Gemini, E2B) are required for full functionality + +## Usage +1. Upload an image of a coding problem OR type your problem description +2. Click "Generate & Execute Solution" +3. View the generated solution with full documentation +4. See execution results and any generated files +5. Review any error messages or execution timeouts diff --git a/ai_agent_tutorials/ai_coding_agent_o3-mini/ai_coding_agent_o3.py b/ai_agent_tutorials/ai_coding_agent_o3-mini/ai_coding_agent_o3.py new file mode 100644 index 0000000..22c91e6 --- /dev/null +++ b/ai_agent_tutorials/ai_coding_agent_o3-mini/ai_coding_agent_o3.py @@ -0,0 +1,280 @@ +from typing import Optional, Dict, Any +import streamlit as st +from agno.agent import Agent, RunResponse +from agno.models.openai import OpenAIChat +from agno.models.google import Gemini +from e2b_code_interpreter import Sandbox +import os +from PIL import Image +from io import BytesIO +import base64 + +def initialize_session_state() -> None: + if 'openai_key' not in st.session_state: + st.session_state.openai_key = '' + if 'gemini_key' not in st.session_state: + st.session_state.gemini_key = '' + if 'e2b_key' not in st.session_state: + st.session_state.e2b_key = '' + if 'sandbox' not in st.session_state: + st.session_state.sandbox = None + +def setup_sidebar() -> None: + with st.sidebar: + st.title("API Configuration") + st.session_state.openai_key = st.text_input("OpenAI API Key", + value=st.session_state.openai_key, + type="password") + st.session_state.gemini_key = st.text_input("Gemini API Key", + value=st.session_state.gemini_key, + type="password") + st.session_state.e2b_key = st.text_input("E2B API Key", + value=st.session_state.e2b_key, + type="password") + +def create_agents() -> tuple[Agent, Agent, Agent]: + vision_agent = Agent( + model=Gemini(id="gemini-2.0-flash", api_key=st.session_state.gemini_key), + markdown=True, + ) + + coding_agent = Agent( + model=OpenAIChat( + id="o3-mini", + api_key=st.session_state.openai_key, + system_prompt="""You are an expert Python programmer. You will receive coding problems similar to LeetCode questions, + which may include problem statements, sample inputs, and examples. Your task is to: + 1. Analyze the problem carefully and Optimally with best possible time and space complexities. + 2. Write clean, efficient Python code to solve it + 3. Include proper documentation and type hints + 4. The code will be executed in an e2b sandbox environment + Please ensure your code is complete and handles edge cases appropriately.""" + ), + markdown=True + ) + + execution_agent = Agent( + model=OpenAIChat( + id="o3-mini", + api_key=st.session_state.openai_key, + system_prompt="""You are an expert at executing Python code in sandbox environments. + Your task is to: + 1. Take the provided Python code + 2. Execute it in the e2b sandbox + 3. Format and explain the results clearly + 4. Handle any execution errors gracefully + Always ensure proper error handling and clear output formatting.""" + ), + markdown=True + ) + + return vision_agent, coding_agent, execution_agent + +def initialize_sandbox() -> None: + try: + if st.session_state.sandbox: + try: + st.session_state.sandbox.close() + except: + pass + os.environ['E2B_API_KEY'] = st.session_state.e2b_key + # Initialize sandbox with 60 second timeout + st.session_state.sandbox = Sandbox(timeout=60) + except Exception as e: + st.error(f"Failed to initialize sandbox: {str(e)}") + st.session_state.sandbox = None + +def run_code_in_sandbox(code: str) -> Dict[str, Any]: + if not st.session_state.sandbox: + initialize_sandbox() + + execution = st.session_state.sandbox.run_code(code) + return { + "logs": execution.logs, + "files": st.session_state.sandbox.files.list("/") + } + +def process_image_with_gemini(vision_agent: Agent, image: Image) -> str: + prompt = """Analyze this image and extract any coding problem or code snippet shown. + Describe it in clear natural language, including any: + 1. Problem statement + 2. Input/output examples + 3. Constraints or requirements + Format it as a proper coding problem description.""" + + # Save image to a temporary file + temp_path = "temp_image.png" + try: + # Convert to RGB if needed + if image.mode != 'RGB': + image = image.convert('RGB') + image.save(temp_path, format="PNG") + + # Read the file and create image data + with open(temp_path, 'rb') as img_file: + img_bytes = img_file.read() + + # Pass image to Gemini + response = vision_agent.run( + prompt, + images=[{"filepath": temp_path}] # Use filepath instead of content + ) + return response.content + except Exception as e: + st.error(f"Error processing image: {str(e)}") + return "Failed to process the image. Please try again or use text input instead." + finally: + # Clean up temporary file + if os.path.exists(temp_path): + os.remove(temp_path) + +def execute_code_with_agent(execution_agent: Agent, code: str, sandbox: Sandbox) -> str: + try: + # Set timeout to 30 seconds for code execution + sandbox.set_timeout(30) + execution = sandbox.run_code(code) + + # Handle execution errors + if execution.error: + if "TimeoutException" in str(execution.error): + return "โš ๏ธ Execution Timeout: The code took too long to execute (>30 seconds). Please optimize your solution or try a smaller input." + + error_prompt = f"""The code execution resulted in an error: + Error: {execution.error} + + Please analyze the error and provide a clear explanation of what went wrong.""" + response = execution_agent.run(error_prompt) + return f"โš ๏ธ Execution Error:\n{response.content}" + + # Get files list safely + try: + files = sandbox.files.list("/") + except: + files = [] + + prompt = f"""Here is the code execution result: + Logs: {execution.logs} + Files: {str(files)} + + Please provide a clear explanation of the results and any outputs.""" + + response = execution_agent.run(prompt) + return response.content + except Exception as e: + # Reinitialize sandbox on error + try: + initialize_sandbox() + except: + pass + return f"โš ๏ธ Sandbox Error: {str(e)}" + +def main() -> None: + st.title("O3-Mini Coding Agent") + + # Add timeout info in sidebar + initialize_session_state() + setup_sidebar() + with st.sidebar: + st.info("โฑ๏ธ Code execution timeout: 30 seconds") + + # Check all required API keys + if not (st.session_state.openai_key and + st.session_state.gemini_key and + st.session_state.e2b_key): + st.warning("Please enter all required API keys in the sidebar.") + return + + vision_agent, coding_agent, execution_agent = create_agents() + + # Clean, single-column layout + uploaded_image = st.file_uploader( + "Upload an image of your coding problem (optional)", + type=['png', 'jpg', 'jpeg'] + ) + + if uploaded_image: + st.image(uploaded_image, caption="Uploaded Image", use_container_width=True) + + user_query = st.text_area( + "Or type your coding problem here:", + placeholder="Example: Write a function to find the sum of two numbers. Include sample input/output cases.", + height=100 + ) + + # Process button + if st.button("Generate & Execute Solution", type="primary"): + if uploaded_image and not user_query: + # Process image with Gemini + with st.spinner("Processing image..."): + try: + # Save uploaded file to temporary location + image = Image.open(uploaded_image) + extracted_query = process_image_with_gemini(vision_agent, image) + + if extracted_query.startswith("Failed to process"): + st.error(extracted_query) + return + + st.info("๐Ÿ“ Extracted Problem:") + st.write(extracted_query) + + # Pass extracted query to coding agent + with st.spinner("Generating solution..."): + response = coding_agent.run(extracted_query) + except Exception as e: + st.error(f"Error processing image: {str(e)}") + return + + elif user_query and not uploaded_image: + # Direct text input processing + with st.spinner("Generating solution..."): + response = coding_agent.run(user_query) + + elif user_query and uploaded_image: + st.error("Please use either image upload OR text input, not both.") + return + else: + st.warning("Please provide either an image or text description of your coding problem.") + return + + # Display and execute solution + if 'response' in locals(): + st.divider() + st.subheader("๐Ÿ’ป Solution") + + # Extract code from markdown response + code_blocks = response.content.split("```python") + if len(code_blocks) > 1: + code = code_blocks[1].split("```")[0].strip() + + # Display the code + st.code(code, language="python") + + # Execute code with execution agent + with st.spinner("Executing code..."): + # Always initialize a fresh sandbox for each execution + initialize_sandbox() + + if st.session_state.sandbox: + execution_results = execute_code_with_agent( + execution_agent, + code, + st.session_state.sandbox + ) + + # Display execution results + st.divider() + st.subheader("๐Ÿš€ Execution Results") + st.markdown(execution_results) + + # Try to display files if available + try: + files = st.session_state.sandbox.files.list("/") + if files: + st.markdown("๐Ÿ“ **Generated Files:**") + st.json(files) + except: + pass + +if __name__ == "__main__": + main() diff --git a/ai_agent_tutorials/ai_coding_agent_o3-mini/requirements.txt b/ai_agent_tutorials/ai_coding_agent_o3-mini/requirements.txt new file mode 100644 index 0000000..5b06e93 --- /dev/null +++ b/ai_agent_tutorials/ai_coding_agent_o3-mini/requirements.txt @@ -0,0 +1,4 @@ +streamlit +e2b-code-interpreter +agno +Pillow \ No newline at end of file diff --git a/ai_agent_tutorials/ai_competitor_intelligence_agent_team/README.md b/ai_agent_tutorials/ai_competitor_intelligence_agent_team/README.md new file mode 100644 index 0000000..e112f2c --- /dev/null +++ b/ai_agent_tutorials/ai_competitor_intelligence_agent_team/README.md @@ -0,0 +1,77 @@ +# ๐Ÿงฒ AI Competitor Intelligence Agent Team + +The AI Competitor Intelligence Agent Team is a powerful competitor analysis tool powered by Firecrawl and Agno's AI Agent framework. This app helps businesses analyze their competitors by extracting structured data from competitor websites and generating actionable insights using AI. + +## Features + +- **Multi-Agent System** + - **Firecrawl Agent**: Specializes in crawling and summarizing competitor websites + - **Analysis Agent**: Generates detailed competitive analysis reports + - **Comparison Agent**: Creates structured comparisons between competitors + +- **Competitor Discovery**: + - Finds similar companies using URL matching with Exa AI + - Discovers competitors based on business descriptions + - Automatically extracts relevant competitor URLs + +- **Comprehensive Analysis**: + - Provides structured analysis reports with: + - Market gaps and opportunities + - Competitor weaknesses + - Recommended features + - Pricing strategies + - Growth opportunities + - Actionable recommendations + +- **Interactive Analysis**: Users can input either their company URL or description for analysis + +## Requirements + +The application requires the following Python libraries: + +- `agno` +- `exa-py` +- `streamlit` +- `pandas` +- `firecrawl-py` + +You'll also need API keys for: +- OpenAI +- Firecrawl +- Exa + +## How to Run + +Follow these steps to set up and run the application: + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_competitors_analysis_team + ``` + +2. **Install the dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Set up your API keys**: + - Get an OpenAI API key from: https://platform.openai.com/api-keys + - Get a Firecrawl API key from: [Firecrawl website](https://www.firecrawl.dev/app/api-keys) + - Get an Exa API key from: [Exa website](https://dashboard.exa.ai/api-keys) + +4. **Run the Streamlit app**: + ```bash + streamlit run ai_competitor_analyser.py + ``` + +## Usage + +1. Enter your API keys in the sidebar +2. Input either: + - Your company's website URL + - A description of your company +3. Click "Analyze Competitors" to generate: + - Competitor comparison table + - Detailed analysis report + - Strategic recommendations diff --git a/ai_agent_tutorials/ai_competitor_intelligence_agent_team/competitor_agent_team.py b/ai_agent_tutorials/ai_competitor_intelligence_agent_team/competitor_agent_team.py new file mode 100644 index 0000000..2c9052d --- /dev/null +++ b/ai_agent_tutorials/ai_competitor_intelligence_agent_team/competitor_agent_team.py @@ -0,0 +1,346 @@ +import streamlit as st +from exa_py import Exa +from agno.agent import Agent +from agno.tools.firecrawl import FirecrawlTools +from agno.models.openai import OpenAIChat +from agno.tools.duckduckgo import DuckDuckGoTools +import pandas as pd +import requests +from firecrawl import FirecrawlApp +from pydantic import BaseModel, Field +from typing import List, Optional +import json + +# Streamlit UI +st.set_page_config(page_title="AI Competitor Intelligence Agent Team", layout="wide") + +# Sidebar for API keys +st.sidebar.title("API Keys") +openai_api_key = st.sidebar.text_input("OpenAI API Key", type="password") +firecrawl_api_key = st.sidebar.text_input("Firecrawl API Key", type="password") + +# Add search engine selection before API keys +search_engine = st.sidebar.selectbox( + "Select Search Endpoint", + options=["Perplexity AI - Sonar Pro", "Exa AI"], + help="Choose which AI service to use for finding competitor URLs" +) + +# Show relevant API key input based on selection +if search_engine == "Perplexity AI - Sonar Pro": + perplexity_api_key = st.sidebar.text_input("Perplexity API Key", type="password") + # Store API keys in session state + if openai_api_key and firecrawl_api_key and perplexity_api_key: + st.session_state.openai_api_key = openai_api_key + st.session_state.firecrawl_api_key = firecrawl_api_key + st.session_state.perplexity_api_key = perplexity_api_key + else: + st.sidebar.warning("Please enter all required API keys to proceed.") +else: # Exa AI + exa_api_key = st.sidebar.text_input("Exa API Key", type="password") + # Store API keys in session state + if openai_api_key and firecrawl_api_key and exa_api_key: + st.session_state.openai_api_key = openai_api_key + st.session_state.firecrawl_api_key = firecrawl_api_key + st.session_state.exa_api_key = exa_api_key + else: + st.sidebar.warning("Please enter all required API keys to proceed.") + +# Main UI +st.title("๐Ÿงฒ AI Competitor Intelligence Agent Team") +st.info( + """ + This app helps businesses analyze their competitors by extracting structured data from competitor websites and generating insights using AI. + - Provide a **URL** or a **description** of your company. + - The app will fetch competitor URLs, extract relevant information, and generate a detailed analysis report. + """ +) +st.success("For better results, provide both URL and a 5-6 word description of your company!") + +# Input fields for URL and description +url = st.text_input("Enter your company URL :") +description = st.text_area("Enter a description of your company (if URL is not available):") + +# Initialize API keys and tools +if "openai_api_key" in st.session_state and "firecrawl_api_key" in st.session_state: + if (search_engine == "Perplexity AI - Sonar Pro" and "perplexity_api_key" in st.session_state) or \ + (search_engine == "Exa AI" and "exa_api_key" in st.session_state): + + # Initialize Exa only if selected + if search_engine == "Exa AI": + exa = Exa(api_key=st.session_state.exa_api_key) + + firecrawl_tools = FirecrawlTools( + api_key=st.session_state.firecrawl_api_key, + scrape=False, + crawl=True, + limit=5 + ) + + firecrawl_agent = Agent( + model=OpenAIChat(id="gpt-4o", api_key=st.session_state.openai_api_key), + tools=[firecrawl_tools, DuckDuckGoTools()], + show_tool_calls=True, + markdown=True + ) + + analysis_agent = Agent( + model=OpenAIChat(id="gpt-4o", api_key=st.session_state.openai_api_key), + show_tool_calls=True, + markdown=True + ) + + # New agent for comparing competitor data + comparison_agent = Agent( + model=OpenAIChat(id="gpt-4o", api_key=st.session_state.openai_api_key), + show_tool_calls=True, + markdown=True + ) + + def get_competitor_urls(url: str = None, description: str = None) -> list[str]: + if not url and not description: + raise ValueError("Please provide either a URL or a description.") + + if search_engine == "Perplexity AI - Sonar Pro": + perplexity_url = "https://api.perplexity.ai/chat/completions" + + content = "Find me 3 competitor company URLs similar to the company with " + if url and description: + content += f"URL: {url} and description: {description}" + elif url: + content += f"URL: {url}" + else: + content += f"description: {description}" + content += ". ONLY RESPOND WITH THE URLS, NO OTHER TEXT." + + payload = { + "model": "sonar-pro", + "messages": [ + { + "role": "system", + "content": "Be precise and only return 3 company URLs ONLY." + }, + { + "role": "user", + "content": content + } + ], + "max_tokens": 1000, + "temperature": 0.2, + } + + headers = { + "Authorization": f"Bearer {st.session_state.perplexity_api_key}", + "Content-Type": "application/json" + } + + try: + response = requests.post(perplexity_url, json=payload, headers=headers) + response.raise_for_status() + urls = response.json()['choices'][0]['message']['content'].strip().split('\n') + return [url.strip() for url in urls if url.strip()] + except Exception as e: + st.error(f"Error fetching competitor URLs from Perplexity: {str(e)}") + return [] + + else: # Exa AI + try: + if url: + result = exa.find_similar( + url=url, + num_results=3, + exclude_source_domain=True, + category="company" + ) + else: + result = exa.search( + description, + type="neural", + category="company", + use_autoprompt=True, + num_results=3 + ) + return [item.url for item in result.results] + except Exception as e: + st.error(f"Error fetching competitor URLs from Exa: {str(e)}") + return [] + + class CompetitorDataSchema(BaseModel): + company_name: str = Field(description="Name of the company") + pricing: str = Field(description="Pricing details, tiers, and plans") + key_features: List[str] = Field(description="Main features and capabilities of the product/service") + tech_stack: List[str] = Field(description="Technologies, frameworks, and tools used") + marketing_focus: str = Field(description="Main marketing angles and target audience") + customer_feedback: str = Field(description="Customer testimonials, reviews, and feedback") + + def extract_competitor_info(competitor_url: str) -> Optional[dict]: + try: + # Initialize FirecrawlApp with API key + app = FirecrawlApp(api_key=st.session_state.firecrawl_api_key) + + # Add wildcard to crawl subpages + url_pattern = f"{competitor_url}/*" + + extraction_prompt = """ + Extract detailed information about the company's offerings, including: + - Company name and basic information + - Pricing details, plans, and tiers + - Key features and main capabilities + - Technology stack and technical details + - Marketing focus and target audience + - Customer feedback and testimonials + + Analyze the entire website content to provide comprehensive information for each field. + """ + + response = app.extract( + [url_pattern], + { + 'prompt': extraction_prompt, + 'schema': CompetitorDataSchema.model_json_schema(), + } + ) + + if response.get('success') and response.get('data'): + extracted_info = response['data'] + + # Create JSON structure + competitor_json = { + "competitor_url": competitor_url, + "company_name": extracted_info.get('company_name', 'N/A'), + "pricing": extracted_info.get('pricing', 'N/A'), + "key_features": extracted_info.get('key_features', [])[:5], # Top 5 features + "tech_stack": extracted_info.get('tech_stack', [])[:5], # Top 5 tech stack items + "marketing_focus": extracted_info.get('marketing_focus', 'N/A'), + "customer_feedback": extracted_info.get('customer_feedback', 'N/A') + } + + return competitor_json + + else: + return None + + except Exception as e: + return None + + def generate_comparison_report(competitor_data: list) -> None: + # Format the competitor data for the prompt + formatted_data = json.dumps(competitor_data, indent=2) + print(formatted_data) + + # Updated system prompt for more structured output + system_prompt = f""" + As an expert business analyst, analyze the following competitor data in JSON format and create a structured comparison. + Extract and summarize the key information into concise points. + + {formatted_data} + + Return the data in a structured format with EXACTLY these columns: + Company, Pricing, Key Features, Tech Stack, Marketing Focus, Customer Feedback + + Rules: + 1. For Company: Include company name and URL + 2. For Key Features: List top 3 most important features only + 3. For Tech Stack: List top 3 most relevant technologies only + 4. Keep all entries clear and concise + 5. Format feedback as brief quotes + 6. Return ONLY the structured data, no additional text + """ + + # Get comparison data from agent + comparison_response = comparison_agent.run(system_prompt) + + try: + # Split the response into lines and clean them + table_lines = [ + line.strip() + for line in comparison_response.content.split('\n') + if line.strip() and '|' in line + ] + + # Extract headers (first row) + headers = [ + col.strip() + for col in table_lines[0].split('|') + if col.strip() + ] + + # Extract data rows (skip header and separator rows) + data_rows = [] + for line in table_lines[2:]: # Skip header and separator rows + row_data = [ + cell.strip() + for cell in line.split('|') + if cell.strip() + ] + if len(row_data) == len(headers): + data_rows.append(row_data) + + # Create DataFrame + df = pd.DataFrame( + data_rows, + columns=headers + ) + + # Display the table + st.subheader("Competitor Comparison") + st.table(df) + + except Exception as e: + st.error(f"Error creating comparison table: {str(e)}") + st.write("Raw comparison data for debugging:", comparison_response.content) + + def generate_analysis_report(competitor_data: list): + # Format the competitor data for the prompt + formatted_data = json.dumps(competitor_data, indent=2) + print("Analysis Data:", formatted_data) # For debugging + + report = analysis_agent.run( + f"""Analyze the following competitor data in JSON format and identify market opportunities to improve my own company: + + {formatted_data} + + Tasks: + 1. Identify market gaps and opportunities based on competitor offerings + 2. Analyze competitor weaknesses that we can capitalize on + 3. Recommend unique features or capabilities we should develop + 4. Suggest pricing and positioning strategies to gain competitive advantage + 5. Outline specific growth opportunities in underserved market segments + 6. Provide actionable recommendations for product development and go-to-market strategy + + Focus on finding opportunities where we can differentiate and do better than competitors. + Highlight any unmet customer needs or pain points we can address. + """ + ) + return report.content + + # Run analysis when the user clicks the button + if st.button("Analyze Competitors"): + if url or description: + with st.spinner("Fetching competitor URLs..."): + competitor_urls = get_competitor_urls(url=url, description=description) + st.write(f"Competitor URLs: {competitor_urls}") + + competitor_data = [] + for comp_url in competitor_urls: + with st.spinner(f"Analyzing Competitor: {comp_url}..."): + competitor_info = extract_competitor_info(comp_url) + if competitor_info is not None: + competitor_data.append(competitor_info) + + if competitor_data: + # Generate and display comparison report + with st.spinner("Generating comparison table..."): + generate_comparison_report(competitor_data) + + # Generate and display final analysis report + with st.spinner("Generating analysis report..."): + analysis_report = generate_analysis_report(competitor_data) + st.subheader("Competitor Analysis Report") + st.markdown(analysis_report) + + st.success("Analysis complete!") + else: + st.error("Could not extract data from any competitor URLs") + else: + st.error("Please provide either a URL or a description.") \ No newline at end of file diff --git a/ai_agent_tutorials/ai_competitor_intelligence_agent_team/requirements.txt b/ai_agent_tutorials/ai_competitor_intelligence_agent_team/requirements.txt new file mode 100644 index 0000000..9095861 --- /dev/null +++ b/ai_agent_tutorials/ai_competitor_intelligence_agent_team/requirements.txt @@ -0,0 +1,5 @@ +exa-py==1.7.1 +firecrawl-py==1.9.0 +duckduckgo-search==7.2.1 +agno +streamlit==1.41.1 \ No newline at end of file diff --git a/ai_agent_tutorials/ai_customer_support_agent/README.md b/ai_agent_tutorials/ai_customer_support_agent/README.md index 0eaefaf..2964558 100644 --- a/ai_agent_tutorials/ai_customer_support_agent/README.md +++ b/ai_agent_tutorials/ai_customer_support_agent/README.md @@ -13,6 +13,7 @@ This Streamlit app implements an AI-powered customer support agent for synthetic 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_customer_support_agent ``` 2. Install the required dependencies: @@ -28,7 +29,7 @@ The app expects Qdrant to be running on localhost:6333. Adjust the configuration docker pull qdrant/qdrant docker run -p 6333:6333 -p 6334:6334 \ - -v $(pwd)/qdrant_storage:/qdrant/storage:z \ + -v "$(pwd)/qdrant_storage:/qdrant/storage:z" \ qdrant/qdrant ``` diff --git a/ai_agent_tutorials/ai_customer_support_agent/customer_support_agent.py b/ai_agent_tutorials/ai_customer_support_agent/customer_support_agent.py index a6fd43f..e130d38 100644 --- a/ai_agent_tutorials/ai_customer_support_agent/customer_support_agent.py +++ b/ai_agent_tutorials/ai_customer_support_agent/customer_support_agent.py @@ -14,84 +14,115 @@ openai_api_key = st.text_input("Enter OpenAI API Key", type="password") if openai_api_key: os.environ['OPENAI_API_KEY'] = openai_api_key - + class CustomerSupportAIAgent: def __init__(self): + # Initialize Mem0 with Qdrant as the vector store config = { "vector_store": { "provider": "qdrant", "config": { - "model": "gpt-4o-mini", "host": "localhost", "port": 6333, } }, } - self.memory = Memory.from_config(config) + try: + self.memory = Memory.from_config(config) + except Exception as e: + st.error(f"Failed to initialize memory: {e}") + st.stop() # Stop execution if memory initialization fails + self.client = OpenAI() self.app_id = "customer-support" def handle_query(self, query, user_id=None): - relevant_memories = self.memory.search(query=query, user_id=user_id) - context = "Relevant past information:\n" - for mem in relevant_memories: - context += f"- {mem['text']}\n" + try: + # Search for relevant memories + relevant_memories = self.memory.search(query=query, user_id=user_id) + + # Build context from relevant memories + context = "Relevant past information:\n" + if relevant_memories and "results" in relevant_memories: + for memory in relevant_memories["results"]: + if "memory" in memory: + context += f"- {memory['memory']}\n" - full_prompt = f"{context}\nCustomer: {query}\nSupport Agent:" + # Generate a response using OpenAI + full_prompt = f"{context}\nCustomer: {query}\nSupport Agent:" + response = self.client.chat.completions.create( + model="gpt-4", + messages=[ + {"role": "system", "content": "You are a customer support AI agent for TechGadgets.com, an online electronics store."}, + {"role": "user", "content": full_prompt} + ] + ) + answer = response.choices[0].message.content - response = self.client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": "You are a customer support AI agent for TechGadgets.com, an online electronics store."}, - {"role": "user", "content": full_prompt} - ] - ) - answer = response.choices[0].message.content + # Add the query and answer to memory + self.memory.add(query, user_id=user_id, metadata={"app_id": self.app_id, "role": "user"}) + self.memory.add(answer, user_id=user_id, metadata={"app_id": self.app_id, "role": "assistant"}) - self.memory.add(query, user_id=user_id, metadata={"app_id": self.app_id, "role": "user"}) - self.memory.add(answer, user_id=user_id, metadata={"app_id": self.app_id, "role": "assistant"}) - - return answer + return answer + except Exception as e: + st.error(f"An error occurred while handling the query: {e}") + return "Sorry, I encountered an error. Please try again later." def get_memories(self, user_id=None): - return self.memory.get_all(user_id=user_id) + try: + # Retrieve all memories for a user + return self.memory.get_all(user_id=user_id) + except Exception as e: + st.error(f"Failed to retrieve memories: {e}") + return None - def generate_synthetic_data(self, user_id): - today = datetime.now() - order_date = (today - timedelta(days=10)).strftime("%B %d, %Y") - expected_delivery = (today + timedelta(days=2)).strftime("%B %d, %Y") + def generate_synthetic_data(self, user_id: str) -> dict | None: + try: + today = datetime.now() + order_date = (today - timedelta(days=10)).strftime("%B %d, %Y") + expected_delivery = (today + timedelta(days=2)).strftime("%B %d, %Y") - prompt = f"""Generate a detailed customer profile and order history for a TechGadgets.com customer with ID {user_id}. Include: - 1. Customer name and basic info - 2. A recent order of a high-end electronic device (placed on {order_date}, to be delivered by {expected_delivery}) - 3. Order details (product, price, order number) - 4. Customer's shipping address - 5. 2-3 previous orders from the past year - 6. 2-3 customer service interactions related to these orders - 7. Any preferences or patterns in their shopping behavior + prompt = f"""Generate a detailed customer profile and order history for a TechGadgets.com customer with ID {user_id}. Include: + 1. Customer name and basic info + 2. A recent order of a high-end electronic device (placed on {order_date}, to be delivered by {expected_delivery}) + 3. Order details (product, price, order number) + 4. Customer's shipping address + 5. 2-3 previous orders from the past year + 6. 2-3 customer service interactions related to these orders + 7. Any preferences or patterns in their shopping behavior - Format the output as a JSON object.""" + Format the output as a JSON object.""" - response = self.client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": "You are a data generation AI that creates realistic customer profiles and order histories. Always respond with valid JSON."}, - {"role": "user", "content": prompt} - ], - response_format={"type": "json_object"} - ) + response = self.client.chat.completions.create( + model="gpt-4", + messages=[ + {"role": "system", "content": "You are a data generation AI that creates realistic customer profiles and order histories. Always respond with valid JSON."}, + {"role": "user", "content": prompt} + ] + ) - customer_data = json.loads(response.choices[0].message.content) + customer_data = json.loads(response.choices[0].message.content) - # Add generated data to memory - for key, value in customer_data.items(): - if isinstance(value, list): - for item in value: - self.memory.add(json.dumps(item), user_id=user_id, metadata={"app_id": self.app_id, "role": "system"}) - else: - self.memory.add(f"{key}: {json.dumps(value)}", user_id=user_id, metadata={"app_id": self.app_id, "role": "system"}) + # Add generated data to memory + for key, value in customer_data.items(): + if isinstance(value, list): + for item in value: + self.memory.add( + json.dumps(item), + user_id=user_id, + metadata={"app_id": self.app_id, "role": "system"} + ) + else: + self.memory.add( + f"{key}: {json.dumps(value)}", + user_id=user_id, + metadata={"app_id": self.app_id, "role": "system"} + ) - return customer_data + return customer_data + except Exception as e: + st.error(f"Failed to generate synthetic data: {e}") + return None # Initialize the CustomerSupportAIAgent support_agent = CustomerSupportAIAgent() @@ -111,7 +142,10 @@ if openai_api_key: if customer_id: with st.spinner("Generating customer data..."): st.session_state.customer_data = support_agent.generate_synthetic_data(customer_id) - st.sidebar.success("Synthetic data generated successfully!") + if st.session_state.customer_data: + st.sidebar.success("Synthetic data generated successfully!") + else: + st.sidebar.error("Failed to generate synthetic data.") else: st.sidebar.error("Please enter a customer ID first.") @@ -126,8 +160,10 @@ if openai_api_key: memories = support_agent.get_memories(user_id=customer_id) if memories: st.sidebar.write(f"Memory for customer **{customer_id}**:") - for mem in memories: - st.sidebar.write(f"- {mem['text']}") + if memories and "results" in memories: + for memory in memories["results"]: + if "memory" in memory: + st.write(f"- {memory['memory']}") else: st.sidebar.info("No memory found for this customer ID.") else: @@ -152,7 +188,8 @@ if openai_api_key: st.markdown(query) # Generate and display response - answer = support_agent.handle_query(query, user_id=customer_id) + with st.spinner("Generating response..."): + answer = support_agent.handle_query(query, user_id=customer_id) # Add assistant response to chat history st.session_state.messages.append({"role": "assistant", "content": answer}) diff --git a/ai_agent_tutorials/ai_customer_support_agent/requirements.txt b/ai_agent_tutorials/ai_customer_support_agent/requirements.txt index 088b5ab..c7be07b 100644 --- a/ai_agent_tutorials/ai_customer_support_agent/requirements.txt +++ b/ai_agent_tutorials/ai_customer_support_agent/requirements.txt @@ -1,3 +1,3 @@ streamlit openai -mem0ai \ No newline at end of file +mem0ai==0.1.29 \ No newline at end of file diff --git a/ai_agent_tutorials/ai_data_analysis_agent/README.md b/ai_agent_tutorials/ai_data_analysis_agent/README.md new file mode 100644 index 0000000..bdd53a0 --- /dev/null +++ b/ai_agent_tutorials/ai_data_analysis_agent/README.md @@ -0,0 +1,55 @@ +# ๐Ÿ“Š AI Data Analysis Agent + +An AI data analysis Agent built using the Agno Agent framework and Openai's gpt-4o model. This agent helps users analyze their data - csv, excel files through natural language queries, powered by OpenAI's language models and DuckDB for efficient data processing - making data analysis accessible to users regardless of their SQL expertise. + +## Features + +- ๐Ÿ“ค **File Upload Support**: + - Upload CSV and Excel files + - Automatic data type detection and schema inference + - Support for multiple file formats + +- ๐Ÿ’ฌ **Natural Language Queries**: + - Convert natural language questions into SQL queries + - Get instant answers about your data + - No SQL knowledge required + +- ๐Ÿ” **Advanced Analysis**: + - Perform complex data aggregations + - Filter and sort data + - Generate statistical summaries + - Create data visualizations + +- ๐ŸŽฏ **Interactive UI**: + - User-friendly Streamlit interface + - Real-time query processing + - Clear result presentation + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_data_analysis_agent + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - Get OpenAI API key from [OpenAI Platform](https://platform.openai.com) + +3. **Run the Application** + ```bash + streamlit run ai_data_analyst.py + ``` + +## Usage + +1. Launch the application using the command above +2. Provide your OpenAI API key in the sidebar of Streamlit +3. Upload your CSV or Excel file through the Streamlit interface +4. Ask questions about your data in natural language +5. View the results and generated visualizations + diff --git a/ai_agent_tutorials/ai_data_analysis_agent/ai_data_analyst.py b/ai_agent_tutorials/ai_data_analysis_agent/ai_data_analyst.py new file mode 100644 index 0000000..cfc90af --- /dev/null +++ b/ai_agent_tutorials/ai_data_analysis_agent/ai_data_analyst.py @@ -0,0 +1,137 @@ +import json +import tempfile +import csv +import streamlit as st +import pandas as pd +from agno.models.openai import OpenAIChat +from phi.agent.duckdb import DuckDbAgent +from agno.tools.pandas import PandasTools +import re + +# Function to preprocess and save the uploaded file +def preprocess_and_save(file): + try: + # Read the uploaded file into a DataFrame + if file.name.endswith('.csv'): + df = pd.read_csv(file, encoding='utf-8', na_values=['NA', 'N/A', 'missing']) + elif file.name.endswith('.xlsx'): + df = pd.read_excel(file, na_values=['NA', 'N/A', 'missing']) + else: + st.error("Unsupported file format. Please upload a CSV or Excel file.") + return None, None, None + + # Ensure string columns are properly quoted + for col in df.select_dtypes(include=['object']): + df[col] = df[col].astype(str).replace({r'"': '""'}, regex=True) + + # Parse dates and numeric columns + for col in df.columns: + if 'date' in col.lower(): + df[col] = pd.to_datetime(df[col], errors='coerce') + elif df[col].dtype == 'object': + try: + df[col] = pd.to_numeric(df[col]) + except (ValueError, TypeError): + # Keep as is if conversion fails + pass + + # Create a temporary file to save the preprocessed data + with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as temp_file: + temp_path = temp_file.name + # Save the DataFrame to the temporary CSV file with quotes around string fields + df.to_csv(temp_path, index=False, quoting=csv.QUOTE_ALL) + + return temp_path, df.columns.tolist(), df # Return the DataFrame as well + except Exception as e: + st.error(f"Error processing file: {e}") + return None, None, None + +# Streamlit app +st.title("๐Ÿ“Š Data Analyst Agent") + +# Sidebar for API keys +with st.sidebar: + st.header("API Keys") + openai_key = st.text_input("Enter your OpenAI API key:", type="password") + if openai_key: + st.session_state.openai_key = openai_key + st.success("API key saved!") + else: + st.warning("Please enter your OpenAI API key to proceed.") + +# File upload widget +uploaded_file = st.file_uploader("Upload a CSV or Excel file", type=["csv", "xlsx"]) + +if uploaded_file is not None and "openai_key" in st.session_state: + # Preprocess and save the uploaded file + temp_path, columns, df = preprocess_and_save(uploaded_file) + + if temp_path and columns and df is not None: + # Display the uploaded data as a table + st.write("Uploaded Data:") + st.dataframe(df) # Use st.dataframe for an interactive table + + # Display the columns of the uploaded data + st.write("Uploaded columns:", columns) + + # Configure the semantic model with the temporary file path + semantic_model = { + "tables": [ + { + "name": "uploaded_data", + "description": "Contains the uploaded dataset.", + "path": temp_path, + } + ] + } + + # Initialize the DuckDbAgent for SQL query generation + duckdb_agent = DuckDbAgent( + model=OpenAIChat(model="gpt-4", api_key=st.session_state.openai_key), + semantic_model=json.dumps(semantic_model), + tools=[PandasTools()], + markdown=True, + add_history_to_messages=False, # Disable chat history + followups=False, # Disable follow-up queries + read_tool_call_history=False, # Disable reading tool call history + system_prompt="You are an expert data analyst. Generate SQL queries to solve the user's query. Return only the SQL query, enclosed in ```sql ``` and give the final answer.", + ) + + # Initialize code storage in session state + if "generated_code" not in st.session_state: + st.session_state.generated_code = None + + # Main query input widget + user_query = st.text_area("Ask a query about the data:") + + # Add info message about terminal output + st.info("๐Ÿ’ก Check your terminal for a clearer output of the agent's response") + + if st.button("Submit Query"): + if user_query.strip() == "": + st.warning("Please enter a query.") + else: + try: + # Show loading spinner while processing + with st.spinner('Processing your query...'): + # Get the response from DuckDbAgent + + response1 = duckdb_agent.run(user_query) + + # Extract the content from the RunResponse object + if hasattr(response1, 'content'): + response_content = response1.content + else: + response_content = str(response1) + response = duckdb_agent.print_response( + user_query, + stream=True, + ) + + # Display the response in Streamlit + st.markdown(response_content) + + + except Exception as e: + st.error(f"Error generating response from the DuckDbAgent: {e}") + st.error("Please try rephrasing your query or check if the data format is correct.") \ No newline at end of file diff --git a/ai_agent_tutorials/ai_data_analysis_agent/requirements.txt b/ai_agent_tutorials/ai_data_analysis_agent/requirements.txt new file mode 100644 index 0000000..ed751ae --- /dev/null +++ b/ai_agent_tutorials/ai_data_analysis_agent/requirements.txt @@ -0,0 +1,7 @@ +phidata +streamlit==1.41.1 +openai==1.58.1 +duckdb==1.1.3 +pandas +numpy==1.26.4 +agno \ No newline at end of file diff --git a/ai_agent_tutorials/ai_data_visualisation_agent/README.md b/ai_agent_tutorials/ai_data_visualisation_agent/README.md new file mode 100644 index 0000000..7abffe4 --- /dev/null +++ b/ai_agent_tutorials/ai_data_visualisation_agent/README.md @@ -0,0 +1,41 @@ +# ๐Ÿ“Š AI Data Visualization Agent +A Streamlit application that acts as your personal data visualization expert, powered by LLMs. Simply upload your dataset and ask questions in natural language - the AI agent will analyze your data, generate appropriate visualizations, and provide insights through a combination of charts, statistics, and explanations. + +## Features +#### Natural Language Data Analysis +- Ask questions about your data in plain English +- Get instant visualizations and statistical analysis +- Receive explanations of findings and insights +- Interactive follow-up questioning + +#### Intelligent Visualization Selection +- Automatic choice of appropriate chart types +- Dynamic visualization generation +- Statistical visualization support +- Custom plot formatting and styling + +#### Multi-Model AI Support +- Meta-Llama 3.1 405B for complex analysis +- DeepSeek V3 for detailed insights +- Qwen 2.5 7B for quick analysis +- Meta-Llama 3.3 70B for advanced queries + +## How to Run + +Follow the steps below to set up and run the application: +- Before anything else, Please get a free Together AI API Key here: https://api.together.ai/signin +- Get a free E2B API Key here: https://e2b.dev/ ; https://e2b.dev/docs/legacy/getting-started/api-key + +1. **Clone the Repository** + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_data_visualisation_agent + ``` +2. **Install the dependencies** + ```bash + pip install -r requirements.txt + ``` +3. **Run the Streamlit app** + ```bash + streamlit run ai_data_visualisation_agent.py + ``` \ No newline at end of file diff --git a/ai_agent_tutorials/ai_data_visualisation_agent/ai_data_visualisation_agent.py b/ai_agent_tutorials/ai_data_visualisation_agent/ai_data_visualisation_agent.py new file mode 100644 index 0000000..226bb55 --- /dev/null +++ b/ai_agent_tutorials/ai_data_visualisation_agent/ai_data_visualisation_agent.py @@ -0,0 +1,178 @@ +import os +import json +import re +import sys +import io +import contextlib +import warnings +from typing import Optional, List, Any, Tuple +from PIL import Image +import streamlit as st +import pandas as pd +import base64 +from io import BytesIO +from together import Together +from e2b_code_interpreter import Sandbox + +warnings.filterwarnings("ignore", category=UserWarning, module="pydantic") + +pattern = re.compile(r"```python\n(.*?)\n```", re.DOTALL) + +def code_interpret(e2b_code_interpreter: Sandbox, code: str) -> Optional[List[Any]]: + with st.spinner('Executing code in E2B sandbox...'): + stdout_capture = io.StringIO() + stderr_capture = io.StringIO() + + with contextlib.redirect_stdout(stdout_capture), contextlib.redirect_stderr(stderr_capture): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + exec = e2b_code_interpreter.run_code(code) + + if stderr_capture.getvalue(): + print("[Code Interpreter Warnings/Errors]", file=sys.stderr) + print(stderr_capture.getvalue(), file=sys.stderr) + + if stdout_capture.getvalue(): + print("[Code Interpreter Output]", file=sys.stdout) + print(stdout_capture.getvalue(), file=sys.stdout) + + if exec.error: + print(f"[Code Interpreter ERROR] {exec.error}", file=sys.stderr) + return None + return exec.results + +def match_code_blocks(llm_response: str) -> str: + match = pattern.search(llm_response) + if match: + code = match.group(1) + return code + return "" + +def chat_with_llm(e2b_code_interpreter: Sandbox, user_message: str, dataset_path: str) -> Tuple[Optional[List[Any]], str]: + # Update system prompt to include dataset path information + system_prompt = f"""You're a Python data scientist and data visualization expert. You are given a dataset at path '{dataset_path}' and also the user's query. +You need to analyze the dataset and answer the user's query with a response and you run Python code to solve them. +IMPORTANT: Always use the dataset path variable '{dataset_path}' in your code when reading the CSV file.""" + + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_message}, + ] + + with st.spinner('Getting response from Together AI LLM model...'): + client = Together(api_key=st.session_state.together_api_key) + response = client.chat.completions.create( + model=st.session_state.model_name, + messages=messages, + ) + + response_message = response.choices[0].message + python_code = match_code_blocks(response_message.content) + + if python_code: + code_interpreter_results = code_interpret(e2b_code_interpreter, python_code) + return code_interpreter_results, response_message.content + else: + st.warning(f"Failed to match any Python code in model's response") + return None, response_message.content + +def upload_dataset(code_interpreter: Sandbox, uploaded_file) -> str: + dataset_path = f"./{uploaded_file.name}" + + try: + code_interpreter.files.write(dataset_path, uploaded_file) + return dataset_path + except Exception as error: + st.error(f"Error during file upload: {error}") + raise error + + +def main(): + """Main Streamlit application.""" + st.title("๐Ÿ“Š AI Data Visualization Agent") + st.write("Upload your dataset and ask questions about it!") + + # Initialize session state variables + if 'together_api_key' not in st.session_state: + st.session_state.together_api_key = '' + if 'e2b_api_key' not in st.session_state: + st.session_state.e2b_api_key = '' + if 'model_name' not in st.session_state: + st.session_state.model_name = '' + + with st.sidebar: + st.header("API Keys and Model Configuration") + st.session_state.together_api_key = st.sidebar.text_input("Together AI API Key", type="password") + st.sidebar.info("๐Ÿ’ก Everyone gets a free $1 credit by Together AI - AI Acceleration Cloud platform") + st.sidebar.markdown("[Get Together AI API Key](https://api.together.ai/signin)") + + st.session_state.e2b_api_key = st.sidebar.text_input("Enter E2B API Key", type="password") + st.sidebar.markdown("[Get E2B API Key](https://e2b.dev/docs/legacy/getting-started/api-key)") + + # Add model selection dropdown + model_options = { + "Meta-Llama 3.1 405B": "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", + "DeepSeek V3": "deepseek-ai/DeepSeek-V3", + "Qwen 2.5 7B": "Qwen/Qwen2.5-7B-Instruct-Turbo", + "Meta-Llama 3.3 70B": "meta-llama/Llama-3.3-70B-Instruct-Turbo" + } + st.session_state.model_name = st.selectbox( + "Select Model", + options=list(model_options.keys()), + index=0 # Default to first option + ) + st.session_state.model_name = model_options[st.session_state.model_name] + + uploaded_file = st.file_uploader("Choose a CSV file", type="csv") + + if uploaded_file is not None: + # Display dataset with toggle + df = pd.read_csv(uploaded_file) + st.write("Dataset:") + show_full = st.checkbox("Show full dataset") + if show_full: + st.dataframe(df) + else: + st.write("Preview (first 5 rows):") + st.dataframe(df.head()) + # Query input + query = st.text_area("What would you like to know about your data?", + "Can you compare the average cost for two people between different categories?") + + if st.button("Analyze"): + if not st.session_state.together_api_key or not st.session_state.e2b_api_key: + st.error("Please enter both API keys in the sidebar.") + else: + with Sandbox(api_key=st.session_state.e2b_api_key) as code_interpreter: + # Upload the dataset + dataset_path = upload_dataset(code_interpreter, uploaded_file) + + # Pass dataset_path to chat_with_llm + code_results, llm_response = chat_with_llm(code_interpreter, query, dataset_path) + + # Display LLM's text response + st.write("AI Response:") + st.write(llm_response) + + # Display results/visualizations + if code_results: + for result in code_results: + if hasattr(result, 'png') and result.png: # Check if PNG data is available + # Decode the base64-encoded PNG data + png_data = base64.b64decode(result.png) + + # Convert PNG data to an image and display it + image = Image.open(BytesIO(png_data)) + st.image(image, caption="Generated Visualization", use_container_width=False) + elif hasattr(result, 'figure'): # For matplotlib figures + fig = result.figure # Extract the matplotlib figure + st.pyplot(fig) # Display using st.pyplot + elif hasattr(result, 'show'): # For plotly figures + st.plotly_chart(result) + elif isinstance(result, (pd.DataFrame, pd.Series)): + st.dataframe(result) + else: + st.write(result) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_data_visualisation_agent/requirements.txt b/ai_agent_tutorials/ai_data_visualisation_agent/requirements.txt new file mode 100644 index 0000000..2ec4fbe --- /dev/null +++ b/ai_agent_tutorials/ai_data_visualisation_agent/requirements.txt @@ -0,0 +1,7 @@ +together==1.3.10 +e2b-code-interpreter==1.0.3 +e2b==1.0.5 +Pillow==10.4.0 +streamlit +pandas +matplotlib diff --git a/ai_agent_tutorials/ai_deep_research_agent/README.md b/ai_agent_tutorials/ai_deep_research_agent/README.md new file mode 100644 index 0000000..ed64c17 --- /dev/null +++ b/ai_agent_tutorials/ai_deep_research_agent/README.md @@ -0,0 +1,74 @@ +# Deep Research Agent with OpenAI Agents SDK and Firecrawl + +A powerful research assistant that leverages OpenAI's Agents SDK and Firecrawl's deep research capabilities to perform comprehensive web research on any topic and any question. + +## Features + +- **Deep Web Research**: Automatically searches the web, extracts content, and synthesizes findings +- **Enhanced Analysis**: Uses OpenAI's Agents SDK to elaborate on research findings with additional context and insights +- **Interactive UI**: Clean Streamlit interface for easy interaction +- **Downloadable Reports**: Export research findings as markdown files + +## How It Works + +1. **Input Phase**: User provides a research topic and API credentials +2. **Research Phase**: The tool uses Firecrawl to search the web and extract relevant information +3. **Analysis Phase**: An initial research report is generated based on the findings +4. **Enhancement Phase**: A second agent elaborates on the initial report, adding depth and context +5. **Output Phase**: The enhanced report is presented to the user and available for download + +## Requirements + +- Python 3.8+ +- OpenAI API key +- Firecrawl API key +- Required Python packages (see `requirements.txt`) + +## Installation + +1. Clone this repository: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_deep_research_agent + ``` + +2. Install the required packages: + ```bash + pip install -r requirements.txt + ``` + +## Usage + +1. Run the Streamlit app: + ```bash + streamlit run deep_research_openai.py + ``` + +2. Enter your API keys in the sidebar: + - OpenAI API key + - Firecrawl API key + +3. Enter your research topic in the main input field + +4. Click "Start Research" and wait for the process to complete + +5. View and download your enhanced research report + +## Example Research Topics + +- "Latest developments in quantum computing" +- "Impact of climate change on marine ecosystems" +- "Advancements in renewable energy storage" +- "Ethical considerations in artificial intelligence" +- "Emerging trends in remote work technologies" + +## Technical Details + +The application uses two specialized agents: + +1. **Research Agent**: Utilizes Firecrawl's deep research endpoint to gather comprehensive information from multiple web sources. + +2. **Elaboration Agent**: Enhances the initial research by adding detailed explanations, examples, case studies, and practical implications. + +The Firecrawl deep research tool performs multiple iterations of web searches, content extraction, and analysis to provide thorough coverage of the topic. + diff --git a/ai_agent_tutorials/ai_deep_research_agent/deep_research_openai.py b/ai_agent_tutorials/ai_deep_research_agent/deep_research_openai.py new file mode 100644 index 0000000..db897c3 --- /dev/null +++ b/ai_agent_tutorials/ai_deep_research_agent/deep_research_openai.py @@ -0,0 +1,185 @@ +import asyncio +import streamlit as st +from typing import Dict, Any, List +from agents import Agent, Runner, trace +from agents import set_default_openai_key +from firecrawl import FirecrawlApp +from agents.tool import function_tool + +# Set page configuration +st.set_page_config( + page_title="OpenAI Deep Research Agent", + page_icon="๐Ÿ“˜", + layout="wide" +) + +# Initialize session state for API keys if not exists +if "openai_api_key" not in st.session_state: + st.session_state.openai_api_key = "" +if "firecrawl_api_key" not in st.session_state: + st.session_state.firecrawl_api_key = "" + +# Sidebar for API keys +with st.sidebar: + st.title("API Configuration") + openai_api_key = st.text_input( + "OpenAI API Key", + value=st.session_state.openai_api_key, + type="password" + ) + firecrawl_api_key = st.text_input( + "Firecrawl API Key", + value=st.session_state.firecrawl_api_key, + type="password" + ) + + if openai_api_key: + st.session_state.openai_api_key = openai_api_key + set_default_openai_key(openai_api_key) + if firecrawl_api_key: + st.session_state.firecrawl_api_key = firecrawl_api_key + +# Main content +st.title("๐Ÿ“˜ OpenAI Deep Research Agent") +st.markdown("This OpenAI Agent from the OpenAI Agents SDK performs deep research on any topic using Firecrawl") + +# Research topic input +research_topic = st.text_input("Enter your research topic:", placeholder="e.g., Latest developments in AI") + +# Keep the original deep_research tool +@function_tool +async def deep_research(query: str, max_depth: int, time_limit: int, max_urls: int) -> Dict[str, Any]: + """ + Perform comprehensive web research using Firecrawl's deep research endpoint. + """ + try: + # Initialize FirecrawlApp with the API key from session state + firecrawl_app = FirecrawlApp(api_key=st.session_state.firecrawl_api_key) + + # Define research parameters + params = { + "maxDepth": max_depth, + "timeLimit": time_limit, + "maxUrls": max_urls + } + + # Set up a callback for real-time updates + def on_activity(activity): + st.write(f"[{activity['type']}] {activity['message']}") + + # Run deep research + with st.spinner("Performing deep research..."): + results = firecrawl_app.deep_research( + query=query, + params=params, + on_activity=on_activity + ) + + return { + "success": True, + "final_analysis": results['data']['finalAnalysis'], + "sources_count": len(results['data']['sources']), + "sources": results['data']['sources'] + } + except Exception as e: + st.error(f"Deep research error: {str(e)}") + return {"error": str(e), "success": False} + +# Keep the original agents +research_agent = Agent( + name="research_agent", + instructions="""You are a research assistant that can perform deep web research on any topic. + + When given a research topic or question: + 1. Use the deep_research tool to gather comprehensive information + - Always use these parameters: + * max_depth: 3 (for moderate depth) + * time_limit: 180 (3 minutes) + * max_urls: 10 (sufficient sources) + 2. The tool will search the web, analyze multiple sources, and provide a synthesis + 3. Review the research results and organize them into a well-structured report + 4. Include proper citations for all sources + 5. Highlight key findings and insights + """, + tools=[deep_research] +) + +elaboration_agent = Agent( + name="elaboration_agent", + instructions="""You are an expert content enhancer specializing in research elaboration. + + When given a research report: + 1. Analyze the structure and content of the report + 2. Enhance the report by: + - Adding more detailed explanations of complex concepts + - Including relevant examples, case studies, and real-world applications + - Expanding on key points with additional context and nuance + - Adding visual elements descriptions (charts, diagrams, infographics) + - Incorporating latest trends and future predictions + - Suggesting practical implications for different stakeholders + 3. Maintain academic rigor and factual accuracy + 4. Preserve the original structure while making it more comprehensive + 5. Ensure all additions are relevant and valuable to the topic + """ +) + +async def run_research_process(topic: str): + """Run the complete research process.""" + # Step 1: Initial Research + with st.spinner("Conducting initial research..."): + research_result = await Runner.run(research_agent, topic) + initial_report = research_result.final_output + + # Display initial report in an expander + with st.expander("View Initial Research Report"): + st.markdown(initial_report) + + # Step 2: Enhance the report + with st.spinner("Enhancing the report with additional information..."): + elaboration_input = f""" + RESEARCH TOPIC: {topic} + + INITIAL RESEARCH REPORT: + {initial_report} + + Please enhance this research report with additional information, examples, case studies, + and deeper insights while maintaining its academic rigor and factual accuracy. + """ + + elaboration_result = await Runner.run(elaboration_agent, elaboration_input) + enhanced_report = elaboration_result.final_output + + return enhanced_report + +# Main research process +if st.button("Start Research", disabled=not (openai_api_key and firecrawl_api_key and research_topic)): + if not openai_api_key or not firecrawl_api_key: + st.warning("Please enter both API keys in the sidebar.") + elif not research_topic: + st.warning("Please enter a research topic.") + else: + try: + # Create placeholder for the final report + report_placeholder = st.empty() + + # Run the research process + enhanced_report = asyncio.run(run_research_process(research_topic)) + + # Display the enhanced report + report_placeholder.markdown("## Enhanced Research Report") + report_placeholder.markdown(enhanced_report) + + # Add download button + st.download_button( + "Download Report", + enhanced_report, + file_name=f"{research_topic.replace(' ', '_')}_report.md", + mime="text/markdown" + ) + + except Exception as e: + st.error(f"An error occurred: {str(e)}") + +# Footer +st.markdown("---") +st.markdown("Powered by OpenAI Agents SDK and Firecrawl") \ No newline at end of file diff --git a/ai_agent_tutorials/ai_deep_research_agent/requirements.txt b/ai_agent_tutorials/ai_deep_research_agent/requirements.txt new file mode 100644 index 0000000..810e0d0 --- /dev/null +++ b/ai_agent_tutorials/ai_deep_research_agent/requirements.txt @@ -0,0 +1,4 @@ +openai-agents +firecrawl +streamlit +firecrawl-py \ No newline at end of file diff --git a/ai_agent_tutorials/ai_finance_agent_team/README.md b/ai_agent_tutorials/ai_finance_agent_team/README.md index 35a6391..9723fba 100644 --- a/ai_agent_tutorials/ai_finance_agent_team/README.md +++ b/ai_agent_tutorials/ai_finance_agent_team/README.md @@ -15,6 +15,7 @@ This script demonstrates how to build a team of AI agents that work together as 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_finance_agent_team ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/ai_finance_agent_team/finance_agent_team.py b/ai_agent_tutorials/ai_finance_agent_team/finance_agent_team.py index f8aa3b3..8758cf8 100644 --- a/ai_agent_tutorials/ai_finance_agent_team/finance_agent_team.py +++ b/ai_agent_tutorials/ai_finance_agent_team/finance_agent_team.py @@ -1,16 +1,16 @@ -from phi.agent import Agent -from phi.model.openai import OpenAIChat -from phi.storage.agent.sqlite import SqlAgentStorage -from phi.tools.duckduckgo import DuckDuckGo -from phi.tools.yfinance import YFinanceTools -from phi.playground import Playground, serve_playground_app +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from agno.storage.agent.sqlite import SqliteAgentStorage +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.tools.yfinance import YFinanceTools +from agno.playground import Playground, serve_playground_app web_agent = Agent( name="Web Agent", role="Search the web for information", model=OpenAIChat(id="gpt-4o"), - tools=[DuckDuckGo()], - storage=SqlAgentStorage(table_name="web_agent", db_file="agents.db"), + tools=[DuckDuckGoTools()], + storage=SqliteAgentStorage(table_name="web_agent", db_file="agents.db"), add_history_to_messages=True, markdown=True, ) @@ -21,7 +21,7 @@ finance_agent = Agent( model=OpenAIChat(id="gpt-4o"), tools=[YFinanceTools(stock_price=True, analyst_recommendations=True, company_info=True, company_news=True)], instructions=["Always use tables to display data"], - storage=SqlAgentStorage(table_name="finance_agent", db_file="agents.db"), + storage=SqliteAgentStorage(table_name="finance_agent", db_file="agents.db"), add_history_to_messages=True, markdown=True, ) diff --git a/ai_agent_tutorials/ai_finance_agent_team/requirements.txt b/ai_agent_tutorials/ai_finance_agent_team/requirements.txt index 0638842..958c9bb 100644 --- a/ai_agent_tutorials/ai_finance_agent_team/requirements.txt +++ b/ai_agent_tutorials/ai_finance_agent_team/requirements.txt @@ -1,5 +1,5 @@ openai -phidata +agno duckduckgo-search yfinance fastapi[standard] diff --git a/ai_agent_tutorials/ai_game_design_agent_team/README.md b/ai_agent_tutorials/ai_game_design_agent_team/README.md new file mode 100644 index 0000000..d3566fd --- /dev/null +++ b/ai_agent_tutorials/ai_game_design_agent_team/README.md @@ -0,0 +1,67 @@ +# AI Game Design Agent Team ๐ŸŽฎ + +The AI Game Design Agent Team is a collaborative game design system powered by [AG2](https://github.com/ag2ai/ag2?tab=readme-ov-file)(formerly AutoGen)'s AI Agent framework. This app generates comprehensive game concepts through the coordination of multiple specialized AI agents, each focusing on different aspects of game design based on user inputs such as game type, target audience, art style, and technical requirements. This is built on AG2's new swarm feature run through initiate_chat() method. + +## Features + +- **Specialized Game Design Agent Team** + - ๐ŸŽญ **Story Agent**: Specializes in narrative design and world-building, including character development, plot arcs, dialogue writing, and lore creation + - ๐ŸŽฎ **Gameplay Agent**: Focuses on game mechanics and systems design, including player progression, combat systems, resource management, and balancing + - ๐ŸŽจ **Visuals Agent**: Handles art direction and audio design, covering UI/UX, character/environment art style, sound effects, and music composition + - โš™๏ธ **Tech Agent**: Provides technical architecture and implementation guidance, including engine selection, optimization strategies, networking requirements, and development roadmap + - ๐ŸŽฏ **Task Agent**: Coordinates between all specialized agents and ensures cohesive integration of different game aspects + +- **Comprehensive Game Design Outputs**: + - Detailed narrative and world-building elements + - Core gameplay mechanics and systems + - Visual and audio direction + - Technical specifications and requirements + - Development timeline and budget considerations + - Coherent game design from the team. + +- **Customizable Input Parameters**: + - Game type and target audience + - Art style and visual preferences + - Platform requirements + - Development constraints (time, budget) + - Core mechanics and gameplay features + +- **Interactive Results**: + - Quick show of game design ideas from each agent + - Detailed results are presented in expandable sections for easy navigation and reference + +## How to Run + +Follow these steps to set up and run the application: + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_game_design_team + ``` + +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Set Up OpenAI API Key**: + - Obtain an OpenAI API key from [OpenAI's platform](https://platform.openai.com) + - You'll input this key in the app's sidebar when running + +4. **Run the Streamlit App**: + ```bash + streamlit run ai_game_design_agent_team/game_design_agent_team.py + ``` + +## Usage + +1. Enter your OpenAI API key in the sidebar +2. Fill in the game details: + - Background vibe and setting + - Game type and target audience + - Visual style preferences + - Technical requirements + - Development constraints +3. Click "Generate Game Concept" to receive comprehensive design documentation from all agents +4. Review the outputs in the expandable sections for each aspect of game design diff --git a/ai_agent_tutorials/ai_game_design_agent_team/game_design_agent_team.py b/ai_agent_tutorials/ai_game_design_agent_team/game_design_agent_team.py new file mode 100644 index 0000000..950a4aa --- /dev/null +++ b/ai_agent_tutorials/ai_game_design_agent_team/game_design_agent_team.py @@ -0,0 +1,290 @@ +import asyncio +import streamlit as st +from autogen import ( + SwarmAgent, + SwarmResult, + initiate_swarm_chat, + OpenAIWrapper, + AFTER_WORK, + UPDATE_SYSTEM_MESSAGE +) + +# Initialize session state +if 'output' not in st.session_state: + st.session_state.output = {'story': '', 'gameplay': '', 'visuals': '', 'tech': ''} + +# Sidebar for API key input +st.sidebar.title("API Key") +api_key = st.sidebar.text_input("Enter your OpenAI API Key", type="password") + +# Add guidance in sidebar +st.sidebar.success(""" +โœจ **Getting Started** + +Please provide inputs and features for your dream game! Consider: +- The overall vibe and setting +- Core gameplay elements +- Target audience and platforms +- Visual style preferences +- Technical requirements + +The AI agents will collaborate to develop a comprehensive game concept based on your specifications. +""") + +# Main app UI +st.title("๐ŸŽฎ AI Game Design Agent Team") + +# Add agent information below title +st.info(""" +**Meet Your AI Game Design Team:** + +๐ŸŽญ **Story Agent** - Crafts compelling narratives and rich worlds + +๐ŸŽฎ **Gameplay Agent** - Creates engaging mechanics and systems + +๐ŸŽจ **Visuals Agent** - Shapes the artistic vision and style + +โš™๏ธ **Tech Agent** - Provides technical direction and solutions + +These agents collaborate to create a comprehensive game concept based on your inputs. +""") + +# User inputs +st.subheader("Game Details") +col1, col2 = st.columns(2) + +with col1: + background_vibe = st.text_input("Background Vibe", "Epic fantasy with dragons") + game_type = st.selectbox("Game Type", ["RPG", "Action", "Adventure", "Puzzle", "Strategy", "Simulation", "Platform", "Horror"]) + target_audience = st.selectbox("Target Audience", ["Kids (7-12)", "Teens (13-17)", "Young Adults (18-25)", "Adults (26+)", "All Ages"]) + player_perspective = st.selectbox("Player Perspective", ["First Person", "Third Person", "Top Down", "Side View", "Isometric"]) + multiplayer = st.selectbox("Multiplayer Support", ["Single Player Only", "Local Co-op", "Online Multiplayer", "Both Local and Online"]) + +with col2: + game_goal = st.text_input("Game Goal", "Save the kingdom from eternal winter") + art_style = st.selectbox("Art Style", ["Realistic", "Cartoon", "Pixel Art", "Stylized", "Low Poly", "Anime", "Hand-drawn"]) + platform = st.multiselect("Target Platforms", ["PC", "Mobile", "PlayStation", "Xbox", "Nintendo Switch", "Web Browser"]) + development_time = st.slider("Development Time (months)", 1, 36, 12) + cost = st.number_input("Budget (USD)", min_value=0, value=10000, step=5000) + +# Additional details +st.subheader("Detailed Preferences") +col3, col4 = st.columns(2) + +with col3: + core_mechanics = st.multiselect( + "Core Gameplay Mechanics", + ["Combat", "Exploration", "Puzzle Solving", "Resource Management", "Base Building", "Stealth", "Racing", "Crafting"] + ) + mood = st.multiselect( + "Game Mood/Atmosphere", + ["Epic", "Mysterious", "Peaceful", "Tense", "Humorous", "Dark", "Whimsical", "Scary"] + ) + +with col4: + inspiration = st.text_area("Games for Inspiration (comma-separated)", "") + unique_features = st.text_area("Unique Features or Requirements", "") + +depth = st.selectbox("Level of Detail in Response", ["Low", "Medium", "High"]) + +# Button to start the agent collaboration +if st.button("Generate Game Concept"): + # Check if API key is provided + if not api_key: + st.error("Please enter your OpenAI API key.") + else: + with st.spinner('๐Ÿค– AI Agents are collaborating on your game concept...'): + # Prepare the task based on user inputs + task = f""" + Create a game concept with the following details: + - Background Vibe: {background_vibe} + - Game Type: {game_type} + - Game Goal: {game_goal} + - Target Audience: {target_audience} + - Player Perspective: {player_perspective} + - Multiplayer Support: {multiplayer} + - Art Style: {art_style} + - Target Platforms: {', '.join(platform)} + - Development Time: {development_time} months + - Budget: ${cost:,} + - Core Mechanics: {', '.join(core_mechanics)} + - Mood/Atmosphere: {', '.join(mood)} + - Inspiration: {inspiration} + - Unique Features: {unique_features} + - Detail Level: {depth} + """ + + llm_config = {"config_list": [{"model": "gpt-4o-mini","api_key": api_key}]} + + # initialize context variables + context_variables = { + "story": None, + "gameplay": None, + "visuals": None, + "tech": None, + } + + # define functions to be called by the agents + def update_story_overview(story_summary:str, context_variables:dict) -> SwarmResult: + """Keep the summary as short as possible.""" + context_variables["story"] = story_summary + st.sidebar.success('Story overview: ' + story_summary) + return SwarmResult(agent="gameplay_agent", context_variables=context_variables) + + def update_gameplay_overview(gameplay_summary:str, context_variables:dict) -> SwarmResult: + """Keep the summary as short as possible.""" + context_variables["gameplay"] = gameplay_summary + st.sidebar.success('Gameplay overview: ' + gameplay_summary) + return SwarmResult(agent="visuals_agent", context_variables=context_variables) + + def update_visuals_overview(visuals_summary:str, context_variables:dict) -> SwarmResult: + """Keep the summary as short as possible.""" + context_variables["visuals"] = visuals_summary + st.sidebar.success('Visuals overview: ' + visuals_summary) + return SwarmResult(agent="tech_agent", context_variables=context_variables) + + def update_tech_overview(tech_summary:str, context_variables:dict) -> SwarmResult: + """Keep the summary as short as possible.""" + context_variables["tech"] = tech_summary + st.sidebar.success('Tech overview: ' + tech_summary) + return SwarmResult(agent="story_agent", context_variables=context_variables) + + system_messages = { + "story_agent": """ + You are an experienced game story designer specializing in narrative design and world-building. Your task is to: + 1. Create a compelling narrative that aligns with the specified game type and target audience. + 2. Design memorable characters with clear motivations and character arcs. + 3. Develop the game's world, including its history, culture, and key locations. + 4. Plan story progression and major plot points. + 5. Integrate the narrative with the specified mood/atmosphere. + 6. Consider how the story supports the core gameplay mechanics. + """, + "gameplay_agent": """ + You are a senior game mechanics designer with expertise in player engagement and systems design. Your task is to: + 1. Design core gameplay loops that match the specified game type and mechanics. + 2. Create progression systems (character development, skills, abilities). + 3. Define player interactions and control schemes for the chosen perspective. + 4. Balance gameplay elements for the target audience. + 5. Design multiplayer interactions if applicable. + 6. Specify game modes and difficulty settings. + 7. Consider the budget and development time constraints. + """, + "visuals_agent": """ + You are a creative art director with expertise in game visual and audio design. Your task is to: + 1. Define the visual style guide matching the specified art style. + 2. Design character and environment aesthetics. + 3. Plan visual effects and animations. + 4. Create the audio direction including music style, sound effects, and ambient sound. + 5. Consider technical constraints of chosen platforms. + 6. Align visual elements with the game's mood/atmosphere. + 7. Work within the specified budget constraints. + """, + "tech_agent": """ + You are a technical director with extensive game development experience. Your task is to: + 1. Recommend appropriate game engine and development tools. + 2. Define technical requirements for all target platforms. + 3. Plan the development pipeline and asset workflow. + 4. Identify potential technical challenges and solutions. + 5. Estimate resource requirements within the budget. + 6. Consider scalability and performance optimization. + 7. Plan for multiplayer infrastructure if applicable. + """ + } + + def update_system_message_func(agent: SwarmAgent, messages) -> str: + """""" + system_prompt = system_messages[agent.name] + + current_gen = agent.name.split("_")[0] + if agent._context_variables.get(current_gen) is None: + system_prompt += f"Call the update function provided to first provide a 2-3 sentence summary of your ideas on {current_gen.upper()} based on the context provided." + agent.llm_config['tool_choice'] = {"type": "function", "function": {"name": f"update_{current_gen}_overview"}} + agent.client = OpenAIWrapper(**agent.llm_config) + else: + # remove the tools to avoid the agent from using it and reduce cost + agent.llm_config["tools"] = None + agent.llm_config['tool_choice'] = None + agent.client = OpenAIWrapper(**agent.llm_config) + # the agent has given a summary, now it should generate a detailed response + system_prompt += f"\n\nYour task\nYou task is write the {current_gen} part of the report. Do not include any other parts. Do not use XML tags.\nStart your response with: '## {current_gen.capitalize()} Design'." + + # Remove all messages except the first one with less cost + k = list(agent._oai_messages.keys())[-1] + agent._oai_messages[k] = agent._oai_messages[k][:1] + + system_prompt += f"\n\n\nBelow are some context for you to refer to:" + # Add context variables to the prompt + for k, v in agent._context_variables.items(): + if v is not None: + system_prompt += f"\n{k.capitalize()} Summary:\n{v}" + + return system_prompt + + state_update = UPDATE_SYSTEM_MESSAGE(update_system_message_func) + + # Define agents + story_agent = SwarmAgent( + "story_agent", + llm_config=llm_config, + functions=update_story_overview, + update_agent_state_before_reply=[state_update] + ) + + gameplay_agent = SwarmAgent( + "gameplay_agent", + llm_config= llm_config, + functions=update_gameplay_overview, + update_agent_state_before_reply=[state_update] + ) + + visuals_agent = SwarmAgent( + "visuals_agent", + llm_config=llm_config, + functions=update_visuals_overview, + update_agent_state_before_reply=[state_update] + ) + + tech_agent = SwarmAgent( + name="tech_agent", + llm_config=llm_config, + functions=update_tech_overview, + update_agent_state_before_reply=[state_update] + ) + + story_agent.register_hand_off(AFTER_WORK(gameplay_agent)) + gameplay_agent.register_hand_off(AFTER_WORK(visuals_agent)) + visuals_agent.register_hand_off(AFTER_WORK(tech_agent)) + tech_agent.register_hand_off(AFTER_WORK(story_agent)) + + result, _, _ = initiate_swarm_chat( + initial_agent=story_agent, + agents=[story_agent, gameplay_agent, visuals_agent, tech_agent], + user_agent=None, + messages=task, + max_rounds=13, + ) + + # Update session state with the individual responses + st.session_state.output = { + 'story': result.chat_history[-4]['content'], + 'gameplay': result.chat_history[-3]['content'], + 'visuals': result.chat_history[-2]['content'], + 'tech': result.chat_history[-1]['content'] + } + + # Display success message after completion + st.success('โœจ Game concept generated successfully!') + + # Display the individual outputs in expanders + with st.expander("Story Design"): + st.markdown(st.session_state.output['story']) + + with st.expander("Gameplay Mechanics"): + st.markdown(st.session_state.output['gameplay']) + + with st.expander("Visual and Audio Design"): + st.markdown(st.session_state.output['visuals']) + + with st.expander("Technical Recommendations"): + st.markdown(st.session_state.output['tech']) + diff --git a/ai_agent_tutorials/ai_game_design_agent_team/requirements.txt b/ai_agent_tutorials/ai_game_design_agent_team/requirements.txt new file mode 100644 index 0000000..6519654 --- /dev/null +++ b/ai_agent_tutorials/ai_game_design_agent_team/requirements.txt @@ -0,0 +1,2 @@ +streamlit==1.41.1 +autogen \ No newline at end of file diff --git a/ai_agent_tutorials/ai_health_fitness_agent/README.md b/ai_agent_tutorials/ai_health_fitness_agent/README.md new file mode 100644 index 0000000..371129a --- /dev/null +++ b/ai_agent_tutorials/ai_health_fitness_agent/README.md @@ -0,0 +1,53 @@ +# AI Health & Fitness Planner Agent ๐Ÿ‹๏ธโ€โ™‚๏ธ + +The **AI Health & Fitness Planner** is a personalized health and fitness Agent powered by Agno AI Agent framework. This app generates tailored dietary and fitness plans based on user inputs such as age, weight, height, activity level, dietary preferences, and fitness goals. + +## Features + +- **Health Agent and Fitness Agent** + - The app has two phidata agents that are specialists in giving Diet advice and Fitness/workout advice respectively. + +- **Personalized Dietary Plans**: + - Generates detailed meal plans (breakfast, lunch, dinner, and snacks). + - Includes important considerations like hydration, electrolytes, and fiber intake. + - Supports various dietary preferences like Keto, Vegetarian, Low Carb, etc. + +- **Personalized Fitness Plans**: + - Provides customized exercise routines based on fitness goals. + - Covers warm-ups, main workouts, and cool-downs. + - Includes actionable fitness tips and progress tracking advice. + +- **Interactive Q&A**: Allows users to ask follow-up questions about their plans. + + +## Requirements + +The application requires the following Python libraries: + +- `agno` +- `google-generativeai` +- `streamlit` + +Ensure these dependencies are installed via the `requirements.txt` file according to their mentioned versions + +## How to Run + +Follow the steps below to set up and run the application: +Before anything else, Please get a free Gemini API Key provided by Google AI here: https://aistudio.google.com/apikey + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/ai_agent_tutorials/ai_health_fitness_agent + ``` + +2. **Install the dependencies** + ```bash + pip install -r requirements.txt + ``` +3. **Run the Streamlit app** + ```bash + streamlit run health_agent.py + ``` + + diff --git a/ai_agent_tutorials/ai_health_fitness_agent/health_agent.py b/ai_agent_tutorials/ai_health_fitness_agent/health_agent.py new file mode 100644 index 0000000..30dc5dc --- /dev/null +++ b/ai_agent_tutorials/ai_health_fitness_agent/health_agent.py @@ -0,0 +1,244 @@ +import streamlit as st +from agno.agent import Agent +from agno.models.google import Gemini + +st.set_page_config( + page_title="AI Health & Fitness Planner", + page_icon="๐Ÿ‹๏ธโ€โ™‚๏ธ", + layout="wide", + initial_sidebar_state="expanded" +) + +st.markdown(""" + +""", unsafe_allow_html=True) + +def display_dietary_plan(plan_content): + with st.expander("๐Ÿ“‹ Your Personalized Dietary Plan", expanded=True): + col1, col2 = st.columns([2, 1]) + + with col1: + st.markdown("### ๐ŸŽฏ Why this plan works") + st.info(plan_content.get("why_this_plan_works", "Information not available")) + st.markdown("### ๐Ÿฝ๏ธ Meal Plan") + st.write(plan_content.get("meal_plan", "Plan not available")) + + with col2: + st.markdown("### โš ๏ธ Important Considerations") + considerations = plan_content.get("important_considerations", "").split('\n') + for consideration in considerations: + if consideration.strip(): + st.warning(consideration) + +def display_fitness_plan(plan_content): + with st.expander("๐Ÿ’ช Your Personalized Fitness Plan", expanded=True): + col1, col2 = st.columns([2, 1]) + + with col1: + st.markdown("### ๐ŸŽฏ Goals") + st.success(plan_content.get("goals", "Goals not specified")) + st.markdown("### ๐Ÿ‹๏ธโ€โ™‚๏ธ Exercise Routine") + st.write(plan_content.get("routine", "Routine not available")) + + with col2: + st.markdown("### ๐Ÿ’ก Pro Tips") + tips = plan_content.get("tips", "").split('\n') + for tip in tips: + if tip.strip(): + st.info(tip) + +def main(): + if 'dietary_plan' not in st.session_state: + st.session_state.dietary_plan = {} + st.session_state.fitness_plan = {} + st.session_state.qa_pairs = [] + st.session_state.plans_generated = False + + st.title("๐Ÿ‹๏ธโ€โ™‚๏ธ AI Health & Fitness Planner") + st.markdown(""" +
+ Get personalized dietary and fitness plans tailored to your goals and preferences. + Our AI-powered system considers your unique profile to create the perfect plan for you. +
+ """, unsafe_allow_html=True) + + with st.sidebar: + st.header("๐Ÿ”‘ API Configuration") + gemini_api_key = st.text_input( + "Gemini API Key", + type="password", + help="Enter your Gemini API key to access the service" + ) + + if not gemini_api_key: + st.warning("โš ๏ธ Please enter your Gemini API Key to proceed") + st.markdown("[Get your API key here](https://aistudio.google.com/apikey)") + return + + st.success("API Key accepted!") + + if gemini_api_key: + try: + gemini_model = Gemini(id="gemini-1.5-flash", api_key=gemini_api_key) + except Exception as e: + st.error(f"โŒ Error initializing Gemini model: {e}") + return + + st.header("๐Ÿ‘ค Your Profile") + + col1, col2 = st.columns(2) + + with col1: + age = st.number_input("Age", min_value=10, max_value=100, step=1, help="Enter your age") + height = st.number_input("Height (cm)", min_value=100.0, max_value=250.0, step=0.1) + activity_level = st.selectbox( + "Activity Level", + options=["Sedentary", "Lightly Active", "Moderately Active", "Very Active", "Extremely Active"], + help="Choose your typical activity level" + ) + dietary_preferences = st.selectbox( + "Dietary Preferences", + options=["Vegetarian", "Keto", "Gluten Free", "Low Carb", "Dairy Free"], + help="Select your dietary preference" + ) + + with col2: + weight = st.number_input("Weight (kg)", min_value=20.0, max_value=300.0, step=0.1) + sex = st.selectbox("Sex", options=["Male", "Female", "Other"]) + fitness_goals = st.selectbox( + "Fitness Goals", + options=["Lose Weight", "Gain Muscle", "Endurance", "Stay Fit", "Strength Training"], + help="What do you want to achieve?" + ) + + if st.button("๐ŸŽฏ Generate My Personalized Plan", use_container_width=True): + with st.spinner("Creating your perfect health and fitness routine..."): + try: + dietary_agent = Agent( + name="Dietary Expert", + role="Provides personalized dietary recommendations", + model=gemini_model, + instructions=[ + "Consider the user's input, including dietary restrictions and preferences.", + "Suggest a detailed meal plan for the day, including breakfast, lunch, dinner, and snacks.", + "Provide a brief explanation of why the plan is suited to the user's goals.", + "Focus on clarity, coherence, and quality of the recommendations.", + ] + ) + + fitness_agent = Agent( + name="Fitness Expert", + role="Provides personalized fitness recommendations", + model=gemini_model, + instructions=[ + "Provide exercises tailored to the user's goals.", + "Include warm-up, main workout, and cool-down exercises.", + "Explain the benefits of each recommended exercise.", + "Ensure the plan is actionable and detailed.", + ] + ) + + user_profile = f""" + Age: {age} + Weight: {weight}kg + Height: {height}cm + Sex: {sex} + Activity Level: {activity_level} + Dietary Preferences: {dietary_preferences} + Fitness Goals: {fitness_goals} + """ + + dietary_plan_response = dietary_agent.run(user_profile) + dietary_plan = { + "why_this_plan_works": "High Protein, Healthy Fats, Moderate Carbohydrates, and Caloric Balance", + "meal_plan": dietary_plan_response.content, + "important_considerations": """ + - Hydration: Drink plenty of water throughout the day + - Electrolytes: Monitor sodium, potassium, and magnesium levels + - Fiber: Ensure adequate intake through vegetables and fruits + - Listen to your body: Adjust portion sizes as needed + """ + } + + fitness_plan_response = fitness_agent.run(user_profile) + fitness_plan = { + "goals": "Build strength, improve endurance, and maintain overall fitness", + "routine": fitness_plan_response.content, + "tips": """ + - Track your progress regularly + - Allow proper rest between workouts + - Focus on proper form + - Stay consistent with your routine + """ + } + + st.session_state.dietary_plan = dietary_plan + st.session_state.fitness_plan = fitness_plan + st.session_state.plans_generated = True + st.session_state.qa_pairs = [] + + display_dietary_plan(dietary_plan) + display_fitness_plan(fitness_plan) + + except Exception as e: + st.error(f"โŒ An error occurred: {e}") + + if st.session_state.plans_generated: + st.header("โ“ Questions about your plan?") + question_input = st.text_input("What would you like to know?") + + if st.button("Get Answer"): + if question_input: + with st.spinner("Finding the best answer for you..."): + dietary_plan = st.session_state.dietary_plan + fitness_plan = st.session_state.fitness_plan + + context = f"Dietary Plan: {dietary_plan.get('meal_plan', '')}\n\nFitness Plan: {fitness_plan.get('routine', '')}" + full_context = f"{context}\nUser Question: {question_input}" + + try: + agent = Agent(model=gemini_model, show_tool_calls=True, markdown=True) + run_response = agent.run(full_context) + + if hasattr(run_response, 'content'): + answer = run_response.content + else: + answer = "Sorry, I couldn't generate a response at this time." + + st.session_state.qa_pairs.append((question_input, answer)) + except Exception as e: + st.error(f"โŒ An error occurred while getting the answer: {e}") + + if st.session_state.qa_pairs: + st.header("๐Ÿ’ฌ Q&A History") + for question, answer in st.session_state.qa_pairs: + st.markdown(f"**Q:** {question}") + st.markdown(f"**A:** {answer}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_health_fitness_agent/requirements.txt b/ai_agent_tutorials/ai_health_fitness_agent/requirements.txt new file mode 100644 index 0000000..0762e21 --- /dev/null +++ b/ai_agent_tutorials/ai_health_fitness_agent/requirements.txt @@ -0,0 +1,3 @@ +google-generativeai==0.8.3 +streamlit==1.40.2 +agno \ No newline at end of file diff --git a/ai_agent_tutorials/ai_investment_agent/README.md b/ai_agent_tutorials/ai_investment_agent/README.md index f655d04..6ce5973 100644 --- a/ai_agent_tutorials/ai_investment_agent/README.md +++ b/ai_agent_tutorials/ai_investment_agent/README.md @@ -1,5 +1,5 @@ ## ๐Ÿ“ˆ AI Investment Agent -This Streamlit app is an AI-powered investment agent that compares the performance of two stocks and generates detailed reports. By using GPT-4o with Yahoo Finance data, this app provides valuable insights to help you make informed investment decisions. +This Streamlit app is an AI-powered investment agent built with Agno's AI Agent framework that compares the performance of two stocks and generates detailed reports. By using GPT-4o with Yahoo Finance data, this app provides valuable insights to help you make informed investment decisions. ### Features - Compare the performance of two stocks @@ -13,6 +13,7 @@ This Streamlit app is an AI-powered investment agent that compares the performan ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_investment_agent ``` 2. Install the required dependencies: @@ -31,7 +32,7 @@ streamlit run investment_agent.py ### How it Works? - Upon running the app, you will be prompted to enter your OpenAI API key. This key is used to authenticate and access the OpenAI language model. -- Once you provide a valid API key, an instance of the Assistant class is created. This assistant utilizes the GPT-4 language model from OpenAI and the YFinanceTools for accessing stock data. +- Once you provide a valid API key, an instance of the Assistant class is created. This assistant utilizes the GPT-4o language model from OpenAI and the YFinanceTools for accessing stock data. - Enter the stock symbols of the two companies you want to compare in the provided text input fields. - The assistant will perform the following steps: - Retrieve real-time stock prices and historical data using YFinanceTools diff --git a/ai_agent_tutorials/ai_investment_agent/investment_agent.py b/ai_agent_tutorials/ai_investment_agent/investment_agent.py index 2bd474d..699c2cc 100644 --- a/ai_agent_tutorials/ai_investment_agent/investment_agent.py +++ b/ai_agent_tutorials/ai_investment_agent/investment_agent.py @@ -1,30 +1,34 @@ -# Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.llm.openai import OpenAIChat -from phi.tools.yfinance import YFinanceTools +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from agno.tools.yfinance import YFinanceTools -# Set up the Streamlit app st.title("AI Investment Agent ๐Ÿ“ˆ๐Ÿค–") st.caption("This app allows you to compare the performance of two stocks and generate detailed reports.") -# Get OpenAI API key from user openai_api_key = st.text_input("OpenAI API Key", type="password") if openai_api_key: - # Create an instance of the Assistant - assistant = Assistant( - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), - tools=[YFinanceTools(stock_price=True, analyst_recommendations=True, company_info=True, company_news=True)], + assistant = Agent( + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), + tools=[ + YFinanceTools(stock_price=True, analyst_recommendations=True, stock_fundamentals=True) + ], show_tool_calls=True, + description="You are an investment analyst that researches stock prices, analyst recommendations, and stock fundamentals.", + instructions=[ + "Format your response using markdown and use tables to display data where possible." + ], ) - # Input fields for the stocks to compare - stock1 = st.text_input("Enter the first stock symbol") - stock2 = st.text_input("Enter the second stock symbol") + col1, col2 = st.columns(2) + with col1: + stock1 = st.text_input("Enter first stock symbol (e.g. AAPL)") + with col2: + stock2 = st.text_input("Enter second stock symbol (e.g. MSFT)") if stock1 and stock2: - # Get the response from the assistant - query = f"Compare {stock1} to {stock2}. Use every tool you have." - response = assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + with st.spinner(f"Analyzing {stock1} and {stock2}..."): + query = f"Compare both the stocks - {stock1} and {stock2} and make a detailed report for an investment trying to invest and compare these stocks" + response = assistant.run(query, stream=False) + st.markdown(response.content) diff --git a/ai_agent_tutorials/ai_investment_agent/requirements.txt b/ai_agent_tutorials/ai_investment_agent/requirements.txt index fe27582..b25ad27 100644 --- a/ai_agent_tutorials/ai_investment_agent/requirements.txt +++ b/ai_agent_tutorials/ai_investment_agent/requirements.txt @@ -1,4 +1,4 @@ streamlit -phidata +agno openai yfinance diff --git a/ai_agent_tutorials/ai_journalist_agent/README.md b/ai_agent_tutorials/ai_journalist_agent/README.md index 76b3df7..18aa2ae 100644 --- a/ai_agent_tutorials/ai_journalist_agent/README.md +++ b/ai_agent_tutorials/ai_journalist_agent/README.md @@ -12,6 +12,7 @@ This Streamlit app is an AI-powered journalist agent that generates high-quality ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_journalist_agent ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py b/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py index 48bde3b..af7390b 100644 --- a/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py +++ b/ai_agent_tutorials/ai_journalist_agent/journalist_agent.py @@ -1,10 +1,10 @@ # Import the required libraries from textwrap import dedent -from phi.assistant import Assistant -from phi.tools.serpapi_tools import SerpApiTools -from phi.tools.newspaper_toolkit import NewspaperToolkit +from agno.agent import Agent +from agno.tools.serpapi import SerpApiTools +from agno.tools.newspaper4k import Newspaper4kTools import streamlit as st -from phi.llm.openai import OpenAIChat +from agno.models.openai import OpenAIChat # Set up the Streamlit app st.title("AI Journalist Agent ๐Ÿ—ž๏ธ") @@ -17,10 +17,10 @@ openai_api_key = st.text_input("Enter OpenAI API Key to access GPT-4o", type="pa serp_api_key = st.text_input("Enter Serp API Key for Search functionality", type="password") if openai_api_key and serp_api_key: - searcher = Assistant( + searcher = Agent( name="Searcher", role="Searches for top URLs based on a topic", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), description=dedent( """\ You are a world-class journalist for the New York Times. Given a topic, generate a list of 3 search terms @@ -37,10 +37,10 @@ if openai_api_key and serp_api_key: tools=[SerpApiTools(api_key=serp_api_key)], add_datetime_to_instructions=True, ) - writer = Assistant( + writer = Agent( name="Writer", role="Retrieves text from URLs and writes a high-quality article", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), description=dedent( """\ You are a senior writer for the New York Times. Given a topic and a list of URLs, @@ -57,15 +57,14 @@ if openai_api_key and serp_api_key: "Focus on clarity, coherence, and overall quality.", "Never make up facts or plagiarize. Always provide proper attribution.", ], - tools=[NewspaperToolkit()], + tools=[Newspaper4kTools()], add_datetime_to_instructions=True, - add_chat_history_to_prompt=True, - num_history_messages=3, + markdown=True, ) - editor = Assistant( + editor = Agent( name="Editor", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), team=[searcher, writer], description="You are a senior NYT editor. Given a topic, your goal is to write a NYT worthy article.", instructions=[ @@ -88,4 +87,4 @@ if openai_api_key and serp_api_key: with st.spinner("Processing..."): # Get the response from the assistant response = editor.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/ai_agent_tutorials/ai_journalist_agent/requirements.txt b/ai_agent_tutorials/ai_journalist_agent/requirements.txt index 2316761..c97808a 100644 --- a/ai_agent_tutorials/ai_journalist_agent/requirements.txt +++ b/ai_agent_tutorials/ai_journalist_agent/requirements.txt @@ -1,6 +1,6 @@ streamlit -phidata +agno openai google-search-results -newspaper3k +newspaper4k lxml_html_clean \ No newline at end of file diff --git a/ai_agent_tutorials/ai_lead_generation_agent/README.md b/ai_agent_tutorials/ai_lead_generation_agent/README.md new file mode 100644 index 0000000..93da1f7 --- /dev/null +++ b/ai_agent_tutorials/ai_lead_generation_agent/README.md @@ -0,0 +1,35 @@ +## ๐ŸŽฏ AI Lead Generation Agent - Powered by Firecrawl's Extract Endpoint + +The AI Lead Generation Agent automates the process of finding and qualifying potential leads from Quora. It uses Firecrawl's search and the new Extract endpoint to identify relevant user profiles, extract valuable information, and organize it into a structured format in Google Sheets. This agent helps sales and marketing teams efficiently build targeted lead lists while saving hours of manual research. + +### Features +- **Targeted Search**: Uses Firecrawl's search endpoint to find relevant Quora URLs based on your search criteria +- **Intelligent Extraction**: Leverages Firecrawl's new Extract endpoint to pull user information from Quora profiles +- **Automated Processing**: Formats extracted user information into a clean, structured format +- **Google Sheets Integration**: Automatically creates and populates Google Sheets with lead information +- **Customizable Criteria**: Allows you to define specific search parameters to find your ideal leads for your niche + +### How to Get Started +1. **Clone the repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_lead_generation_agent + ``` +3. **Install the required packages**: + ```bash + pip install -r requirements.txt + ``` +4. **Important thing to do in composio**: + - in the terminal, run this command: `composio add googlesheets` + - In your compposio dashboard, create a new google sheet intergation and make sure it is active in the active integrations/connections tab + +5. **Set up your API keys**: + - Get your Firecrawl API key from [Firecrawl's website](https://www.firecrawl.dev/app/api-keys) + - Get your Composio API key from [Composio's website](https://composio.ai) + - Get your OpenAI API key from [OpenAI's website](https://platform.openai.com/api-keys) + +6. **Run the application**: + ```bash + streamlit run ai_lead_generation_agent.py + ``` + diff --git a/ai_agent_tutorials/ai_lead_generation_agent/ai_lead_generation_agent.py b/ai_agent_tutorials/ai_lead_generation_agent/ai_lead_generation_agent.py new file mode 100644 index 0000000..2814167 --- /dev/null +++ b/ai_agent_tutorials/ai_lead_generation_agent/ai_lead_generation_agent.py @@ -0,0 +1,209 @@ +import streamlit as st +import requests +from agno.agent import Agent +from agno.tools.firecrawl import FirecrawlTools +from agno.models.openai import OpenAIChat +from firecrawl import FirecrawlApp +from pydantic import BaseModel, Field +from typing import List +from composio_phidata import Action, ComposioToolSet +import json + +class QuoraUserInteractionSchema(BaseModel): + username: str = Field(description="The username of the user who posted the question or answer") + bio: str = Field(description="The bio or description of the user") + post_type: str = Field(description="The type of post, either 'question' or 'answer'") + timestamp: str = Field(description="When the question or answer was posted") + upvotes: int = Field(default=0, description="Number of upvotes received") + links: List[str] = Field(default_factory=list, description="Any links included in the post") + +class QuoraPageSchema(BaseModel): + interactions: List[QuoraUserInteractionSchema] = Field(description="List of all user interactions (questions and answers) on the page") + +def search_for_urls(company_description: str, firecrawl_api_key: str, num_links: int) -> List[str]: + url = "https://api.firecrawl.dev/v1/search" + headers = { + "Authorization": f"Bearer {firecrawl_api_key}", + "Content-Type": "application/json" + } + query1 = f"quora websites where people are looking for {company_description} services" + payload = { + "query": query1, + "limit": num_links, + "lang": "en", + "location": "United States", + "timeout": 60000, + } + response = requests.post(url, json=payload, headers=headers) + if response.status_code == 200: + data = response.json() + if data.get("success"): + results = data.get("data", []) + return [result["url"] for result in results] + return [] + +def extract_user_info_from_urls(urls: List[str], firecrawl_api_key: str) -> List[dict]: + user_info_list = [] + firecrawl_app = FirecrawlApp(api_key=firecrawl_api_key) + + try: + for url in urls: + response = firecrawl_app.extract( + [url], + { + 'prompt': 'Extract all user information including username, bio, post type (question/answer), timestamp, upvotes, and any links from Quora posts. Focus on identifying potential leads who are asking questions or providing answers related to the topic.', + 'schema': QuoraPageSchema.model_json_schema(), + } + ) + + if response.get('success') and response.get('status') == 'completed': + interactions = response.get('data', {}).get('interactions', []) + if interactions: + user_info_list.append({ + "website_url": url, + "user_info": interactions + }) + except Exception: + pass + + return user_info_list + +def format_user_info_to_flattened_json(user_info_list: List[dict]) -> List[dict]: + flattened_data = [] + + for info in user_info_list: + website_url = info["website_url"] + user_info = info["user_info"] + + for interaction in user_info: + flattened_interaction = { + "Website URL": website_url, + "Username": interaction.get("username", ""), + "Bio": interaction.get("bio", ""), + "Post Type": interaction.get("post_type", ""), + "Timestamp": interaction.get("timestamp", ""), + "Upvotes": interaction.get("upvotes", 0), + "Links": ", ".join(interaction.get("links", [])), + } + flattened_data.append(flattened_interaction) + + return flattened_data + +def create_google_sheets_agent(composio_api_key: str, openai_api_key: str) -> Agent: + composio_toolset = ComposioToolSet(api_key=composio_api_key) + google_sheets_tool = composio_toolset.get_tools(actions=[Action.GOOGLESHEETS_SHEET_FROM_JSON])[0] + + google_sheets_agent = Agent( + model=OpenAIChat(id="gpt-4o-mini", api_key=openai_api_key), + tools=[google_sheets_tool], + show_tool_calls=True, + system_prompt="You are an expert at creating and updating Google Sheets. You will be given user information in JSON format, and you need to write it into a new Google Sheet.", + markdown=True + ) + return google_sheets_agent + +def write_to_google_sheets(flattened_data: List[dict], composio_api_key: str, openai_api_key: str) -> str: + google_sheets_agent = create_google_sheets_agent(composio_api_key, openai_api_key) + + try: + message = ( + "Create a new Google Sheet with this data. " + "The sheet should have these columns: Website URL, Username, Bio, Post Type, Timestamp, Upvotes, and Links in the same order as mentioned. " + "Here's the data in JSON format:\n\n" + f"{json.dumps(flattened_data, indent=2)}" + ) + + create_sheet_response = google_sheets_agent.run(message) + + if "https://docs.google.com/spreadsheets/d/" in create_sheet_response.content: + google_sheets_link = create_sheet_response.content.split("https://docs.google.com/spreadsheets/d/")[1].split(" ")[0] + return f"https://docs.google.com/spreadsheets/d/{google_sheets_link}" + except Exception: + pass + return None + +def create_prompt_transformation_agent(openai_api_key: str) -> Agent: + return Agent( + model=OpenAIChat(id="gpt-4o-mini", api_key=openai_api_key), + system_prompt="""You are an expert at transforming detailed user queries into concise company descriptions. +Your task is to extract the core business/product focus in 3-4 words. + +Examples: +Input: "Generate leads looking for AI-powered customer support chatbots for e-commerce stores." +Output: "AI customer support chatbots for e commerce" + +Input: "Find people interested in voice cloning technology for creating audiobooks and podcasts" +Output: "voice cloning technology" + +Input: "Looking for users who need automated video editing software with AI capabilities" +Output: "AI video editing software" + +Input: "Need to find businesses interested in implementing machine learning solutions for fraud detection" +Output: "ML fraud detection" + +Always focus on the core product/service and keep it concise but clear.""", + markdown=True + ) + +def main(): + st.title("๐ŸŽฏ AI Lead Generation Agent") + st.info("This firecrawl powered agent helps you generate leads from Quora by searching for relevant posts and extracting user information.") + + with st.sidebar: + st.header("API Keys") + firecrawl_api_key = st.text_input("Firecrawl API Key", type="password") + st.caption(" Get your Firecrawl API key from [Firecrawl's website](https://www.firecrawl.dev/app/api-keys)") + openai_api_key = st.text_input("OpenAI API Key", type="password") + st.caption(" Get your OpenAI API key from [OpenAI's website](https://platform.openai.com/api-keys)") + composio_api_key = st.text_input("Composio API Key", type="password") + st.caption(" Get your Composio API key from [Composio's website](https://composio.ai)") + + num_links = st.number_input("Number of links to search", min_value=1, max_value=10, value=3) + + if st.button("Reset"): + st.session_state.clear() + st.experimental_rerun() + + user_query = st.text_area( + "Describe what kind of leads you're looking for:", + placeholder="e.g., Looking for users who need automated video editing software with AI capabilities", + help="Be specific about the product/service and target audience. The AI will convert this into a focused search query." + ) + + if st.button("Generate Leads"): + if not all([firecrawl_api_key, openai_api_key, composio_api_key, user_query]): + st.error("Please fill in all the API keys and describe what leads you're looking for.") + else: + with st.spinner("Processing your query..."): + transform_agent = create_prompt_transformation_agent(openai_api_key) + company_description = transform_agent.run(f"Transform this query into a concise 3-4 word company description: {user_query}") + st.write("๐ŸŽฏ Searching for:", company_description.content) + + with st.spinner("Searching for relevant URLs..."): + urls = search_for_urls(company_description.content, firecrawl_api_key, num_links) + + if urls: + st.subheader("Quora Links Used:") + for url in urls: + st.write(url) + + with st.spinner("Extracting user info from URLs..."): + user_info_list = extract_user_info_from_urls(urls, firecrawl_api_key) + + with st.spinner("Formatting user info..."): + flattened_data = format_user_info_to_flattened_json(user_info_list) + + with st.spinner("Writing to Google Sheets..."): + google_sheets_link = write_to_google_sheets(flattened_data, composio_api_key, openai_api_key) + + if google_sheets_link: + st.success("Lead generation and data writing to Google Sheets completed successfully!") + st.subheader("Google Sheets Link:") + st.markdown(f"[View Google Sheet]({google_sheets_link})") + else: + st.error("Failed to retrieve the Google Sheets link.") + else: + st.warning("No relevant URLs found.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_lead_generation_agent/requirements.txt b/ai_agent_tutorials/ai_lead_generation_agent/requirements.txt new file mode 100644 index 0000000..5fa3753 --- /dev/null +++ b/ai_agent_tutorials/ai_lead_generation_agent/requirements.txt @@ -0,0 +1,6 @@ +firecrawl-py==1.9.0 +agno +composio-phidata +composio==0.1.1 +pydantic==2.10.5 +streamlit \ No newline at end of file diff --git a/ai_agent_tutorials/ai_legal_agent_team/README.md b/ai_agent_tutorials/ai_legal_agent_team/README.md new file mode 100644 index 0000000..d97cd99 --- /dev/null +++ b/ai_agent_tutorials/ai_legal_agent_team/README.md @@ -0,0 +1,56 @@ +# ๐Ÿ‘จโ€โš–๏ธ AI Legal Agent Team + +A Streamlit application that simulates a full-service legal team using multiple AI agents to analyze legal documents and provide comprehensive legal insights. Each agent represents a different legal specialist role, from research and contract analysis to strategic planning, working together to provide thorough legal analysis and recommendations. + +## Features + +- **Specialized Legal AI Agent Team** + - **Legal Researcher**: Equipped with DuckDuckGo search tool to find and cite relevant legal cases and precedents. Provides detailed research summaries with sources and references specific sections from uploaded documents. + + - **Contract Analyst**: Specializes in thorough contract review, identifying key terms, obligations, and potential issues. References specific clauses from documents for detailed analysis. + + - **Legal Strategist**: Focuses on developing comprehensive legal strategies, providing actionable recommendations while considering both risks and opportunities. + + - **Team Lead**: Coordinates analysis between team members, ensures comprehensive responses, properly sourced recommendations, and references to specific document parts. Acts as an Agent Team coordinator for all three agents. + +- **Document Analysis Types** + - Contract Review - Done by Contract Analyst + - Legal Research - Done by Legal Researcher + - Risk Assessment - Done by Legal Strategist, Contract Analyst + - Compliance Check - Done by Legal Strategist, Legal Researcher, Contract Analyst + - Custom Queries - Done by Agent Team - Legal Researcher, Legal Strategist, Contract Analyst + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/ai_agent_tutorials/ai_legal_agent_team + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - Get OpenAI API key from [OpenAI Platform](https://platform.openai.com) + - Get Qdrant API key and URL from [Qdrant Cloud](https://cloud.qdrant.io) + +3. **Run the Application** + ```bash + streamlit run legal_agent_team.py + ``` +4. **Use the Interface** + - Enter API credentials + - Upload a legal document (PDF) + - Select analysis type + - Add custom queries if needed + - View analysis results + +## Notes + +- Supports PDF documents only +- Uses GPT-4o for analysis +- Uses text-embedding-3-small for embeddings +- Requires stable internet connection +- API usage costs apply diff --git a/ai_agent_tutorials/ai_legal_agent_team/legal_agent_team.py b/ai_agent_tutorials/ai_legal_agent_team/legal_agent_team.py new file mode 100644 index 0000000..46a705b --- /dev/null +++ b/ai_agent_tutorials/ai_legal_agent_team/legal_agent_team.py @@ -0,0 +1,400 @@ +import streamlit as st +from agno.agent import Agent +from agno.knowledge.pdf import PDFKnowledgeBase, PDFReader +from agno.vectordb.qdrant import Qdrant +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.models.openai import OpenAIChat +from agno.embedder.openai import OpenAIEmbedder +import tempfile +import os +from agno.document.chunking.document import DocumentChunking + +def init_session_state(): + """Initialize session state variables""" + if 'openai_api_key' not in st.session_state: + st.session_state.openai_api_key = None + if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = None + if 'qdrant_url' not in st.session_state: + st.session_state.qdrant_url = None + if 'vector_db' not in st.session_state: + st.session_state.vector_db = None + if 'legal_team' not in st.session_state: + st.session_state.legal_team = None + if 'knowledge_base' not in st.session_state: + st.session_state.knowledge_base = None + # Add a new state variable to track processed files + if 'processed_files' not in st.session_state: + st.session_state.processed_files = set() + +COLLECTION_NAME = "legal_documents" # Define your collection name + +def init_qdrant(): + """Initialize Qdrant client with configured settings.""" + if not all([st.session_state.qdrant_api_key, st.session_state.qdrant_url]): + return None + try: + # Create Agno's Qdrant instance which implements VectorDb + vector_db = Qdrant( + collection=COLLECTION_NAME, + url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key, + embedder=OpenAIEmbedder( + id="text-embedding-3-small", + api_key=st.session_state.openai_api_key + ) + ) + return vector_db + except Exception as e: + st.error(f"๐Ÿ”ด Qdrant connection failed: {str(e)}") + return None + +def process_document(uploaded_file, vector_db: Qdrant): + """ + Process document, create embeddings and store in Qdrant vector database + + Args: + uploaded_file: Streamlit uploaded file object + vector_db (Qdrant): Initialized Qdrant instance from Agno + + Returns: + PDFKnowledgeBase: Initialized knowledge base with processed documents + """ + if not st.session_state.openai_api_key: + raise ValueError("OpenAI API key not provided") + + os.environ['OPENAI_API_KEY'] = st.session_state.openai_api_key + + try: + # Save the uploaded file to a temporary location + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file: + temp_file.write(uploaded_file.getvalue()) + temp_file_path = temp_file.name + + st.info("Loading and processing document...") + + # Create a PDFKnowledgeBase with the vector_db + knowledge_base = PDFKnowledgeBase( + path=temp_file_path, # Single string path, not a list + vector_db=vector_db, + reader=PDFReader(), + chunking_strategy=DocumentChunking( + chunk_size=1000, + overlap=200 + ) + ) + + # Load the documents into the knowledge base + with st.spinner('๐Ÿ“ค Loading documents into knowledge base...'): + try: + knowledge_base.load(recreate=True, upsert=True) + st.success("โœ… Documents stored successfully!") + except Exception as e: + st.error(f"Error loading documents: {str(e)}") + raise + + # Clean up the temporary file + try: + os.unlink(temp_file_path) + except Exception: + pass + + return knowledge_base + + except Exception as e: + st.error(f"Document processing error: {str(e)}") + raise Exception(f"Error processing document: {str(e)}") + +def main(): + st.set_page_config(page_title="Legal Document Analyzer", layout="wide") + init_session_state() + + st.title("AI Legal Agent Team ๐Ÿ‘จโ€โš–๏ธ") + + with st.sidebar: + st.header("๐Ÿ”‘ API Configuration") + + openai_key = st.text_input( + "OpenAI API Key", + type="password", + value=st.session_state.openai_api_key if st.session_state.openai_api_key else "", + help="Enter your OpenAI API key" + ) + if openai_key: + st.session_state.openai_api_key = openai_key + + qdrant_key = st.text_input( + "Qdrant API Key", + type="password", + value=st.session_state.qdrant_api_key if st.session_state.qdrant_api_key else "", + help="Enter your Qdrant API key" + ) + if qdrant_key: + st.session_state.qdrant_api_key = qdrant_key + + qdrant_url = st.text_input( + "Qdrant URL", + value=st.session_state.qdrant_url if st.session_state.qdrant_url else "", + help="Enter your Qdrant instance URL" + ) + if qdrant_url: + st.session_state.qdrant_url = qdrant_url + + if all([st.session_state.qdrant_api_key, st.session_state.qdrant_url]): + try: + if not st.session_state.vector_db: + # Make sure we're initializing a QdrantClient here + st.session_state.vector_db = init_qdrant() + if st.session_state.vector_db: + st.success("Successfully connected to Qdrant!") + except Exception as e: + st.error(f"Failed to connect to Qdrant: {str(e)}") + + st.divider() + + if all([st.session_state.openai_api_key, st.session_state.vector_db]): + st.header("๐Ÿ“„ Document Upload") + uploaded_file = st.file_uploader("Upload Legal Document", type=['pdf']) + + if uploaded_file: + # Check if this file has already been processed + if uploaded_file.name not in st.session_state.processed_files: + with st.spinner("Processing document..."): + try: + # Process the document and get the knowledge base + knowledge_base = process_document(uploaded_file, st.session_state.vector_db) + + if knowledge_base: + st.session_state.knowledge_base = knowledge_base + # Add the file to processed files + st.session_state.processed_files.add(uploaded_file.name) + + # Initialize agents + legal_researcher = Agent( + name="Legal Researcher", + role="Legal research specialist", + model=OpenAIChat(id="gpt-4o"), + tools=[DuckDuckGoTools()], + knowledge=st.session_state.knowledge_base, + search_knowledge=True, + instructions=[ + "Find and cite relevant legal cases and precedents", + "Provide detailed research summaries with sources", + "Reference specific sections from the uploaded document", + "Always search the knowledge base for relevant information" + ], + show_tool_calls=True, + markdown=True + ) + + contract_analyst = Agent( + name="Contract Analyst", + role="Contract analysis specialist", + model=OpenAIChat(id="gpt-4o"), + knowledge=st.session_state.knowledge_base, + search_knowledge=True, + instructions=[ + "Review contracts thoroughly", + "Identify key terms and potential issues", + "Reference specific clauses from the document" + ], + markdown=True + ) + + legal_strategist = Agent( + name="Legal Strategist", + role="Legal strategy specialist", + model=OpenAIChat(id="gpt-4o"), + knowledge=st.session_state.knowledge_base, + search_knowledge=True, + instructions=[ + "Develop comprehensive legal strategies", + "Provide actionable recommendations", + "Consider both risks and opportunities" + ], + markdown=True + ) + + # Legal Agent Team + st.session_state.legal_team = Agent( + name="Legal Team Lead", + role="Legal team coordinator", + model=OpenAIChat(id="gpt-4o"), + team=[legal_researcher, contract_analyst, legal_strategist], + knowledge=st.session_state.knowledge_base, + search_knowledge=True, + instructions=[ + "Coordinate analysis between team members", + "Provide comprehensive responses", + "Ensure all recommendations are properly sourced", + "Reference specific parts of the uploaded document", + "Always search the knowledge base before delegating tasks" + ], + show_tool_calls=True, + markdown=True + ) + + st.success("โœ… Document processed and team initialized!") + + except Exception as e: + st.error(f"Error processing document: {str(e)}") + else: + # File already processed, just show a message + st.success("โœ… Document already processed and team ready!") + + st.divider() + st.header("๐Ÿ” Analysis Options") + analysis_type = st.selectbox( + "Select Analysis Type", + [ + "Contract Review", + "Legal Research", + "Risk Assessment", + "Compliance Check", + "Custom Query" + ] + ) + else: + st.warning("Please configure all API credentials to proceed") + + # Main content area + if not all([st.session_state.openai_api_key, st.session_state.vector_db]): + st.info("๐Ÿ‘ˆ Please configure your API credentials in the sidebar to begin") + elif not uploaded_file: + st.info("๐Ÿ‘ˆ Please upload a legal document to begin analysis") + elif st.session_state.legal_team: + # Create a dictionary for analysis type icons + analysis_icons = { + "Contract Review": "๐Ÿ“‘", + "Legal Research": "๐Ÿ”", + "Risk Assessment": "โš ๏ธ", + "Compliance Check": "โœ…", + "Custom Query": "๐Ÿ’ญ" + } + + # Dynamic header with icon + st.header(f"{analysis_icons[analysis_type]} {analysis_type} Analysis") + + analysis_configs = { + "Contract Review": { + "query": "Review this contract and identify key terms, obligations, and potential issues.", + "agents": ["Contract Analyst"], + "description": "Detailed contract analysis focusing on terms and obligations" + }, + "Legal Research": { + "query": "Research relevant cases and precedents related to this document.", + "agents": ["Legal Researcher"], + "description": "Research on relevant legal cases and precedents" + }, + "Risk Assessment": { + "query": "Analyze potential legal risks and liabilities in this document.", + "agents": ["Contract Analyst", "Legal Strategist"], + "description": "Combined risk analysis and strategic assessment" + }, + "Compliance Check": { + "query": "Check this document for regulatory compliance issues.", + "agents": ["Legal Researcher", "Contract Analyst", "Legal Strategist"], + "description": "Comprehensive compliance analysis" + }, + "Custom Query": { + "query": None, + "agents": ["Legal Researcher", "Contract Analyst", "Legal Strategist"], + "description": "Custom analysis using all available agents" + } + } + + st.info(f"๐Ÿ“‹ {analysis_configs[analysis_type]['description']}") + st.write(f"๐Ÿค– Active Legal AI Agents: {', '.join(analysis_configs[analysis_type]['agents'])}") #dictionary!! + + # Replace the existing user_query section with this: + if analysis_type == "Custom Query": + user_query = st.text_area( + "Enter your specific query:", + help="Add any specific questions or points you want to analyze" + ) + else: + user_query = None # Set to None for non-custom queries + + + if st.button("Analyze"): + if analysis_type == "Custom Query" and not user_query: + st.warning("Please enter a query") + else: + with st.spinner("Analyzing document..."): + try: + # Ensure OpenAI API key is set + os.environ['OPENAI_API_KEY'] = st.session_state.openai_api_key + + # Combine predefined and user queries + if analysis_type != "Custom Query": + combined_query = f""" + Using the uploaded document as reference: + + Primary Analysis Task: {analysis_configs[analysis_type]['query']} + Focus Areas: {', '.join(analysis_configs[analysis_type]['agents'])} + + Please search the knowledge base and provide specific references from the document. + """ + else: + combined_query = f""" + Using the uploaded document as reference: + + {user_query} + + Please search the knowledge base and provide specific references from the document. + Focus Areas: {', '.join(analysis_configs[analysis_type]['agents'])} + """ + + response = st.session_state.legal_team.run(combined_query) + + # Display results in tabs + tabs = st.tabs(["Analysis", "Key Points", "Recommendations"]) + + with tabs[0]: + st.markdown("### Detailed Analysis") + if response.content: + st.markdown(response.content) + else: + for message in response.messages: + if message.role == 'assistant' and message.content: + st.markdown(message.content) + + with tabs[1]: + st.markdown("### Key Points") + key_points_response = st.session_state.legal_team.run( + f"""Based on this previous analysis: + {response.content} + + Please summarize the key points in bullet points. + Focus on insights from: {', '.join(analysis_configs[analysis_type]['agents'])}""" + ) + if key_points_response.content: + st.markdown(key_points_response.content) + else: + for message in key_points_response.messages: + if message.role == 'assistant' and message.content: + st.markdown(message.content) + + with tabs[2]: + st.markdown("### Recommendations") + recommendations_response = st.session_state.legal_team.run( + f"""Based on this previous analysis: + {response.content} + + What are your key recommendations based on the analysis, the best course of action? + Provide specific recommendations from: {', '.join(analysis_configs[analysis_type]['agents'])}""" + ) + if recommendations_response.content: + st.markdown(recommendations_response.content) + else: + for message in recommendations_response.messages: + if message.role == 'assistant' and message.content: + st.markdown(message.content) + + except Exception as e: + st.error(f"Error during analysis: {str(e)}") + else: + st.info("Please upload a legal document to begin analysis") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/README.md b/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/local_legal_agent.py b/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/local_legal_agent.py new file mode 100644 index 0000000..16c0e8f --- /dev/null +++ b/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/local_legal_agent.py @@ -0,0 +1,278 @@ +import streamlit as st +from agno.agent import Agent +from agno.knowledge.pdf import PDFKnowledgeBase, PDFReader +from agno.vectordb.qdrant import Qdrant +from agno.models.ollama import Ollama +from agno.embedder.ollama import OllamaEmbedder +import tempfile +import os + +def init_session_state(): + if 'vector_db' not in st.session_state: + st.session_state.vector_db = None + if 'legal_team' not in st.session_state: + st.session_state.legal_team = None + if 'knowledge_base' not in st.session_state: + st.session_state.knowledge_base = None + +def init_qdrant(): + """Initialize local Qdrant vector database""" + return Qdrant( + collection="legal_knowledge", + url="http://localhost:6333", + embedder=OllamaEmbedder(model="openhermes") + ) + +def process_document(uploaded_file, vector_db: Qdrant): + """Process document using local resources""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_file_path = os.path.join(temp_dir, uploaded_file.name) + with open(temp_file_path, "wb") as f: + f.write(uploaded_file.getbuffer()) + + try: + st.write("Processing document...") + # Create knowledge base with local embedder + knowledge_base = PDFKnowledgeBase( + path=temp_dir, + vector_db=vector_db, + reader=PDFReader(chunk=True), + recreate_vector_db=True + ) + + st.write("Loading knowledge base...") + knowledge_base.load() + + # Verify knowledge base + st.write("Verifying knowledge base...") + test_results = knowledge_base.search("test") + if not test_results: + raise Exception("Knowledge base verification failed") + + st.write("Knowledge base ready!") + return knowledge_base + + except Exception as e: + raise Exception(f"Error processing document: {str(e)}") + +def main(): + st.set_page_config(page_title="Local Legal Document Analyzer", layout="wide") + init_session_state() + + st.title("Local AI Legal Agent Team") + + # Initialize local Qdrant + if not st.session_state.vector_db: + try: + st.session_state.vector_db = init_qdrant() + st.success("Connected to local Qdrant!") + except Exception as e: + st.error(f"Failed to connect to Qdrant: {str(e)}") + return + + # Document upload section + st.header("๐Ÿ“„ Document Upload") + uploaded_file = st.file_uploader("Upload Legal Document", type=['pdf']) + + if uploaded_file: + with st.spinner("Processing document..."): + try: + knowledge_base = process_document(uploaded_file, st.session_state.vector_db) + st.session_state.knowledge_base = knowledge_base + + # Initialize agents with Llama model + legal_researcher = Agent( + name="Legal Researcher", + role="Legal research specialist", + model=Ollama(id="llama3.1:8b"), + knowledge=st.session_state.knowledge_base, + search_knowledge=True, + instructions=[ + "Find and cite relevant legal cases and precedents", + "Provide detailed research summaries with sources", + "Reference specific sections from the uploaded document" + ], + markdown=True + ) + + contract_analyst = Agent( + name="Contract Analyst", + role="Contract analysis specialist", + model=Ollama(id="llama3.1:8b"), + knowledge=knowledge_base, + search_knowledge=True, + instructions=[ + "Review contracts thoroughly", + "Identify key terms and potential issues", + "Reference specific clauses from the document" + ], + markdown=True + ) + + legal_strategist = Agent( + name="Legal Strategist", + role="Legal strategy specialist", + model=Ollama(id="llama3.1:8b"), + knowledge=knowledge_base, + search_knowledge=True, + instructions=[ + "Develop comprehensive legal strategies", + "Provide actionable recommendations", + "Consider both risks and opportunities" + ], + markdown=True + ) + + # Legal Agent Team + st.session_state.legal_team = Agent( + name="Legal Team Lead", + role="Legal team coordinator", + model=Ollama(id="llama3.1:8b"), + team=[legal_researcher, contract_analyst, legal_strategist], + knowledge=st.session_state.knowledge_base, + search_knowledge=True, + instructions=[ + "Coordinate analysis between team members", + "Provide comprehensive responses", + "Ensure all recommendations are properly sourced", + "Reference specific parts of the uploaded document" + ], + markdown=True + ) + + st.success("โœ… Document processed and team initialized!") + + except Exception as e: + st.error(f"Error processing document: {str(e)}") + + st.divider() + st.header("๐Ÿ” Analysis Options") + analysis_type = st.selectbox( + "Select Analysis Type", + [ + "Contract Review", + "Legal Research", + "Risk Assessment", + "Compliance Check", + "Custom Query" + ] + ) + + # Main content area + if not st.session_state.vector_db: + st.info("๐Ÿ‘ˆ Waiting for Qdrant connection...") + elif not uploaded_file: + st.info("๐Ÿ‘ˆ Please upload a legal document to begin analysis") + elif st.session_state.legal_team: + st.header("Document Analysis") + + analysis_configs = { + "Contract Review": { + "query": "Review this contract and identify key terms, obligations, and potential issues.", + "agents": ["Contract Analyst"], + "description": "Detailed contract analysis focusing on terms and obligations" + }, + "Legal Research": { + "query": "Research relevant cases and precedents related to this document.", + "agents": ["Legal Researcher"], + "description": "Research on relevant legal cases and precedents" + }, + "Risk Assessment": { + "query": "Analyze potential legal risks and liabilities in this document.", + "agents": ["Contract Analyst", "Legal Strategist"], + "description": "Combined risk analysis and strategic assessment" + }, + "Compliance Check": { + "query": "Check this document for regulatory compliance issues.", + "agents": ["Legal Researcher", "Contract Analyst", "Legal Strategist"], + "description": "Comprehensive compliance analysis" + }, + "Custom Query": { + "query": None, + "agents": ["Legal Researcher", "Contract Analyst", "Legal Strategist"], + "description": "Custom analysis using all available agents" + } + } + + st.info(f"๐Ÿ“‹ {analysis_configs[analysis_type]['description']}") + st.write(f"๐Ÿค– Active Agents: {', '.join(analysis_configs[analysis_type]['agents'])}") + + user_query = st.text_area( + "Enter your specific query:", + help="Add any specific questions or points you want to analyze" + ) + + if st.button("Analyze"): + if user_query or analysis_type != "Custom Query": + with st.spinner("Analyzing document..."): + try: + # Combine predefined and user queries + if analysis_type != "Custom Query": + combined_query = f""" + Using the uploaded document as reference: + + Primary Analysis Task: {analysis_configs[analysis_type]['query']} + Additional User Query: {user_query if user_query else 'None'} + + Focus Areas: {', '.join(analysis_configs[analysis_type]['agents'])} + + Please search the knowledge base and provide specific references from the document. + """ + else: + combined_query = user_query + + response = st.session_state.legal_team.run(combined_query) + + # Display results in tabs + tabs = st.tabs(["Analysis", "Key Points", "Recommendations"]) + + with tabs[0]: + st.markdown("### Detailed Analysis") + if response.content: + st.markdown(response.content) + else: + for message in response.messages: + if message.role == 'assistant' and message.content: + st.markdown(message.content) + + with tabs[1]: + st.markdown("### Key Points") + key_points_response = st.session_state.legal_team.run( + f"""Based on this previous analysis: + {response.content} + + Please summarize the key points in bullet points. + Focus on insights from: {', '.join(analysis_configs[analysis_type]['agents'])}""" + ) + if key_points_response.content: + st.markdown(key_points_response.content) + else: + for message in key_points_response.messages: + if message.role == 'assistant' and message.content: + st.markdown(message.content) + + with tabs[2]: + st.markdown("### Recommendations") + recommendations_response = st.session_state.legal_team.run( + f"""Based on this previous analysis: + {response.content} + + What are your key recommendations based on the analysis, the best course of action? + Provide specific recommendations from: {', '.join(analysis_configs[analysis_type]['agents'])}""" + ) + if recommendations_response.content: + st.markdown(recommendations_response.content) + else: + for message in recommendations_response.messages: + if message.role == 'assistant' and message.content: + st.markdown(message.content) + + except Exception as e: + st.error(f"Error during analysis: {str(e)}") + else: + st.warning("Please enter a query or select an analysis type") + else: + st.info("Please upload a legal document to begin analysis") + +if __name__ == "__main__": + main() diff --git a/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/requirements.txt b/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/requirements.txt new file mode 100644 index 0000000..e23a639 --- /dev/null +++ b/ai_agent_tutorials/ai_legal_agent_team/local_ai_legal_agent_team/requirements.txt @@ -0,0 +1,4 @@ +agno +streamlit==1.40.2 +qdrant-client==1.12.1 +ollama==0.4.4 diff --git a/ai_agent_tutorials/ai_legal_agent_team/requirements.txt b/ai_agent_tutorials/ai_legal_agent_team/requirements.txt new file mode 100644 index 0000000..c6cda18 --- /dev/null +++ b/ai_agent_tutorials/ai_legal_agent_team/requirements.txt @@ -0,0 +1,6 @@ +agno +streamlit==1.40.2 +qdrant-client==1.12.1 +openai +pypdf +duckduckgo-search diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/README.md b/ai_agent_tutorials/ai_medical_imaging_agent/README.md new file mode 100644 index 0000000..57801ff --- /dev/null +++ b/ai_agent_tutorials/ai_medical_imaging_agent/README.md @@ -0,0 +1,66 @@ +# ๐Ÿฉป Medical Imaging Diagnosis Agent + +A Medical Imaging Diagnosis Agent build on agno powered by Gemini 2.0 Flash that provides AI-assisted analysis of medical images of various scans. The agent acts as a medical imaging diagnosis expert to analyze various types of medical images and videos, providing detailed diagnostic insights and explanations. + +## Features + +- **Comprehensive Image Analysis** + - Image Type Identification (X-ray, MRI, CT scan, ultrasound) + - Anatomical Region Detection + - Key Findings and Observations + - Potential Abnormalities Detection + - Image Quality Assessment + - Research and Reference + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_medical_imaging_agent + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - Get Google API key from [Google AI Studio](https://aistudio.google.com) + +3. **Run the Application** + ```bash + streamlit run ai_medical_imaging.py + ``` + +## Analysis Components + +- **Image Type and Region** + - Identifies imaging modality + - Specifies anatomical region + +- **Key Findings** + - Systematic listing of observations + - Detailed appearance descriptions + - Abnormality highlighting + +- **Diagnostic Assessment** + - Potential diagnoses ranking + - Differential diagnoses + - Severity assessment + +- **Patient-Friendly Explanations** + - Simplified terminology + - Detailed first-principles explanations + - Visual reference points + +## Notes + +- Uses Gemini 2.0 Flash for analysis +- Requires stable internet connection +- Free API usage costs - 1,500 free requests per day by google! +- For educational and development purposes only +- Not a replacement for professional medical diagnosis + +## Disclaimer + +This tool is for educational and informational purposes only. All analyses should be reviewed by qualified healthcare professionals. Do not make medical decisions based solely on this analysis. \ No newline at end of file diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py b/ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py new file mode 100644 index 0000000..b02d79e --- /dev/null +++ b/ai_agent_tutorials/ai_medical_imaging_agent/ai_medical_imaging.py @@ -0,0 +1,156 @@ +import os +from PIL import Image as PILImage +from agno.agent import Agent +from agno.models.google import Gemini +import streamlit as st +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.media import Image as AgnoImage + +if "GOOGLE_API_KEY" not in st.session_state: + st.session_state.GOOGLE_API_KEY = None + +with st.sidebar: + st.title("โ„น๏ธ Configuration") + + if not st.session_state.GOOGLE_API_KEY: + api_key = st.text_input( + "Enter your Google API Key:", + type="password" + ) + st.caption( + "Get your API key from [Google AI Studio]" + "(https://aistudio.google.com/apikey) ๐Ÿ”‘" + ) + if api_key: + st.session_state.GOOGLE_API_KEY = api_key + st.success("API Key saved!") + st.rerun() + else: + st.success("API Key is configured") + if st.button("๐Ÿ”„ Reset API Key"): + st.session_state.GOOGLE_API_KEY = None + st.rerun() + + st.info( + "This tool provides AI-powered analysis of medical imaging data using " + "advanced computer vision and radiological expertise." + ) + st.warning( + "โš DISCLAIMER: This tool is for educational and informational purposes only. " + "All analyses should be reviewed by qualified healthcare professionals. " + "Do not make medical decisions based solely on this analysis." + ) + +medical_agent = Agent( + model=Gemini( + id="gemini-2.0-flash", + api_key=st.session_state.GOOGLE_API_KEY + ), + tools=[DuckDuckGoTools()], + markdown=True +) if st.session_state.GOOGLE_API_KEY else None + +if not medical_agent: + st.warning("Please configure your API key in the sidebar to continue") + +# Medical Analysis Query +query = """ +You are a highly skilled medical imaging expert with extensive knowledge in radiology and diagnostic imaging. Analyze the patient's medical image and structure your response as follows: + +### 1. Image Type & Region +- Specify imaging modality (X-ray/MRI/CT/Ultrasound/etc.) +- Identify the patient's anatomical region and positioning +- Comment on image quality and technical adequacy + +### 2. Key Findings +- List primary observations systematically +- Note any abnormalities in the patient's imaging with precise descriptions +- Include measurements and densities where relevant +- Describe location, size, shape, and characteristics +- Rate severity: Normal/Mild/Moderate/Severe + +### 3. Diagnostic Assessment +- Provide primary diagnosis with confidence level +- List differential diagnoses in order of likelihood +- Support each diagnosis with observed evidence from the patient's imaging +- Note any critical or urgent findings + +### 4. Patient-Friendly Explanation +- Explain the findings in simple, clear language that the patient can understand +- Avoid medical jargon or provide clear definitions +- Include visual analogies if helpful +- Address common patient concerns related to these findings + +### 5. Research Context +IMPORTANT: Use the DuckDuckGo search tool to: +- Find recent medical literature about similar cases +- Search for standard treatment protocols +- Provide a list of relevant medical links of them too +- Research any relevant technological advances +- Include 2-3 key references to support your analysis + +Format your response using clear markdown headers and bullet points. Be concise yet thorough. +""" + +st.title("๐Ÿฅ Medical Imaging Diagnosis Agent") +st.write("Upload a medical image for professional analysis") + +# Create containers for better organization +upload_container = st.container() +image_container = st.container() +analysis_container = st.container() + +with upload_container: + uploaded_file = st.file_uploader( + "Upload Medical Image", + type=["jpg", "jpeg", "png", "dicom"], + help="Supported formats: JPG, JPEG, PNG, DICOM" + ) + +if uploaded_file is not None: + with image_container: + col1, col2, col3 = st.columns([1, 2, 1]) + with col2: + image = PILImage.open(uploaded_file) + width, height = image.size + aspect_ratio = width / height + new_width = 500 + new_height = int(new_width / aspect_ratio) + resized_image = image.resize((new_width, new_height)) + + st.image( + resized_image, + caption="Uploaded Medical Image", + use_container_width=True + ) + + analyze_button = st.button( + "๐Ÿ” Analyze Image", + type="primary", + use_container_width=True + ) + + with analysis_container: + if analyze_button: + with st.spinner("๐Ÿ”„ Analyzing image... Please wait."): + try: + temp_path = "temp_resized_image.png" + resized_image.save(temp_path) + + # Create AgnoImage object + agno_image = AgnoImage(filepath=temp_path) # Adjust if constructor differs + + # Run analysis + response = medical_agent.run(query, images=[agno_image]) + st.markdown("### ๐Ÿ“‹ Analysis Results") + st.markdown("---") + st.markdown(response.content) + st.markdown("---") + st.caption( + "Note: This analysis is generated by AI and should be reviewed by " + "a qualified healthcare professional." + ) + except Exception as e: + st.error(f"Analysis error: {e}") +else: + st.info("๐Ÿ‘† Please upload a medical image to begin analysis") diff --git a/ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt b/ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt new file mode 100644 index 0000000..9eb6d27 --- /dev/null +++ b/ai_agent_tutorials/ai_medical_imaging_agent/requirements.txt @@ -0,0 +1,5 @@ +streamlit==1.40.2 +agno +Pillow==10.0.0 +duckduckgo-search==6.4.1 +google-generativeai==0.8.3 \ No newline at end of file diff --git a/ai_agent_tutorials/ai_meeting_agent/README.md b/ai_agent_tutorials/ai_meeting_agent/README.md index dfa738c..b93679b 100644 --- a/ai_agent_tutorials/ai_meeting_agent/README.md +++ b/ai_agent_tutorials/ai_meeting_agent/README.md @@ -14,6 +14,7 @@ This Streamlit application leverages multiple AI agents to create comprehensive ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_meeting_agent ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/ai_meeting_agent/meeting_agent.py b/ai_agent_tutorials/ai_meeting_agent/meeting_agent.py index d9d7e70..652935c 100644 --- a/ai_agent_tutorials/ai_meeting_agent/meeting_agent.py +++ b/ai_agent_tutorials/ai_meeting_agent/meeting_agent.py @@ -1,7 +1,6 @@ import streamlit as st -from crewai import Agent, Task, Crew, Process -from langchain_openai import ChatOpenAI -from langchain_anthropic import ChatAnthropic +from crewai import Agent, Task, Crew, LLM +from crewai.process import Process from crewai_tools import SerperDevTool import os @@ -11,20 +10,16 @@ st.title("AI Meeting Preparation Agent ๐Ÿ“") # Sidebar for API keys st.sidebar.header("API Keys") -openai_api_key = st.sidebar.text_input("OpenAI API Key", type="password") anthropic_api_key = st.sidebar.text_input("Anthropic API Key", type="password") serper_api_key = st.sidebar.text_input("Serper API Key", type="password") # Check if all API keys are set -if openai_api_key and anthropic_api_key and serper_api_key: - # Set API keys as environment variables - os.environ["OPENAI_API_KEY"] = openai_api_key +if anthropic_api_key and serper_api_key: + # # Set API keys as environment variables os.environ["ANTHROPIC_API_KEY"] = anthropic_api_key os.environ["SERPER_API_KEY"] = serper_api_key - # Initialize the AI models and tools - gpt4 = ChatOpenAI(model_name="gpt-4o-mini") - claude = ChatAnthropic(model_name="claude-3-5-sonnet-20240620") + claude = LLM(model="claude-3-5-sonnet-20240620", temperature= 0.7, api_key=anthropic_api_key) search_tool = SerperDevTool() # Input fields @@ -41,7 +36,7 @@ if openai_api_key and anthropic_api_key and serper_api_key: backstory='You are an expert at quickly understanding complex business contexts and identifying critical information.', verbose=True, allow_delegation=False, - llm=gpt4, + llm=claude, tools=[search_tool] ) @@ -51,7 +46,7 @@ if openai_api_key and anthropic_api_key and serper_api_key: backstory='You are a seasoned industry analyst with a knack for spotting emerging trends and opportunities.', verbose=True, allow_delegation=False, - llm=gpt4, + llm=claude, tools=[search_tool] ) diff --git a/ai_agent_tutorials/ai_meme_generator_agent_browseruse/README.md b/ai_agent_tutorials/ai_meme_generator_agent_browseruse/README.md new file mode 100644 index 0000000..ee3dd35 --- /dev/null +++ b/ai_agent_tutorials/ai_meme_generator_agent_browseruse/README.md @@ -0,0 +1,51 @@ +# ๐Ÿฅธ AI Meme Generator Agent - Browser Use + +The AI Meme Generator Agent is a powerful browser automation tool that creates memes using AI agents. This app combines multi-LLM capabilities with automated browser interactions to generate memes based on text prompts through direct website manipulation. + +## Features + +- **Multi-LLM Support** + - Claude 3.5 Sonnet (Anthropic) + - GPT-4o (OpenAI) + - Deepseek v3 (Deepseek) + - Automatic model switching with API key validation + +- **Browser Automation**: + - Direct interaction with imgflip.com meme templates + - Automated search for relevant meme formats + - Dynamic text insertion for top/bottom captions + - Image link extraction from generated memes + +- **Smart Generation Workflow**: + - Action verb extraction from prompts + - Metaphorical template matching + - Multi-step quality validation + - Automatic retry mechanism for failed generations + +- **User-Friendly Interface**: + - Model configuration sidebar + - API key management + - Direct meme preview with clickable links + - Responsive error handling + + +API keys required: +- **Anthropic** (for Claude) +- **Deepseek** +- **OpenAI** (for GPT-4o) + +## How to Run + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_meme_generator_browseruse + ``` +2. **Install the dependencies**: + ```bash + pip install -r requirements.txt + ``` +3. **Run the Streamlit app**: + ```bash + streamlit run ai_meme_generator.py + ``` \ No newline at end of file diff --git a/ai_agent_tutorials/ai_meme_generator_agent_browseruse/ai_meme_generator_agent.py b/ai_agent_tutorials/ai_meme_generator_agent_browseruse/ai_meme_generator_agent.py new file mode 100644 index 0000000..23a8821 --- /dev/null +++ b/ai_agent_tutorials/ai_meme_generator_agent_browseruse/ai_meme_generator_agent.py @@ -0,0 +1,130 @@ +import asyncio +import streamlit as st +from browser_use import Agent, SystemPrompt +from langchain_openai import ChatOpenAI +from langchain_anthropic import ChatAnthropic +from langchain_core.messages import HumanMessage +import re + +async def generate_meme(query: str, model_choice: str, api_key: str) -> None: + # Initialize the appropriate LLM based on user selection + if model_choice == "Claude": + llm = ChatAnthropic( + model="claude-3-5-sonnet-20241022", + api_key=api_key + ) + elif model_choice == "Deepseek": + llm = ChatOpenAI( + base_url='https://api.deepseek.com/v1', + model='deepseek-chat', + api_key=api_key, + temperature=0.3 + ) + else: # OpenAI + llm = ChatOpenAI( + model="gpt-4o", + api_key=api_key, + temperature=0.0 + ) + + task_description = ( + "You are a meme generator expert. You are given a query and you need to generate a meme for it.\n" + "1. Go to https://imgflip.com/memetemplates \n" + "2. Click on the Search bar in the middle and search for ONLY ONE MAIN ACTION VERB (like 'bully', 'laugh', 'cry') in this query: '{0}'\n" + "3. Choose any meme template that metaphorically fits the meme topic: '{0}'\n" + " by clicking on the 'Add Caption' button below it\n" + "4. Write a Top Text (setup/context) and Bottom Text (punchline/outcome) related to '{0}'.\n" + "5. Check the preview making sure it is funny and a meaningful meme. Adjust text directly if needed. \n" + "6. Look at the meme and text on it, if it doesnt make sense, PLEASE retry by filling the text boxes with different text. \n" + "7. Click on the Generate meme button to generate the meme\n" + "8. Copy the image link and give it as the output\n" + ).format(query) + + agent = Agent( + task=task_description, + llm=llm, + max_actions_per_step=5, + max_failures=25, + use_vision=(model_choice != "Deepseek") + ) + + history = await agent.run() + + # Extract final result from agent history + final_result = history.final_result() + + # Use regex to find the meme URL in the result + url_match = re.search(r'https://imgflip\.com/i/(\w+)', final_result) + if url_match: + meme_id = url_match.group(1) + return f"https://i.imgflip.com/{meme_id}.jpg" + return None + +def main(): + # Custom CSS styling + + + st.title("๐Ÿฅธ AI Meme Generator Agent - Browser Use") + st.info("This AI browser agent does browser automation to generate memes based on your input with browser use. Please enter your API key and describe the meme you want to generate.") + + # Sidebar configuration + with st.sidebar: + st.markdown('', unsafe_allow_html=True) + + # Model selection + model_choice = st.selectbox( + "Select AI Model", + ["Claude", "Deepseek", "OpenAI"], + index=0, + help="Choose which LLM to use for meme generation" + ) + + # API key input based on model selection + api_key = "" + if model_choice == "Claude": + api_key = st.text_input("Claude API Key", type="password", + help="Get your API key from https://console.anthropic.com") + elif model_choice == "Deepseek": + api_key = st.text_input("Deepseek API Key", type="password", + help="Get your API key from https://platform.deepseek.com") + else: + api_key = st.text_input("OpenAI API Key", type="password", + help="Get your API key from https://platform.openai.com") + + # Main content area + st.markdown('

๐ŸŽจ Describe Your Meme Concept

', unsafe_allow_html=True) + + query = st.text_input( + "Meme Idea Input", + placeholder="Example: 'Ilya's SSI quietly looking at the OpenAI vs Deepseek debate while diligently working on ASI'", + label_visibility="collapsed" + ) + + if st.button("Generate Meme ๐Ÿš€"): + if not api_key: + st.warning(f"Please provide the {model_choice} API key") + st.stop() + if not query: + st.warning("Please enter a meme idea") + st.stop() + + with st.spinner(f"๐Ÿง  {model_choice} is generating your meme..."): + try: + meme_url = asyncio.run(generate_meme(query, model_choice, api_key)) + + if meme_url: + st.success("โœ… Meme Generated Successfully!") + st.image(meme_url, caption="Generated Meme Preview", use_container_width=True) + st.markdown(f""" + **Direct Link:** [Open in ImgFlip]({meme_url}) + **Embed URL:** `{meme_url}` + """) + else: + st.error("โŒ Failed to generate meme. Please try again with a different prompt.") + + except Exception as e: + st.error(f"Error: {str(e)}") + st.info("๐Ÿ’ก If using OpenAI, ensure your account has GPT-4o access") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_meme_generator_agent_browseruse/requirements.txt b/ai_agent_tutorials/ai_meme_generator_agent_browseruse/requirements.txt new file mode 100644 index 0000000..0918b46 --- /dev/null +++ b/ai_agent_tutorials/ai_meme_generator_agent_browseruse/requirements.txt @@ -0,0 +1,6 @@ +streamlit +browser-use==0.1.26 +playwright==1.49.1 +langchain-openai +langchain-anthropic +asyncio diff --git a/ai_agent_tutorials/ai_mental_wellbeing_agent/README.md b/ai_agent_tutorials/ai_mental_wellbeing_agent/README.md new file mode 100644 index 0000000..a70b0bc --- /dev/null +++ b/ai_agent_tutorials/ai_mental_wellbeing_agent/README.md @@ -0,0 +1,73 @@ +# AI Mental Wellbeing Agent Team ๐Ÿง  + +The AI Mental Wellbeing Agent Team is a supportive mental health assessment and guidance system powered by [AG2](https://github.com/ag2ai/ag2?tab=readme-ov-file)(formerly AutoGen)'s AI Agent framework. This app provides personalized mental health support through the coordination of specialized AI agents, each focusing on different aspects of mental health care based on user inputs such as emotional state, stress levels, sleep patterns, and current symptoms. This is built on AG2's new swarm feature run through initiate_swarm_chat() method. + +## Features + +- **Specialized Mental Wellbeing Support Team** + - ๐Ÿง  **Assessment Agent**: Analyzes emotional state and psychological needs with clinical precision and empathy + - ๐ŸŽฏ **Action Agent**: Creates immediate action plans and connects users with appropriate resources + - ๐Ÿ”„ **Follow-up Agent**: Designs long-term support strategies and prevention plans + +- **Comprehensive Mental Wellbeing Support**: + - Detailed psychological assessment + - Immediate coping strategies + - Resource recommendations + - Long-term support planning + - Crisis prevention strategies + - Progress monitoring systems + +- **Customizable Input Parameters**: + - Current emotional state + - Sleep patterns + - Stress levels + - Support system information + - Recent life changes + - Current symptoms + +- **Interactive Results**: + - Real-time assessment summaries + - Detailed recommendations in expandable sections + - Clear action steps and resources + - Long-term support strategies + +## How to Run + +Follow these steps to set up and run the application: + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_mental_wellbeing_agent + ``` + +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Create Environment File**: + Create a `.env` file in the project directory: + ```bash + echo "AUTOGEN_USE_DOCKER=0" > .env + ``` + This disables Docker requirement for code execution in AutoGen. + +4. **Set Up OpenAI API Key**: + - Obtain an OpenAI API key from [OpenAI's platform](https://platform.openai.com) + - You'll input this key in the app's sidebar when running + +5. **Run the Streamlit App**: + ```bash + streamlit run ai_mental_wellbeing_agent.py + ``` + + +## โš ๏ธ Important Notice + +This application is a supportive tool and does not replace professional mental health care. If you're experiencing thoughts of self-harm or severe crisis: + +- Call National Crisis Hotline: 988 +- Call Emergency Services: 911 +- Seek immediate professional help + diff --git a/ai_agent_tutorials/ai_mental_wellbeing_agent/ai_mental_wellbeing_agent.py b/ai_agent_tutorials/ai_mental_wellbeing_agent/ai_mental_wellbeing_agent.py new file mode 100644 index 0000000..2210743 --- /dev/null +++ b/ai_agent_tutorials/ai_mental_wellbeing_agent/ai_mental_wellbeing_agent.py @@ -0,0 +1,226 @@ +import streamlit as st +from autogen import (SwarmAgent, SwarmResult, initiate_swarm_chat, OpenAIWrapper,AFTER_WORK,UPDATE_SYSTEM_MESSAGE) +import os + +os.environ["AUTOGEN_USE_DOCKER"] = "0" + +if 'output' not in st.session_state: + st.session_state.output = { + 'assessment': '', + 'action': '', + 'followup': '' + } + +st.sidebar.title("OpenAI API Key") +api_key = st.sidebar.text_input("Enter your OpenAI API Key", type="password") + +st.sidebar.warning(""" +## โš ๏ธ Important Notice + +This application is a supportive tool and does not replace professional mental health care. If you're experiencing thoughts of self-harm or severe crisis: + +- Call National Crisis Hotline: 988 +- Call Emergency Services: 911 +- Seek immediate professional help +""") + +st.title("๐Ÿง  Mental Wellbeing Agent") + +st.info(""" +**Meet Your Mental Wellbeing Agent Team:** + +๐Ÿง  **Assessment Agent** - Analyzes your situation and emotional needs +๐ŸŽฏ **Action Agent** - Creates immediate action plan and connects you with resources +๐Ÿ”„ **Follow-up Agent** - Designs your long-term support strategy +""") + +st.subheader("Personal Information") +col1, col2 = st.columns(2) + +with col1: + mental_state = st.text_area("How have you been feeling recently?", + placeholder="Describe your emotional state, thoughts, or concerns...") + sleep_pattern = st.select_slider( + "Sleep Pattern (hours per night)", + options=[f"{i}" for i in range(0, 13)], + value="7" + ) + +with col2: + stress_level = st.slider("Current Stress Level (1-10)", 1, 10, 5) + support_system = st.multiselect( + "Current Support System", + ["Family", "Friends", "Therapist", "Support Groups", "None"] + ) + +recent_changes = st.text_area( + "Any significant life changes or events recently?", + placeholder="Job changes, relationships, losses, etc..." +) + +current_symptoms = st.multiselect( + "Current Symptoms", + ["Anxiety", "Depression", "Insomnia", "Fatigue", "Loss of Interest", + "Difficulty Concentrating", "Changes in Appetite", "Social Withdrawal", + "Mood Swings", "Physical Discomfort"] +) + +if st.button("Get Support Plan"): + if not api_key: + st.error("Please enter your OpenAI API key.") + else: + with st.spinner('๐Ÿค– AI Agents are analyzing your situation...'): + try: + task = f""" + Create a comprehensive mental health support plan based on: + + Emotional State: {mental_state} + Sleep: {sleep_pattern} hours per night + Stress Level: {stress_level}/10 + Support System: {', '.join(support_system) if support_system else 'None reported'} + Recent Changes: {recent_changes} + Current Symptoms: {', '.join(current_symptoms) if current_symptoms else 'None reported'} + """ + + system_messages = { + "assessment_agent": """ + You are an experienced mental health professional speaking directly to the user. Your task is to: + 1. Create a safe space by acknowledging their courage in seeking support + 2. Analyze their emotional state with clinical precision and genuine empathy + 3. Ask targeted follow-up questions to understand their full situation + 4. Identify patterns in their thoughts, behaviors, and relationships + 5. Assess risk levels with validated screening approaches + 6. Help them understand their current mental health in accessible language + 7. Validate their experiences without minimizing or catastrophizing + + Always use "you" and "your" when addressing the user. Blend clinical expertise with genuine warmth and never rush to conclusions. + """, + + "action_agent": """ + You are a crisis intervention and resource specialist speaking directly to the user. Your task is to: + 1. Provide immediate evidence-based coping strategies tailored to their specific situation + 2. Prioritize interventions based on urgency and effectiveness + 3. Connect them with appropriate mental health services while acknowledging barriers (cost, access, stigma) + 4. Create a concrete daily wellness plan with specific times and activities + 5. Suggest specific support communities with details on how to join + 6. Balance crisis resources with empowerment techniques + 7. Teach simple self-regulation techniques they can use immediately + + Focus on practical, achievable steps that respect their current capacity and energy levels. Provide options ranging from minimal effort to more involved actions. + """, + + "followup_agent": """ + You are a mental health recovery planner speaking directly to the user. Your task is to: + 1. Design a personalized long-term support strategy with milestone markers + 2. Create a progress monitoring system that matches their preferences and habits + 3. Develop specific relapse prevention strategies based on their unique triggers + 4. Establish a support network mapping exercise to identify existing resources + 5. Build a graduated self-care routine that evolves with their recovery + 6. Plan for setbacks with self-compassion techniques + 7. Set up a maintenance schedule with clear check-in mechanisms + + Focus on building sustainable habits that integrate with their lifestyle and values. Emphasize progress over perfection and teach skills for self-directed care. + """ + } + + llm_config = { + "config_list": [{"model": "gpt-4o", "api_key": api_key}] + } + + context_variables = { + "assessment": None, + "action": None, + "followup": None, + } + + def update_assessment_overview(assessment_summary: str, context_variables: dict) -> SwarmResult: + context_variables["assessment"] = assessment_summary + st.sidebar.success('Assessment: ' + assessment_summary) + return SwarmResult(agent="action_agent", context_variables=context_variables) + + def update_action_overview(action_summary: str, context_variables: dict) -> SwarmResult: + context_variables["action"] = action_summary + st.sidebar.success('Action Plan: ' + action_summary) + return SwarmResult(agent="followup_agent", context_variables=context_variables) + + def update_followup_overview(followup_summary: str, context_variables: dict) -> SwarmResult: + context_variables["followup"] = followup_summary + st.sidebar.success('Follow-up Strategy: ' + followup_summary) + return SwarmResult(agent="assessment_agent", context_variables=context_variables) + + def update_system_message_func(agent: SwarmAgent, messages) -> str: + system_prompt = system_messages[agent.name] + current_gen = agent.name.split("_")[0] + + if agent._context_variables.get(current_gen) is None: + system_prompt += f"Call the update function provided to first provide a 2-3 sentence summary of your ideas on {current_gen.upper()} based on the context provided." + agent.llm_config['tool_choice'] = {"type": "function", "function": {"name": f"update_{current_gen}_overview"}} + else: + agent.llm_config["tools"] = None + agent.llm_config['tool_choice'] = None + system_prompt += f"\n\nYour task\nYou task is write the {current_gen} part of the report. Do not include any other parts. Do not use XML tags.\nStart your reponse with: '## {current_gen.capitalize()} Design'." + k = list(agent._oai_messages.keys())[-1] + agent._oai_messages[k] = agent._oai_messages[k][:1] + + system_prompt += f"\n\n\nBelow are some context for you to refer to:" + for k, v in agent._context_variables.items(): + if v is not None: + system_prompt += f"\n{k.capitalize()} Summary:\n{v}" + + agent.client = OpenAIWrapper(**agent.llm_config) + return system_prompt + + state_update = UPDATE_SYSTEM_MESSAGE(update_system_message_func) + + assessment_agent = SwarmAgent( + "assessment_agent", + llm_config=llm_config, + functions=update_assessment_overview, + update_agent_state_before_reply=[state_update] + ) + + action_agent = SwarmAgent( + "action_agent", + llm_config=llm_config, + functions=update_action_overview, + update_agent_state_before_reply=[state_update] + ) + + followup_agent = SwarmAgent( + "followup_agent", + llm_config=llm_config, + functions=update_followup_overview, + update_agent_state_before_reply=[state_update] + ) + + assessment_agent.register_hand_off(AFTER_WORK(action_agent)) + action_agent.register_hand_off(AFTER_WORK(followup_agent)) + followup_agent.register_hand_off(AFTER_WORK(assessment_agent)) + + result, _, _ = initiate_swarm_chat( + initial_agent=assessment_agent, + agents=[assessment_agent, action_agent, followup_agent], + user_agent=None, + messages=task, + max_rounds=13, + ) + + st.session_state.output = { + 'assessment': result.chat_history[-3]['content'], + 'action': result.chat_history[-2]['content'], + 'followup': result.chat_history[-1]['content'] + } + + with st.expander("Situation Assessment"): + st.markdown(st.session_state.output['assessment']) + + with st.expander("Action Plan & Resources"): + st.markdown(st.session_state.output['action']) + + with st.expander("Long-term Support Strategy"): + st.markdown(st.session_state.output['followup']) + + st.success('โœจ Mental health support plan generated successfully!') + + except Exception as e: + st.error(f"An error occurred: {str(e)}") diff --git a/ai_agent_tutorials/ai_mental_wellbeing_agent/requirements.txt b/ai_agent_tutorials/ai_mental_wellbeing_agent/requirements.txt new file mode 100644 index 0000000..da4c21a --- /dev/null +++ b/ai_agent_tutorials/ai_mental_wellbeing_agent/requirements.txt @@ -0,0 +1,4 @@ +autogen-agentchat +autogen-ext +pyautogen +streamlit \ No newline at end of file diff --git a/ai_agent_tutorials/ai_movie_production_agent/README.md b/ai_agent_tutorials/ai_movie_production_agent/README.md index 0a1bf8e..3dac2c7 100644 --- a/ai_agent_tutorials/ai_movie_production_agent/README.md +++ b/ai_agent_tutorials/ai_movie_production_agent/README.md @@ -12,6 +12,7 @@ This Streamlit app is an AI-powered movie production assistant that helps bring ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_movie_production_agent ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/ai_movie_production_agent/movie_production_agent.py b/ai_agent_tutorials/ai_movie_production_agent/movie_production_agent.py index 064d2c2..b324bca 100644 --- a/ai_agent_tutorials/ai_movie_production_agent/movie_production_agent.py +++ b/ai_agent_tutorials/ai_movie_production_agent/movie_production_agent.py @@ -1,8 +1,8 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.tools.serpapi_tools import SerpApiTools -from phi.llm.anthropic import Claude +from agno.agent import Agent +from agno.tools.serpapi import SerpApiTools +from agno.models.anthropic import Claude from textwrap import dedent # Set up the Streamlit app @@ -15,9 +15,9 @@ anthropic_api_key = st.text_input("Enter Anthropic API Key to access Claude Sonn serp_api_key = st.text_input("Enter Serp API Key for Search functionality", type="password") if anthropic_api_key and serp_api_key: - script_writer = Assistant( + script_writer = Agent( name="ScriptWriter", - llm=Claude(model="claude-3-5-sonnet-20240620", api_key=anthropic_api_key), + model=Claude(id="claude-3-5-sonnet-20240620", api_key=anthropic_api_key), description=dedent( """\ You are an expert screenplay writer. Given a movie idea and genre, @@ -31,9 +31,9 @@ if anthropic_api_key and serp_api_key: ], ) - casting_director = Assistant( + casting_director = Agent( name="CastingDirector", - llm=Claude(model="claude-3-5-sonnet-20240620", api_key=anthropic_api_key), + model=Claude(id="claude-3-5-sonnet-20240620", api_key=anthropic_api_key), description=dedent( """\ You are a talented casting director. Given a script outline and character descriptions, @@ -49,9 +49,9 @@ if anthropic_api_key and serp_api_key: tools=[SerpApiTools(api_key=serp_api_key)], ) - movie_producer = Assistant( + movie_producer = Agent( name="MovieProducer", - llm=Claude(model="claude-3-5-sonnet-20240620", api_key=anthropic_api_key), + model=Claude(id="claude-3-5-sonnet-20240620", api_key=anthropic_api_key), team=[script_writer, casting_director], description="Experienced movie producer overseeing script and casting.", instructions=[ diff --git a/ai_agent_tutorials/ai_movie_production_agent/requirements.txt b/ai_agent_tutorials/ai_movie_production_agent/requirements.txt index 2c59945..fc46b55 100644 --- a/ai_agent_tutorials/ai_movie_production_agent/requirements.txt +++ b/ai_agent_tutorials/ai_movie_production_agent/requirements.txt @@ -1,5 +1,5 @@ streamlit -phidata +agno anthropic google-search-results lxml_html_clean \ No newline at end of file diff --git a/ai_agent_tutorials/ai_personal_finance_agent/README.md b/ai_agent_tutorials/ai_personal_finance_agent/README.md index 92683af..c96ae2b 100644 --- a/ai_agent_tutorials/ai_personal_finance_agent/README.md +++ b/ai_agent_tutorials/ai_personal_finance_agent/README.md @@ -12,6 +12,7 @@ This Streamlit app is an AI-powered personal finance planner that generates pers ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_personal_finance_agent ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/ai_personal_finance_agent/finance_agent.py b/ai_agent_tutorials/ai_personal_finance_agent/finance_agent.py index 55be0a4..4d59936 100644 --- a/ai_agent_tutorials/ai_personal_finance_agent/finance_agent.py +++ b/ai_agent_tutorials/ai_personal_finance_agent/finance_agent.py @@ -1,8 +1,8 @@ from textwrap import dedent -from phi.assistant import Assistant -from phi.tools.serpapi_tools import SerpApiTools +from agno.agent import Agent +from agno.tools.serpapi import SerpApiTools import streamlit as st -from phi.llm.openai import OpenAIChat +from agno.models.openai import OpenAIChat # Set up the Streamlit app st.title("AI Personal Finance Planner ๐Ÿ’ฐ") @@ -15,10 +15,10 @@ openai_api_key = st.text_input("Enter OpenAI API Key to access GPT-4o", type="pa serp_api_key = st.text_input("Enter Serp API Key for Search functionality", type="password") if openai_api_key and serp_api_key: - researcher = Assistant( + researcher = Agent( name="Researcher", role="Searches for financial advice, investment opportunities, and savings strategies based on user preferences", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), description=dedent( """\ You are a world-class financial researcher. Given a user's financial goals and current financial situation, @@ -35,10 +35,10 @@ if openai_api_key and serp_api_key: tools=[SerpApiTools(api_key=serp_api_key)], add_datetime_to_instructions=True, ) - planner = Assistant( + planner = Agent( name="Planner", role="Generates a personalized financial plan based on user preferences and research results", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), description=dedent( """\ You are a senior financial planner. Given a user's financial goals, current financial situation, and a list of research results, @@ -54,8 +54,6 @@ if openai_api_key and serp_api_key: "Never make up facts or plagiarize. Always provide proper attribution.", ], add_datetime_to_instructions=True, - add_chat_history_to_prompt=True, - num_history_messages=3, ) # Input fields for the user's financial goals and current financial situation @@ -66,4 +64,4 @@ if openai_api_key and serp_api_key: with st.spinner("Processing..."): # Get the response from the assistant response = planner.run(f"Financial goals: {financial_goals}, Current situation: {current_situation}", stream=False) - st.write(response) + st.write(response.content) diff --git a/ai_agent_tutorials/ai_personal_finance_agent/requirements.txt b/ai_agent_tutorials/ai_personal_finance_agent/requirements.txt index 549573b..ffff278 100644 --- a/ai_agent_tutorials/ai_personal_finance_agent/requirements.txt +++ b/ai_agent_tutorials/ai_personal_finance_agent/requirements.txt @@ -1,4 +1,4 @@ streamlit -phidata +agno openai google-search-results \ No newline at end of file diff --git a/ai_agent_tutorials/ai_real_estate_agent/README.md b/ai_agent_tutorials/ai_real_estate_agent/README.md new file mode 100644 index 0000000..c9e9f44 --- /dev/null +++ b/ai_agent_tutorials/ai_real_estate_agent/README.md @@ -0,0 +1,61 @@ +## ๐Ÿ  AI Real Estate Agent - Powered by Firecrawl's Extract Endpoint + +The AI Real Estate Agent automates property search and market analysis using Firecrawl's Extract endpoint and Agno AI Agent's insights. It helps users find properties matching their criteria while providing detailed location trends and investment recommendations. This agent streamlines the property search process by combining data from multiple real estate websites and offering intelligent analysis. + +### Features +- **Smart Property Search**: Uses Firecrawl's Extract endpoint to find properties across multiple real estate websites +- **Multi-Source Integration**: Aggregates data from 99acres, Housing.com, Square Yards, Nobroker, and MagicBricks +- **Location Analysis**: Provides detailed price trends and investment insights for different localities +- **AI-Powered Recommendations**: Uses GPT models to analyze properties and provide structured recommendations +- **User-Friendly Interface**: Clean Streamlit UI for easy property search and results viewing +- **Customizable Search**: Filter by city, property type, category, and budget + +### How to Get Started +1. **Clone the repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_real_estate_agent + ``` + +2. **Install the required packages**: + ```bash + pip install -r requirements.txt + ``` + +3. **Set up your API keys**: + - Get your Firecrawl API key from [Firecrawl's website](https://www.firecrawl.dev/app/api-keys) + - Get your OpenAI API key from [OpenAI's website](https://platform.openai.com/api-keys) + +4. **Run the application**: + ```bash + streamlit run ai_real_estate_agent.py + ``` + +### Using the Agent +1. **Enter API Keys**: + - Input your Firecrawl and OpenAI API keys in the sidebar + - Keys are securely stored in the session state + +2. **Set Search Criteria**: + - Enter the city name + - Select property category (Residential/Commercial) + - Choose property type (Flat/Individual House) + - Set maximum budget in Crores + +3. **View Results**: + - Property recommendations with detailed analysis + - Location trends with investment insights + - Expandable sections for easy reading + +### Features in Detail +- **Property Finding**: + - Searches across multiple real estate websites + - Returns 3-6 properties matching criteria + - Provides detailed property information and analysis + +- **Location Analysis**: + - Price trends for different localities + - Rental yield analysis + - Investment potential assessment + - Top performing areas identification + diff --git a/ai_agent_tutorials/ai_real_estate_agent/ai_real_estate_agent.py b/ai_agent_tutorials/ai_real_estate_agent/ai_real_estate_agent.py new file mode 100644 index 0000000..f9b65d2 --- /dev/null +++ b/ai_agent_tutorials/ai_real_estate_agent/ai_real_estate_agent.py @@ -0,0 +1,321 @@ +from typing import Dict, List +from pydantic import BaseModel, Field +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from firecrawl import FirecrawlApp +import streamlit as st + +class PropertyData(BaseModel): + """Schema for property data extraction""" + building_name: str = Field(description="Name of the building/property", alias="Building_name") + property_type: str = Field(description="Type of property (commercial, residential, etc)", alias="Property_type") + location_address: str = Field(description="Complete address of the property") + price: str = Field(description="Price of the property", alias="Price") + description: str = Field(description="Detailed description of the property", alias="Description") + +class PropertiesResponse(BaseModel): + """Schema for multiple properties response""" + properties: List[PropertyData] = Field(description="List of property details") + +class LocationData(BaseModel): + """Schema for location price trends""" + location: str + price_per_sqft: float + percent_increase: float + rental_yield: float + +class LocationsResponse(BaseModel): + """Schema for multiple locations response""" + locations: List[LocationData] = Field(description="List of location data points") + +class FirecrawlResponse(BaseModel): + """Schema for Firecrawl API response""" + success: bool + data: Dict + status: str + expiresAt: str + +class PropertyFindingAgent: + """Agent responsible for finding properties and providing recommendations""" + + def __init__(self, firecrawl_api_key: str, openai_api_key: str, model_id: str = "o3-mini"): + self.agent = Agent( + model=OpenAIChat(id=model_id, api_key=openai_api_key), + markdown=True, + description="I am a real estate expert who helps find and analyze properties based on user preferences." + ) + self.firecrawl = FirecrawlApp(api_key=firecrawl_api_key) + + def find_properties( + self, + city: str, + max_price: float, + property_category: str = "Residential", + property_type: str = "Flat" + ) -> str: + """Find and analyze properties based on user preferences""" + formatted_location = city.lower() + + urls = [ + f"https://www.squareyards.com/sale/property-for-sale-in-{formatted_location}/*", + f"https://www.99acres.com/property-in-{formatted_location}-ffid/*", + f"https://housing.com/in/buy/{formatted_location}/{formatted_location}", + # f"https://www.nobroker.in/property/sale/{city}/{formatted_location}", + ] + + property_type_prompt = "Flats" if property_type == "Flat" else "Individual Houses" + + raw_response = self.firecrawl.extract( + urls=urls, + params={ + 'prompt': f"""Extract ONLY 10 OR LESS different {property_category} {property_type_prompt} from {city} that cost less than {max_price} crores. + + Requirements: + - Property Category: {property_category} properties only + - Property Type: {property_type_prompt} only + - Location: {city} + - Maximum Price: {max_price} crores + - Include complete property details with exact location + - IMPORTANT: Return data for at least 3 different properties. MAXIMUM 10. + - Format as a list of properties with their respective details + """, + 'schema': PropertiesResponse.model_json_schema() + } + ) + + print("Raw Property Response:", raw_response) + + if isinstance(raw_response, dict) and raw_response.get('success'): + properties = raw_response['data'].get('properties', []) + else: + properties = [] + + print("Processed Properties:", properties) + + + analysis = self.agent.run( + f"""As a real estate expert, analyze these properties and market trends: + + Properties Found in json format: + {properties} + + **IMPORTANT INSTRUCTIONS:** + 1. ONLY analyze properties from the above JSON data that match the user's requirements: + - Property Category: {property_category} + - Property Type: {property_type} + - Maximum Price: {max_price} crores + 2. DO NOT create new categories or property types + 3. From the matching properties, select 5-6 properties with prices closest to {max_price} crores + + Please provide your analysis in this format: + + ๐Ÿ  SELECTED PROPERTIES + โ€ข List only 5-6 best matching properties with prices closest to {max_price} crores + โ€ข For each property include: + - Name and Location + - Price (with value analysis) + - Key Features + - Pros and Cons + + ๐Ÿ’ฐ BEST VALUE ANALYSIS + โ€ข Compare the selected properties based on: + - Price per sq ft + - Location advantage + - Amenities offered + + ๐Ÿ“ LOCATION INSIGHTS + โ€ข Specific advantages of the areas where selected properties are located + + ๐Ÿ’ก RECOMMENDATIONS + โ€ข Top 3 properties from the selection with reasoning + โ€ข Investment potential + โ€ข Points to consider before purchase + + ๐Ÿค NEGOTIATION TIPS + โ€ข Property-specific negotiation strategies + + Format your response in a clear, structured way using the above sections. + """ + ) + + return analysis.content + + def get_location_trends(self, city: str) -> str: + """Get price trends for different localities in the city""" + raw_response = self.firecrawl.extract([ + f"https://www.99acres.com/property-rates-and-price-trends-in-{city.lower()}-prffid/*" + ], { + 'prompt': """Extract price trends data for ALL major localities in the city. + IMPORTANT: + - Return data for at least 5-10 different localities + - Include both premium and affordable areas + - Do not skip any locality mentioned in the source + - Format as a list of locations with their respective data + """, + 'schema': LocationsResponse.model_json_schema(), + }) + + if isinstance(raw_response, dict) and raw_response.get('success'): + locations = raw_response['data'].get('locations', []) + + analysis = self.agent.run( + f"""As a real estate expert, analyze these location price trends for {city}: + + {locations} + + Please provide: + 1. A bullet-point summary of the price trends for each location + 2. Identify the top 3 locations with: + - Highest price appreciation + - Best rental yields + - Best value for money + 3. Investment recommendations: + - Best locations for long-term investment + - Best locations for rental income + - Areas showing emerging potential + 4. Specific advice for investors based on these trends + + Format the response as follows: + + ๐Ÿ“Š LOCATION TRENDS SUMMARY + โ€ข [Bullet points for each location] + + ๐Ÿ† TOP PERFORMING AREAS + โ€ข [Bullet points for best areas] + + ๐Ÿ’ก INVESTMENT INSIGHTS + โ€ข [Bullet points with investment advice] + + ๐ŸŽฏ RECOMMENDATIONS + โ€ข [Bullet points with specific recommendations] + """ + ) + + return analysis.content + + return "No price trends data available" + +def create_property_agent(): + """Create PropertyFindingAgent with API keys from session state""" + if 'property_agent' not in st.session_state: + st.session_state.property_agent = PropertyFindingAgent( + firecrawl_api_key=st.session_state.firecrawl_key, + openai_api_key=st.session_state.openai_key, + model_id=st.session_state.model_id + ) + +def main(): + st.set_page_config( + page_title="AI Real Estate Agent", + page_icon="๐Ÿ ", + layout="wide" + ) + + with st.sidebar: + st.title("๐Ÿ”‘ API Configuration") + + st.subheader("๐Ÿค– Model Selection") + model_id = st.selectbox( + "Choose OpenAI Model", + options=["o3-mini", "gpt-4o"], + help="Select the AI model to use. Choose gpt-4o if your api doesn't have access to o3-mini" + ) + st.session_state.model_id = model_id + + st.divider() + + st.subheader("๐Ÿ” API Keys") + firecrawl_key = st.text_input( + "Firecrawl API Key", + type="password", + help="Enter your Firecrawl API key" + ) + openai_key = st.text_input( + "OpenAI API Key", + type="password", + help="Enter your OpenAI API key" + ) + + if firecrawl_key and openai_key: + st.session_state.firecrawl_key = firecrawl_key + st.session_state.openai_key = openai_key + create_property_agent() + + st.title("๐Ÿ  AI Real Estate Agent") + st.info( + """ + Welcome to the AI Real Estate Agent! + Enter your search criteria below to get property recommendations + and location insights. + """ + ) + + col1, col2 = st.columns(2) + + with col1: + city = st.text_input( + "City", + placeholder="Enter city name (e.g., Bangalore)", + help="Enter the city where you want to search for properties" + ) + + property_category = st.selectbox( + "Property Category", + options=["Residential", "Commercial"], + help="Select the type of property you're interested in" + ) + + with col2: + max_price = st.number_input( + "Maximum Price (in Crores)", + min_value=0.1, + max_value=100.0, + value=5.0, + step=0.1, + help="Enter your maximum budget in Crores" + ) + + property_type = st.selectbox( + "Property Type", + options=["Flat", "Individual House"], + help="Select the specific type of property" + ) + + if st.button("๐Ÿ” Start Search", use_container_width=True): + if 'property_agent' not in st.session_state: + st.error("โš ๏ธ Please enter your API keys in the sidebar first!") + return + + if not city: + st.error("โš ๏ธ Please enter a city name!") + return + + try: + with st.spinner("๐Ÿ” Searching for properties..."): + property_results = st.session_state.property_agent.find_properties( + city=city, + max_price=max_price, + property_category=property_category, + property_type=property_type + ) + + st.success("โœ… Property search completed!") + + st.subheader("๐Ÿ˜๏ธ Property Recommendations") + st.markdown(property_results) + + st.divider() + + with st.spinner("๐Ÿ“Š Analyzing location trends..."): + location_trends = st.session_state.property_agent.get_location_trends(city) + + st.success("โœ… Location analysis completed!") + + with st.expander("๐Ÿ“ˆ Location Trends Analysis of the city"): + st.markdown(location_trends) + + except Exception as e: + st.error(f"โŒ An error occurred: {str(e)}") + +if __name__ == "__main__": + main() diff --git a/ai_agent_tutorials/ai_real_estate_agent/requirements.txt b/ai_agent_tutorials/ai_real_estate_agent/requirements.txt new file mode 100644 index 0000000..8590901 --- /dev/null +++ b/ai_agent_tutorials/ai_real_estate_agent/requirements.txt @@ -0,0 +1,5 @@ +agno +firecrawl-py==1.9.0 +pydantic +streamlit +openai \ No newline at end of file diff --git a/ai_agent_tutorials/ai_reasoning_agent/README.md b/ai_agent_tutorials/ai_reasoning_agent/README.md new file mode 100644 index 0000000..119bc83 --- /dev/null +++ b/ai_agent_tutorials/ai_reasoning_agent/README.md @@ -0,0 +1,51 @@ +## AI Reasoning Agent + +The AI Reasoning Agent leverages advanced AI models to provide insightful reasoning and decision-making capabilities. This agent is designed to assist users in various analytical tasks by processing information and generating structured outputs. + +### Features +- **Advanced Reasoning**: Utilizes the Ollama model to perform complex reasoning tasks +- **Interactive Playground**: Provides a user-friendly interface for interacting with the reasoning agent +- **Markdown Support**: Outputs results in markdown format for easy readability and sharing +- **Customizable Agent**: Easily configurable to suit different reasoning scenarios + +### How to Get Started +1. **Clone the repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_reasoning_agent + ``` + +2. **Install the required packages**: + #### For Local AI Reasoning Agent + ```bash + pip install -r requirements_local_ai_reasoning_agent.txt + ``` + +3. **Run the application**: + ```bash + python local_ai_reasoning_agent.py + ``` + +### Using the Agent +1. **Access the Playground**: + - Open the provided URL to access the interactive playground + - The playground allows you to input queries and receive structured reasoning outputs + +2. **Input Queries**: + - Enter your queries in the provided input field + - The agent processes the input and provides detailed reasoning and analysis + +3. **View Results**: + - Results are displayed in markdown format + - Easily copy and share the outputs for further use + +### Features in Detail +- **Reasoning Capabilities**: + - Handles a wide range of analytical tasks + - Provides clear and structured outputs + - Supports markdown for easy sharing and readability + +- **Interactive Interface**: + - User-friendly playground for seamless interaction + - Real-time processing and output generation + - Configurable settings to tailor the agent's behavior \ No newline at end of file diff --git a/ai_agent_tutorials/ai_reasoning_agent/agents.db b/ai_agent_tutorials/ai_reasoning_agent/agents.db deleted file mode 100644 index c2d3d52..0000000 Binary files a/ai_agent_tutorials/ai_reasoning_agent/agents.db and /dev/null differ diff --git a/ai_agent_tutorials/ai_reasoning_agent/local_ai_reasoning_agent.py b/ai_agent_tutorials/ai_reasoning_agent/local_ai_reasoning_agent.py new file mode 100644 index 0000000..d31910c --- /dev/null +++ b/ai_agent_tutorials/ai_reasoning_agent/local_ai_reasoning_agent.py @@ -0,0 +1,12 @@ +from agno.agent import Agent +from agno.models.ollama import Ollama +from agno.playground import Playground, serve_playground_app + +reasoning_agent = Agent(name="Reasoning Agent", model=Ollama(id="qwq:32b"), markdown=True) + +# UI for Reasoning agent +app = Playground(agents=[reasoning_agent]).get_app() + +# Run the Playground app +if __name__ == "__main__": + serve_playground_app("local_ai_reasoning_agent:app", reload=True) \ No newline at end of file diff --git a/ai_agent_tutorials/ai_reasoning_agent/reasoning_agent.py b/ai_agent_tutorials/ai_reasoning_agent/reasoning_agent.py index 4f55d79..137b5d3 100644 --- a/ai_agent_tutorials/ai_reasoning_agent/reasoning_agent.py +++ b/ai_agent_tutorials/ai_reasoning_agent/reasoning_agent.py @@ -1,9 +1,9 @@ -from phi.agent import Agent -from phi.model.openai import OpenAIChat -from phi.cli.console import console +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from rich.console import Console regular_agent = Agent(model=OpenAIChat(id="gpt-4o-mini"), markdown=True) - +console = Console() reasoning_agent = Agent( model=OpenAIChat(id="gpt-4o"), reasoning=True, diff --git a/ai_agent_tutorials/ai_reasoning_agent/requirements.txt b/ai_agent_tutorials/ai_reasoning_agent/requirements.txt new file mode 100644 index 0000000..88c5c83 --- /dev/null +++ b/ai_agent_tutorials/ai_reasoning_agent/requirements.txt @@ -0,0 +1,4 @@ +agno +ollama +fastapi +uvicorn \ No newline at end of file diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/README.md b/ai_agent_tutorials/ai_recruitment_agent_team/README.md new file mode 100644 index 0000000..55c48dd --- /dev/null +++ b/ai_agent_tutorials/ai_recruitment_agent_team/README.md @@ -0,0 +1,101 @@ +# ๐Ÿ’ผ AI Recruitment Agent Team + +A Streamlit application that simulates a full-service recruitment team using multiple AI agents to automate and streamline the hiring process. Each agent represents a different recruitment specialist role - from resume analysis and candidate evaluation to interview scheduling and communication - working together to provide comprehensive hiring solutions. The system combines the expertise of technical recruiters, HR coordinators, and scheduling specialists into a cohesive automated workflow. + +## Features + +#### Specialized AI Agents + +- Technical Recruiter Agent: Analyzes resumes and evaluates technical skills +- Communication Agent: Handles professional email correspondence +- Scheduling Coordinator Agent: Manages interview scheduling and coordination +- Each agent has specific expertise and collaborates for comprehensive recruitment + + +#### End-to-End Recruitment Process +- Automated resume screening and analysis +- Role-specific technical evaluation +- Professional candidate communication +- Automated interview scheduling +- Integrated feedback system + +## Important Things to do before running the application + +- Create/Use a new Gmail account for the recruiter +- Enable 2-Step Verification and generate an App Password for the Gmail account +- The App Password is a 16 digit code (use without spaces) that should be generated here - [Google App Password](https://support.google.com/accounts/answer/185833?hl=en) Please go through the steps to generate the password - it will of the format - 'afec wejf awoj fwrv' (remove the spaces and enter it in the streamlit app) +- Create/ Use a Zoom account and go to the Zoom App Marketplace to get the API credentials : +[Zoom Marketplace](https://marketplace.zoom.us) +- Go to Developer Dashboard and create a new app - Select Server to Server OAuth and get the credentials, You see 3 credentials - Client ID, Client Secret and Account ID +- After that, you need to add a few scopes to the app - so that the zoom link of the candidate is sent and created through the mail. +- The Scopes are meeting:write:invite_links:admin, meeting:write:meeting:admin, meeting:write:meeting:master, meeting:write:invite_links:master, meeting:write:open_app:admin, user:read:email:admin, user:read:list_users:admin, billing:read:user_entitlement:admin, dashboard:read:list_meeting_participants:admin [last 3 are optional] + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_recruitment_agent_team + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - OpenAI API key for GPT-4o access + - Zoom API credentials (Account ID, Client ID, Client Secret) + - Email App Password of Recruiter's Email + +3. **Run the Application** + ```bash + streamlit run ai_recruitment_agent_team.py + ``` + +## System Components + +- **Resume Analyzer Agent** + - Skills matching algorithm + - Experience verification + - Technical assessment + - Selection decision making + +- **Email Communication Agent** + - Professional email drafting + - Automated notifications + - Feedback communication + - Follow-up management + +- **Interview Scheduler Agent** + - Zoom meeting coordination + - Calendar management + - Timezone handling + - Reminder system + +- **Candidate Experience** + - Simple upload interface + - Real-time feedback + - Clear communication + - Streamlined process + +## Technical Stack + +- **Framework**: Phidata +- **Model**: OpenAI GPT-4o +- **Integration**: Zoom API, EmailTools Tool from Phidata +- **PDF Processing**: PyPDF2 +- **Time Management**: pytz +- **State Management**: Streamlit Session State + + +## Disclaimer + +This tool is designed to assist in the recruitment process but should not completely replace human judgment in hiring decisions. All automated decisions should be reviewed by human recruiters for final approval. + +## Future Enhancements + +- Integration with ATS systems +- Advanced candidate scoring +- Video interview capabilities +- Skills assessment integration +- Multi-language support diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py new file mode 100644 index 0000000..59e56dc --- /dev/null +++ b/ai_agent_tutorials/ai_recruitment_agent_team/ai_recruitment_agent_team.py @@ -0,0 +1,521 @@ +from typing import Literal, Tuple, Dict, Optional +import os +import time +import json +import requests +import PyPDF2 +from datetime import datetime, timedelta +import pytz + +import streamlit as st +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from agno.tools.email import EmailTools +from phi.tools.zoom import ZoomTool +from phi.utils.log import logger +from streamlit_pdf_viewer import pdf_viewer + + + +class CustomZoomTool(ZoomTool): + def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, name: str = "zoom_tool"): + super().__init__(account_id=account_id, client_id=client_id, client_secret=client_secret, name=name) + self.token_url = "https://zoom.us/oauth/token" + self.access_token = None + self.token_expires_at = 0 + + def get_access_token(self) -> str: + if self.access_token and time.time() < self.token_expires_at: + return str(self.access_token) + + headers = {"Content-Type": "application/x-www-form-urlencoded"} + data = {"grant_type": "account_credentials", "account_id": self.account_id} + + try: + response = requests.post(self.token_url, headers=headers, data=data, auth=(self.client_id, self.client_secret)) + response.raise_for_status() + + token_info = response.json() + self.access_token = token_info["access_token"] + expires_in = token_info["expires_in"] + self.token_expires_at = time.time() + expires_in - 60 + + self._set_parent_token(str(self.access_token)) + return str(self.access_token) + + except requests.RequestException as e: + logger.error(f"Error fetching access token: {e}") + return "" + + def _set_parent_token(self, token: str) -> None: + """Helper method to set the token in the parent ZoomTool class""" + if token: + self._ZoomTool__access_token = token + + +# Role requirements as a constant dictionary +ROLE_REQUIREMENTS: Dict[str, str] = { + "ai_ml_engineer": """ + Required Skills: + - Python, PyTorch/TensorFlow + - Machine Learning algorithms and frameworks + - Deep Learning and Neural Networks + - Data preprocessing and analysis + - MLOps and model deployment + - RAG, LLM, Finetuning and Prompt Engineering + """, + + "frontend_engineer": """ + Required Skills: + - React/Vue.js/Angular + - HTML5, CSS3, JavaScript/TypeScript + - Responsive design + - State management + - Frontend testing + """, + + "backend_engineer": """ + Required Skills: + - Python/Java/Node.js + - REST APIs + - Database design and management + - System architecture + - Cloud services (AWS/GCP/Azure) + - Kubernetes, Docker, CI/CD + """ +} + + +def init_session_state() -> None: + """Initialize only necessary session state variables.""" + defaults = { + 'candidate_email': "", 'openai_api_key': "", 'resume_text': "", 'analysis_complete': False, + 'is_selected': False, 'zoom_account_id': "", 'zoom_client_id': "", 'zoom_client_secret': "", + 'email_sender': "", 'email_passkey': "", 'company_name': "", 'current_pdf': None + } + for key, value in defaults.items(): + if key not in st.session_state: + st.session_state[key] = value + + +def create_resume_analyzer() -> Agent: + """Creates and returns a resume analysis agent.""" + if not st.session_state.openai_api_key: + st.error("Please enter your OpenAI API key first.") + return None + + return Agent( + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + description="You are an expert technical recruiter who analyzes resumes.", + instructions=[ + "Analyze the resume against the provided job requirements", + "Be lenient with AI/ML candidates who show strong potential", + "Consider project experience as valid experience", + "Value hands-on experience with key technologies", + "Return a JSON response with selection decision and feedback" + ], + markdown=True + ) + +def create_email_agent() -> Agent: + return Agent( + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + tools=[EmailTools( + receiver_email=st.session_state.candidate_email, + sender_email=st.session_state.email_sender, + sender_name=st.session_state.company_name, + sender_passkey=st.session_state.email_passkey + )], + description="You are a professional recruitment coordinator handling email communications.", + instructions=[ + "Draft and send professional recruitment emails", + "Act like a human writing an email and use all lowercase letters", + "Maintain a friendly yet professional tone", + "Always end emails with exactly: 'best,\nthe ai recruiting team'", + "Never include the sender's or receiver's name in the signature", + f"The name of the company is '{st.session_state.company_name}'" + ], + markdown=True, + show_tool_calls=True + ) + + +def create_scheduler_agent() -> Agent: + zoom_tools = CustomZoomTool( + account_id=st.session_state.zoom_account_id, + client_id=st.session_state.zoom_client_id, + client_secret=st.session_state.zoom_client_secret + ) + + return Agent( + name="Interview Scheduler", + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + tools=[zoom_tools], + description="You are an interview scheduling coordinator.", + instructions=[ + "You are an expert at scheduling technical interviews using Zoom.", + "Schedule interviews during business hours (9 AM - 5 PM EST)", + "Create meetings with proper titles and descriptions", + "Ensure all meeting details are included in responses", + "Use ISO 8601 format for dates", + "Handle scheduling errors gracefully" + ], + markdown=True, + show_tool_calls=True + ) + + +def extract_text_from_pdf(pdf_file) -> str: + try: + pdf_reader = PyPDF2.PdfReader(pdf_file) + text = "" + for page in pdf_reader.pages: + text += page.extract_text() + return text + except Exception as e: + st.error(f"Error extracting PDF text: {str(e)}") + return "" + + +def analyze_resume( + resume_text: str, + role: Literal["ai_ml_engineer", "frontend_engineer", "backend_engineer"], + analyzer: Agent +) -> Tuple[bool, str]: + try: + response = analyzer.run( + f"""Please analyze this resume against the following requirements and provide your response in valid JSON format: + Role Requirements: + {ROLE_REQUIREMENTS[role]} + Resume Text: + {resume_text} + Your response must be a valid JSON object like this: + {{ + "selected": true/false, + "feedback": "Detailed feedback explaining the decision", + "matching_skills": ["skill1", "skill2"], + "missing_skills": ["skill3", "skill4"], + "experience_level": "junior/mid/senior" + }} + Evaluation criteria: + 1. Match at least 70% of required skills + 2. Consider both theoretical knowledge and practical experience + 3. Value project experience and real-world applications + 4. Consider transferable skills from similar technologies + 5. Look for evidence of continuous learning and adaptability + Important: Return ONLY the JSON object without any markdown formatting or backticks. + """ + ) + + assistant_message = next((msg.content for msg in response.messages if msg.role == 'assistant'), None) + if not assistant_message: + raise ValueError("No assistant message found in response.") + + result = json.loads(assistant_message.strip()) + if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]): + raise ValueError("Invalid response format") + + return result["selected"], result["feedback"] + + except (json.JSONDecodeError, ValueError) as e: + st.error(f"Error processing response: {str(e)}") + return False, f"Error analyzing resume: {str(e)}" + + +def send_selection_email(email_agent: Agent, to_email: str, role: str) -> None: + email_agent.run( + f""" + Send an email to {to_email} regarding their selection for the {role} position. + The email should: + 1. Congratulate them on being selected + 2. Explain the next steps in the process + 3. Mention that they will receive interview details shortly + 4. The name of the company is 'AI Recruiting Team' + """ + ) + + +def send_rejection_email(email_agent: Agent, to_email: str, role: str, feedback: str) -> None: + """ + Send a rejection email with constructive feedback. + """ + email_agent.run( + f""" + Send an email to {to_email} regarding their application for the {role} position. + Use this specific style: + 1. use all lowercase letters + 2. be empathetic and human + 3. mention specific feedback from: {feedback} + 4. encourage them to upskill and try again + 5. suggest some learning resources based on missing skills + 6. end the email with exactly: + best, + the ai recruiting team + + Do not include any names in the signature. + The tone should be like a human writing a quick but thoughtful email. + """ + ) + + +def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None: + """ + Schedule interviews during business hours (9 AM - 5 PM IST). + """ + try: + # Get current time in IST + ist_tz = pytz.timezone('Asia/Kolkata') + current_time_ist = datetime.now(ist_tz) + + tomorrow_ist = current_time_ist + timedelta(days=1) + interview_time = tomorrow_ist.replace(hour=11, minute=0, second=0, microsecond=0) + formatted_time = interview_time.strftime('%Y-%m-%dT%H:%M:%S') + + meeting_response = scheduler.run( + f"""Schedule a 60-minute technical interview with these specifications: + - Title: '{role} Technical Interview' + - Date: {formatted_time} + - Timezone: IST (India Standard Time) + - Attendee: {candidate_email} + + Important Notes: + - The meeting must be between 9 AM - 5 PM IST + - Use IST (UTC+5:30) timezone for all communications + - Include timezone information in the meeting details + """ + ) + + email_agent.run( + f"""Send an interview confirmation email with these details: + - Role: {role} position + - Meeting Details: {meeting_response} + + Important: + - Clearly specify that the time is in IST (India Standard Time) + - Ask the candidate to join 5 minutes early + - Include timezone conversion link if possible + - Ask him to be confident and not so nervous and prepare well for the interview + """ + ) + + st.success("Interview scheduled successfully! Check your email for details.") + + except Exception as e: + logger.error(f"Error scheduling interview: {str(e)}") + st.error("Unable to schedule interview. Please try again.") + + +def main() -> None: + st.title("AI Recruitment System") + + init_session_state() + with st.sidebar: + st.header("Configuration") + + # OpenAI Configuration + st.subheader("OpenAI Settings") + api_key = st.text_input("OpenAI API Key", type="password", value=st.session_state.openai_api_key, help="Get your API key from platform.openai.com") + if api_key: st.session_state.openai_api_key = api_key + + st.subheader("Zoom Settings") + zoom_account_id = st.text_input("Zoom Account ID", type="password", value=st.session_state.zoom_account_id) + zoom_client_id = st.text_input("Zoom Client ID", type="password", value=st.session_state.zoom_client_id) + zoom_client_secret = st.text_input("Zoom Client Secret", type="password", value=st.session_state.zoom_client_secret) + + st.subheader("Email Settings") + email_sender = st.text_input("Sender Email", value=st.session_state.email_sender, help="Email address to send from") + email_passkey = st.text_input("Email App Password", type="password", value=st.session_state.email_passkey, help="App-specific password for email") + company_name = st.text_input("Company Name", value=st.session_state.company_name, help="Name to use in email communications") + + if zoom_account_id: st.session_state.zoom_account_id = zoom_account_id + if zoom_client_id: st.session_state.zoom_client_id = zoom_client_id + if zoom_client_secret: st.session_state.zoom_client_secret = zoom_client_secret + if email_sender: st.session_state.email_sender = email_sender + if email_passkey: st.session_state.email_passkey = email_passkey + if company_name: st.session_state.company_name = company_name + + required_configs = {'OpenAI API Key': st.session_state.openai_api_key, 'Zoom Account ID': st.session_state.zoom_account_id, + 'Zoom Client ID': st.session_state.zoom_client_id, 'Zoom Client Secret': st.session_state.zoom_client_secret, + 'Email Sender': st.session_state.email_sender, 'Email Password': st.session_state.email_passkey, + 'Company Name': st.session_state.company_name} + + missing_configs = [k for k, v in required_configs.items() if not v] + if missing_configs: + st.warning(f"Please configure the following in the sidebar: {', '.join(missing_configs)}") + return + + if not st.session_state.openai_api_key: + st.warning("Please enter your OpenAI API key in the sidebar to continue.") + return + + role = st.selectbox("Select the role you're applying for:", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"]) + with st.expander("View Required Skills", expanded=True): st.markdown(ROLE_REQUIREMENTS[role]) + + # Add a "New Application" button before the resume upload + if st.button("๐Ÿ“ New Application"): + # Clear only the application-related states + keys_to_clear = ['resume_text', 'analysis_complete', 'is_selected', 'candidate_email', 'current_pdf'] + for key in keys_to_clear: + if key in st.session_state: + st.session_state[key] = None if key == 'current_pdf' else "" + st.rerun() + + resume_file = st.file_uploader("Upload your resume (PDF)", type=["pdf"], key="resume_uploader") + if resume_file is not None and resume_file != st.session_state.get('current_pdf'): + st.session_state.current_pdf = resume_file + st.session_state.resume_text = "" + st.session_state.analysis_complete = False + st.session_state.is_selected = False + st.rerun() + + if resume_file: + st.subheader("Uploaded Resume") + col1, col2 = st.columns([4, 1]) + + with col1: + import tempfile, os + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(resume_file.read()) + tmp_file_path = tmp_file.name + resume_file.seek(0) + try: pdf_viewer(tmp_file_path) + finally: os.unlink(tmp_file_path) + + with col2: + st.download_button(label="๐Ÿ“ฅ Download", data=resume_file, file_name=resume_file.name, mime="application/pdf") + # Process the resume text + if not st.session_state.resume_text: + with st.spinner("Processing your resume..."): + resume_text = extract_text_from_pdf(resume_file) + if resume_text: + st.session_state.resume_text = resume_text + st.success("Resume processed successfully!") + else: + st.error("Could not process the PDF. Please try again.") + + # Email input with session state + email = st.text_input( + "Candidate's email address", + value=st.session_state.candidate_email, + key="email_input" + ) + st.session_state.candidate_email = email + + # Analysis and next steps + if st.session_state.resume_text and email and not st.session_state.analysis_complete: + if st.button("Analyze Resume"): + with st.spinner("Analyzing your resume..."): + resume_analyzer = create_resume_analyzer() + email_agent = create_email_agent() # Create email agent here + + if resume_analyzer and email_agent: + print("DEBUG: Starting resume analysis") + is_selected, feedback = analyze_resume( + st.session_state.resume_text, + role, + resume_analyzer + ) + print(f"DEBUG: Analysis complete - Selected: {is_selected}, Feedback: {feedback}") + + if is_selected: + st.success("Congratulations! Your skills match our requirements.") + st.session_state.analysis_complete = True + st.session_state.is_selected = True + st.rerun() + else: + st.warning("Unfortunately, your skills don't match our requirements.") + st.write(f"Feedback: {feedback}") + + # Send rejection email + with st.spinner("Sending feedback email..."): + try: + send_rejection_email( + email_agent=email_agent, + to_email=email, + role=role, + feedback=feedback + ) + st.info("We've sent you an email with detailed feedback.") + except Exception as e: + logger.error(f"Error sending rejection email: {e}") + st.error("Could not send feedback email. Please try again.") + + if st.session_state.get('analysis_complete') and st.session_state.get('is_selected', False): + st.success("Congratulations! Your skills match our requirements.") + st.info("Click 'Proceed with Application' to continue with the interview process.") + + if st.button("Proceed with Application", key="proceed_button"): + print("DEBUG: Proceed button clicked") # Debug + with st.spinner("๐Ÿ”„ Processing your application..."): + try: + print("DEBUG: Creating email agent") # Debug + email_agent = create_email_agent() + print(f"DEBUG: Email agent created: {email_agent}") # Debug + + print("DEBUG: Creating scheduler agent") # Debug + scheduler_agent = create_scheduler_agent() + print(f"DEBUG: Scheduler agent created: {scheduler_agent}") # Debug + + # 3. Send selection email + with st.status("๐Ÿ“ง Sending confirmation email...", expanded=True) as status: + print(f"DEBUG: Attempting to send email to {st.session_state.candidate_email}") # Debug + send_selection_email( + email_agent, + st.session_state.candidate_email, + role + ) + print("DEBUG: Email sent successfully") # Debug + status.update(label="โœ… Confirmation email sent!") + + # 4. Schedule interview + with st.status("๐Ÿ“… Scheduling interview...", expanded=True) as status: + print("DEBUG: Attempting to schedule interview") # Debug + schedule_interview( + scheduler_agent, + st.session_state.candidate_email, + email_agent, + role + ) + print("DEBUG: Interview scheduled successfully") # Debug + status.update(label="โœ… Interview scheduled!") + + print("DEBUG: All processes completed successfully") # Debug + st.success(""" + ๐ŸŽ‰ Application Successfully Processed! + + Please check your email for: + 1. Selection confirmation โœ… + 2. Interview details with Zoom link ๐Ÿ”— + + Next steps: + 1. Review the role requirements + 2. Prepare for your technical interview + 3. Join the interview 5 minutes early + """) + + except Exception as e: + print(f"DEBUG: Error occurred: {str(e)}") # Debug + print(f"DEBUG: Error type: {type(e)}") # Debug + import traceback + print(f"DEBUG: Full traceback: {traceback.format_exc()}") # Debug + st.error(f"An error occurred: {str(e)}") + st.error("Please try again or contact support.") + + # Reset button + if st.sidebar.button("Reset Application"): + for key in st.session_state.keys(): + if key != 'openai_api_key': + del st.session_state[key] + st.rerun() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt b/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt new file mode 100644 index 0000000..caaaa4e --- /dev/null +++ b/ai_agent_tutorials/ai_recruitment_agent_team/requirements.txt @@ -0,0 +1,13 @@ +# Core dependencies +phidata +agno +streamlit==1.40.2 +PyPDF2==3.0.1 +streamlit-pdf-viewer==0.0.19 +requests==2.32.3 +pytz==2023.4 +typing-extensions>=4.9.0 + +# Optional but recommended +black>=24.1.1 # for code formatting +python-dateutil>=2.8.2 # for date parsing diff --git a/ai_agent_tutorials/ai_services_agency/README.md b/ai_agent_tutorials/ai_services_agency/README.md new file mode 100644 index 0000000..4003cea --- /dev/null +++ b/ai_agent_tutorials/ai_services_agency/README.md @@ -0,0 +1,80 @@ +# AI Services Agency ๐Ÿ‘จโ€๐Ÿ’ผ + +An AI application that simulates a full-service digital agency using multiple AI agents to analyze and plan software projects. Each agent represents a different role in the project lifecycle, from strategic planning to technical implementation. + +## Demo: + +https://github.com/user-attachments/assets/a0befa3a-f4c3-400d-9790-4b9e37254405 + +## Features + +### Five specialized AI agents + +- **CEO Agent**: Strategic leader and final decision maker + - Analyzes startup ideas using structured evaluation + - Makes strategic decisions across product, technical, marketing, and financial domains + - Uses AnalyzeStartupTool and MakeStrategicDecision tools + +- **CTO Agent**: Technical architecture and feasibility expert + - Evaluates technical requirements and feasibility + - Provides architecture decisions + - Uses QueryTechnicalRequirements and EvaluateTechnicalFeasibility tools + +- **Product Manager Agent**: Product strategy specialist + - Defines product strategy and roadmap + - Coordinates between technical and marketing teams + - Focuses on product-market fit + +- **Developer Agent**: Technical implementation expert + - Provides detailed technical implementation guidance + - Suggests optimal tech stack and cloud solutions + - Estimates development costs and timelines + +- **Client Success Agent**: Marketing strategy leader + - Develops go-to-market strategies + - Plans customer acquisition approaches + - Coordinates with product team + +### Custom Tools + +The agency uses specialized tools built with OpenAI Schema for structured analysis: +- **Analysis Tools**: AnalyzeProjectRequirements for market evaluation and analysis of startup idea +- **Technical Tools**: CreateTechnicalSpecification for technical assessment + +### ๐Ÿ”„ Asynchronous Communication + +The agency operates in async mode, enabling: +- Parallel processing of analyses from different agents +- Efficient multi-agent collaboration +- Real-time communication between agents +- Non-blocking operations for better performance + +### ๐Ÿ”— Agent Communication Flows +- CEO โ†”๏ธ All Agents (Strategic Oversight) +- CTO โ†”๏ธ Developer (Technical Implementation) +- Product Manager โ†”๏ธ Marketing Manager (Go-to-Market Strategy) +- Product Manager โ†”๏ธ Developer (Feature Implementation) +- (and more!) + +## How to Run + +Follow the steps below to set up and run the application: +Before anything else, Please get your OpenAI API Key here: https://platform.openai.com/api-keys + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/ai_agent_tutorials/ai_services_agency + ``` + +2. **Install the dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Run the Streamlit app**: + ```bash + streamlit run ai_services_agency/agency.py + ``` + +4. **Enter your OpenAI API Key** in the sidebar when prompted and start analyzing your startup idea! diff --git a/ai_agent_tutorials/ai_services_agency/agency.py b/ai_agent_tutorials/ai_services_agency/agency.py new file mode 100644 index 0000000..43ab885 --- /dev/null +++ b/ai_agent_tutorials/ai_services_agency/agency.py @@ -0,0 +1,365 @@ +from typing import List, Literal, Dict, Optional +from agency_swarm import Agent, Agency, set_openai_key, BaseTool +from pydantic import Field, BaseModel +import streamlit as st + +class AnalyzeProjectRequirements(BaseTool): + project_name: str = Field(..., description="Name of the project") + project_description: str = Field(..., description="Project description and goals") + project_type: Literal["Web Application", "Mobile App", "API Development", + "Data Analytics", "AI/ML Solution", "Other"] = Field(..., + description="Type of project") + budget_range: Literal["$10k-$25k", "$25k-$50k", "$50k-$100k", "$100k+"] = Field(..., + description="Budget range for the project") + + class ToolConfig: + name = "analyze_project" + description = "Analyzes project requirements and feasibility" + one_call_at_a_time = True + + def run(self) -> str: + """Analyzes project and stores results in shared state""" + if self._shared_state.get("project_analysis", None) is not None: + raise ValueError("Project analysis already exists. Please proceed with technical specification.") + + analysis = { + "name": self.project_name, + "type": self.project_type, + "complexity": "high", + "timeline": "6 months", + "budget_feasibility": "within range", + "requirements": ["Scalable architecture", "Security", "API integration"] + } + + self._shared_state.set("project_analysis", analysis) + return "Project analysis completed. Please proceed with technical specification." + +class CreateTechnicalSpecification(BaseTool): + architecture_type: Literal["monolithic", "microservices", "serverless", "hybrid"] = Field( + ..., + description="Proposed architecture type" + ) + core_technologies: str = Field( + ..., + description="Comma-separated list of main technologies and frameworks" + ) + scalability_requirements: Literal["high", "medium", "low"] = Field( + ..., + description="Scalability needs" + ) + + class ToolConfig: + name = "create_technical_spec" + description = "Creates technical specifications based on project analysis" + one_call_at_a_time = True + + def run(self) -> str: + """Creates technical specification based on analysis""" + project_analysis = self._shared_state.get("project_analysis", None) + if project_analysis is None: + raise ValueError("Please analyze project requirements first using AnalyzeProjectRequirements tool.") + + spec = { + "project_name": project_analysis["name"], + "architecture": self.architecture_type, + "technologies": self.core_technologies.split(","), + "scalability": self.scalability_requirements + } + + self._shared_state.set("technical_specification", spec) + return f"Technical specification created for {project_analysis['name']}." + +def init_session_state() -> None: + """Initialize session state variables""" + if 'messages' not in st.session_state: + st.session_state.messages = [] + if 'api_key' not in st.session_state: + st.session_state.api_key = None + +def main() -> None: + st.set_page_config(page_title="AI Services Agency", layout="wide") + init_session_state() + + st.title("๐Ÿš€ AI Services Agency") + + # API Configuration + with st.sidebar: + st.header("๐Ÿ”‘ API Configuration") + openai_api_key = st.text_input( + "OpenAI API Key", + type="password", + help="Enter your OpenAI API key to continue" + ) + + if openai_api_key: + st.session_state.api_key = openai_api_key + st.success("API Key accepted!") + else: + st.warning("โš ๏ธ Please enter your OpenAI API Key to proceed") + st.markdown("[Get your API key here](https://platform.openai.com/api-keys)") + return + + # Initialize agents with the provided API key + set_openai_key(st.session_state.api_key) + api_headers = {"Authorization": f"Bearer {st.session_state.api_key}"} + + # Project Input Form + with st.form("project_form"): + st.subheader("Project Details") + + project_name = st.text_input("Project Name") + project_description = st.text_area( + "Project Description", + help="Describe the project, its goals, and any specific requirements" + ) + + col1, col2 = st.columns(2) + with col1: + project_type = st.selectbox( + "Project Type", + ["Web Application", "Mobile App", "API Development", + "Data Analytics", "AI/ML Solution", "Other"] + ) + timeline = st.selectbox( + "Expected Timeline", + ["1-2 months", "3-4 months", "5-6 months", "6+ months"] + ) + + with col2: + budget_range = st.selectbox( + "Budget Range", + ["$10k-$25k", "$25k-$50k", "$50k-$100k", "$100k+"] + ) + priority = st.selectbox( + "Project Priority", + ["High", "Medium", "Low"] + ) + + tech_requirements = st.text_area( + "Technical Requirements (optional)", + help="Any specific technical requirements or preferences" + ) + + special_considerations = st.text_area( + "Special Considerations (optional)", + help="Any additional information or special requirements" + ) + + submitted = st.form_submit_button("Analyze Project") + + if submitted and project_name and project_description: + try: + # Set OpenAI key + set_openai_key(st.session_state.api_key) + + # Create agents + ceo = Agent( + name="Project Director", + description="You are a CEO of multiple companies in the past and have a lot of experience in evaluating projects and making strategic decisions.", + instructions=""" + You are an experienced CEO who evaluates projects. Follow these steps strictly: + + 1. FIRST, use the AnalyzeProjectRequirements tool with: + - project_name: The name from the project details + - project_description: The full project description + - project_type: The type of project (Web Application, Mobile App, etc) + - budget_range: The specified budget range + + 2. WAIT for the analysis to complete before proceeding. + + 3. Review the analysis results and provide strategic recommendations. + """, + tools=[AnalyzeProjectRequirements], + api_headers=api_headers, + temperature=0.7, + max_prompt_tokens=25000 + ) + + cto = Agent( + name="Technical Architect", + description="Senior technical architect with deep expertise in system design.", + instructions=""" + You are a technical architect. Follow these steps strictly: + + 1. WAIT for the project analysis to be completed by the CEO. + + 2. Use the CreateTechnicalSpecification tool with: + - architecture_type: Choose from monolithic/microservices/serverless/hybrid + - core_technologies: List main technologies as comma-separated values + - scalability_requirements: Choose high/medium/low based on project needs + + 3. Review the technical specification and provide additional recommendations. + """, + tools=[CreateTechnicalSpecification], + api_headers=api_headers, + temperature=0.5, + max_prompt_tokens=25000 + ) + + product_manager = Agent( + name="Product Manager", + description="Experienced product manager focused on delivery excellence.", + instructions=""" + - Manage project scope and timeline giving the roadmap of the project + - Define product requirements and you should give potential products and features that can be built for the startup + """, + api_headers=api_headers, + temperature=0.4, + max_prompt_tokens=25000 + ) + + developer = Agent( + name="Lead Developer", + description="Senior developer with full-stack expertise.", + instructions=""" + - Plan technical implementation + - Provide effort estimates + - Review technical feasibility + """, + api_headers=api_headers, + temperature=0.3, + max_prompt_tokens=25000 + ) + + client_manager = Agent( + name="Client Success Manager", + description="Experienced client manager focused on project delivery.", + instructions=""" + - Ensure client satisfaction + - Manage expectations + - Handle feedback + """, + api_headers=api_headers, + temperature=0.6, + max_prompt_tokens=25000 + ) + + # Create agency + agency = Agency( + [ + ceo, cto, product_manager, developer, client_manager, + [ceo, cto], + [ceo, product_manager], + [ceo, developer], + [ceo, client_manager], + [cto, developer], + [product_manager, developer], + [product_manager, client_manager] + ], + async_mode='threading', + shared_files='shared_files' + ) + + # Prepare project info + project_info = { + "name": project_name, + "description": project_description, + "type": project_type, + "timeline": timeline, + "budget": budget_range, + "priority": priority, + "technical_requirements": tech_requirements, + "special_considerations": special_considerations + } + + st.session_state.messages.append({"role": "user", "content": str(project_info)}) + # Create tabs and run analysis + with st.spinner("AI Services Agency is analyzing your project..."): + try: + # Get analysis from each agent using agency.get_completion() + ceo_response = agency.get_completion( + message=f"""Analyze this project using the AnalyzeProjectRequirements tool: + Project Name: {project_name} + Project Description: {project_description} + Project Type: {project_type} + Budget Range: {budget_range} + + Use these exact values with the tool and wait for the analysis results.""", + recipient_agent=ceo + ) + + cto_response = agency.get_completion( + message=f"""Review the project analysis and create technical specifications using the CreateTechnicalSpecification tool. + Choose the most appropriate: + - architecture_type (monolithic/microservices/serverless/hybrid) + - core_technologies (comma-separated list) + - scalability_requirements (high/medium/low) + + Base your choices on the project requirements and analysis.""", + recipient_agent=cto + ) + + pm_response = agency.get_completion( + message=f"Analyze project management aspects: {str(project_info)}", + recipient_agent=product_manager, + additional_instructions="Focus on product-market fit and roadmap development, and coordinate with technical and marketing teams." + ) + + developer_response = agency.get_completion( + message=f"Analyze technical implementation based on CTO's specifications: {str(project_info)}", + recipient_agent=developer, + additional_instructions="Provide technical implementation details, optimal tech stack you would be using including the costs of cloud services (if any) and feasibility feedback, and coordinate with product manager and CTO to build the required products for the startup." + ) + + client_response = agency.get_completion( + message=f"Analyze client success aspects: {str(project_info)}", + recipient_agent=client_manager, + additional_instructions="Provide detailed go-to-market strategy and customer acquisition plan, and coordinate with product manager." + ) + + # Create tabs for different analyses + tabs = st.tabs([ + "CEO's Project Analysis", + "CTO's Technical Specification", + "Product Manager's Plan", + "Developer's Implementation", + "Client Success Strategy" + ]) + + with tabs[0]: + st.markdown("## CEO's Strategic Analysis") + st.markdown(ceo_response) + st.session_state.messages.append({"role": "assistant", "content": ceo_response}) + + with tabs[1]: + st.markdown("## CTO's Technical Specification") + st.markdown(cto_response) + st.session_state.messages.append({"role": "assistant", "content": cto_response}) + + with tabs[2]: + st.markdown("## Product Manager's Plan") + st.markdown(pm_response) + st.session_state.messages.append({"role": "assistant", "content": pm_response}) + + with tabs[3]: + st.markdown("## Lead Developer's Development Plan") + st.markdown(developer_response) + st.session_state.messages.append({"role": "assistant", "content": developer_response}) + + with tabs[4]: + st.markdown("## Client Success Strategy") + st.markdown(client_response) + st.session_state.messages.append({"role": "assistant", "content": client_response}) + + except Exception as e: + st.error(f"Error during analysis: {str(e)}") + st.error("Please check your inputs and API key and try again.") + + except Exception as e: + st.error(f"Error during analysis: {str(e)}") + st.error("Please check your API key and try again.") + + # Add history management in sidebar + with st.sidebar: + st.subheader("Options") + if st.checkbox("Show Analysis History"): + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + + if st.button("Clear History"): + st.session_state.messages = [] + st.rerun() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_services_agency/requirements.txt b/ai_agent_tutorials/ai_services_agency/requirements.txt new file mode 100644 index 0000000..a41c04a --- /dev/null +++ b/ai_agent_tutorials/ai_services_agency/requirements.txt @@ -0,0 +1,3 @@ +python-dotenv==1.0.1 +agency-swarm==0.4.1 +streamlit \ No newline at end of file diff --git a/ai_agent_tutorials/ai_startup_trend_analysis_agent/README.md b/ai_agent_tutorials/ai_startup_trend_analysis_agent/README.md new file mode 100644 index 0000000..5402738 --- /dev/null +++ b/ai_agent_tutorials/ai_startup_trend_analysis_agent/README.md @@ -0,0 +1,42 @@ +## ๐Ÿ“ˆ AI Startup Trend Analysis Agent +The AI Startup Trend Analysis Agent is tool for budding entrepreneurs that generates actionable insights by identifying nascent trends, potential market gaps, and growth opportunities in specific sectors. Entrepreneurs can use these data-driven insights to validate ideas, spot market opportunities, and make informed decisions about their startup ventures. It combines Newspaper4k and DuckDuckGo to scan and analyze startup-focused articles and market data. Using Claude 3.5 Sonnet, it processes this information to extract emerging patterns and enable entrepreneurs to identify promising startup opportunities. + + +### Features +- **User Prompt**: Entrepreneurs can input specific startup sectors or technologies of interest for research. +- **News Collection**: This agent gathers recent startup news, funding rounds, and market analyses using DuckDuckGo. +- **Summary Generation**: Concise summaries of verified information are generated using Newspaper4k. +- **Trend Analysis**: The system identifies emerging patterns in startup funding, technology adoption, and market opportunities across analyzed stories. +- **Streamlit UI**: The application features a user-friendly interface built with Streamlit for easy interaction. + +### How to Get Started +1. **Clone the repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/ai_agent_tutorials/ai_startup_trend_analysis_agent + ``` + +2. **Create and activate a virtual environment**: + ```bash + # For macOS/Linux + python -m venv venv + source venv/bin/activate + + # For Windows + python -m venv venv + .\venv\Scripts\activate + ``` + +3. **Install the required packages**: + ```bash + pip install -r requirements.txt + ``` + +4. **Run the application**: + ```bash + streamlit run startup_trends_agent.py + ``` +### Important Note +- The system specifically uses Claude's API for advanced language processing. You can obtain your Anthropic API key from [Anthropic's website](https://www.anthropic.com/api). + + diff --git a/ai_agent_tutorials/ai_startup_trend_analysis_agent/requirements.txt b/ai_agent_tutorials/ai_startup_trend_analysis_agent/requirements.txt new file mode 100644 index 0000000..00ecb40 --- /dev/null +++ b/ai_agent_tutorials/ai_startup_trend_analysis_agent/requirements.txt @@ -0,0 +1,5 @@ +agno +streamlit==1.40.2 +duckduckgo_search==6.3.7 +newspaper4k==0.9.3.1 +lxml_html_clean==0.4.1 \ No newline at end of file diff --git a/ai_agent_tutorials/ai_startup_trend_analysis_agent/startup_trends_agent.py b/ai_agent_tutorials/ai_startup_trend_analysis_agent/startup_trends_agent.py new file mode 100644 index 0000000..8b9a722 --- /dev/null +++ b/ai_agent_tutorials/ai_startup_trend_analysis_agent/startup_trends_agent.py @@ -0,0 +1,99 @@ +import streamlit as st +from agno.agent import Agent +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.models.anthropic import Claude +from agno.tools.newspaper4k import Newspaper4kTools +from agno.tools import Tool +import logging + +logging.basicConfig(level=logging.DEBUG) + +# Setting up Streamlit app +st.title("AI Startup Trend Analysis Agent ๐Ÿ“ˆ") +st.caption("Get the latest trend analysis and startup opportunities based on your topic of interest in a click!.") + +topic = st.text_input("Enter the area of interest for your Startup:") +anthropic_api_key = st.sidebar.text_input("Enter Anthropic API Key", type="password") + +if st.button("Generate Analysis"): + if not anthropic_api_key: + st.warning("Please enter the required API key.") + else: + with st.spinner("Processing your request..."): + try: + # Initialize Anthropic model + anthropic_model = Claude(id ="claude-3-5-sonnet-20240620",api_key=anthropic_api_key) + + # Define News Collector Agent - Duckduckgo_search tool enables an Agent to search the web for information. + search_tool = DuckDuckGoTools(search=True, news=True, fixed_max_results=5) + news_collector = Agent( + name="News Collector", + role="Collects recent news articles on the given topic", + tools=[search_tool], + model=anthropic_model, + instructions=["Gather latest articles on the topic"], + show_tool_calls=True, + markdown=True, + ) + + # Define Summary Writer Agent + news_tool = Newspaper4kTools(read_article=True, include_summary=True) + summary_writer = Agent( + name="Summary Writer", + role="Summarizes collected news articles", + tools=[news_tool], + model=anthropic_model, + instructions=["Provide concise summaries of the articles"], + show_tool_calls=True, + markdown=True, + ) + + # Define Trend Analyzer Agent + trend_analyzer = Agent( + name="Trend Analyzer", + role="Analyzes trends from summaries", + model=anthropic_model, + instructions=["Identify emerging trends and startup opportunities"], + show_tool_calls=True, + markdown=True, + ) + + # The multi agent Team setup of phidata: + agent_team = Agent( + agents=[news_collector, summary_writer, trend_analyzer], + instructions=[ + "First, search DuckDuckGo for recent news articles related to the user's specified topic.", + "Then, provide the collected article links to the summary writer.", + "Important: you must ensure that the summary writer receives all the article links to read.", + "Next, the summary writer will read the articles and prepare concise summaries of each.", + "After summarizing, the summaries will be passed to the trend analyzer.", + "Finally, the trend analyzer will identify emerging trends and potential startup opportunities based on the summaries provided in a detailed Report form so that any young entreprenur can get insane value reading this easily" + ], + show_tool_calls=True, + markdown=True, + ) + + # Executing the workflow + # Step 1: Collect news + news_response = news_collector.run(f"Collect recent news on {topic}") + articles = news_response.content + + # Step 2: Summarize articles + summary_response = summary_writer.run(f"Summarize the following articles:\n{articles}") + summaries = summary_response.content + + # Step 3: Analyze trends + trend_response = trend_analyzer.run(f"Analyze trends from the following summaries:\n{summaries}") + analysis = trend_response.content + + # Display results - if incase you want to use this furthur, you can uncomment the below 2 lines to get the summaries too! + # st.subheader("News Summaries") + # # st.write(summaries) + + st.subheader("Trend Analysis and Potential Startup Opportunities") + st.write(analysis) + + except Exception as e: + st.error(f"An error occurred: {e}") +else: + st.info("Enter the topic and API keys, then click 'Generate Analysis' to start.") diff --git a/ai_agent_tutorials/ai_system_architect_r1/README.md b/ai_agent_tutorials/ai_system_architect_r1/README.md new file mode 100644 index 0000000..a78e925 --- /dev/null +++ b/ai_agent_tutorials/ai_system_architect_r1/README.md @@ -0,0 +1,74 @@ +# ๐Ÿค– AI System Architect Advisor with R1 + +An Agno agentic system that provides expert software architecture analysis and recommendations using a dual-model approach combining DeepSeek R1's Reasoning and Claude. The system provides detailed technical analysis, implementation roadmaps, and architectural decisions for complex software systems. + +## Features + +- **Dual AI Model Architecture** + - **DeepSeek Reasoner**: Provides initial technical analysis and structured reasoning about architecture patterns, tools, and implementation strategies + - **Claude-3.5**: Generates detailed explanations, implementation roadmaps, and technical specifications based on DeepSeek's analysis + +- **Comprehensive Analysis Components** + - Architecture Pattern Selection + - Infrastructure Resource Planning + - Security Measures and Compliance + - Database Architecture + - Performance Requirements + - Cost Estimation + - Risk Assessment + +- **Analysis Types** + - Real-time Event Processing Systems + - Healthcare Data Platforms + - Financial Trading Platforms + - Multi-tenant SaaS Solutions + - Digital Content Delivery Networks + - Supply Chain Management Systems + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/ai_agent_tutorials/ai_system_architect_r1 + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Configure API Keys** + - Get DeepSeek API key from DeepSeek platform + - Get Anthropic API key from [Anthropic Platform](https://www.anthropic.com) + +3. **Run the Application** + ```bash + streamlit run ai_system_architect_r1.py + ``` + +4. **Use the Interface** + - Enter API credentials in sidebar + - Structure your prompt with: + - Project Context + - Requirements + - Constraints + - Scale + - Security/Compliance needs + - View detailed analysis results + +## Example Test Prompts: + +### 1. Financial Trading Platform +"We need to build a high-frequency trading platform that processes market data streams, executes trades with sub-millisecond latency, maintains audit trails, and handles complex risk calculations. The system needs to be globally distributed, handle 100,000 transactions per second, and have robust disaster recovery capabilities." +### 2. Multi-tenant SaaS Platform +"Design a multi-tenant SaaS platform for enterprise resource planning that needs to support customization per tenant, handle different data residency requirements, support offline capabilities, and maintain performance isolation between tenants. The system should scale to 10,000 concurrent users and support custom integrations." + +## Notes + +- Requires both DeepSeek and Anthropic API keys +- Provides real-time analysis with detailed explanations +- Supports chat-based interaction +- Includes clear reasoning for all architectural decisions +- API usage costs apply + + diff --git a/ai_agent_tutorials/ai_system_architect_r1/ai_system_architect_r1.py b/ai_agent_tutorials/ai_system_architect_r1/ai_system_architect_r1.py new file mode 100644 index 0000000..5236c94 --- /dev/null +++ b/ai_agent_tutorials/ai_system_architect_r1/ai_system_architect_r1.py @@ -0,0 +1,315 @@ +from typing import Optional, List, Dict, Any, Union +import os +import time +import streamlit as st +from openai import OpenAI +import anthropic +from dotenv import load_dotenv +from pydantic import BaseModel, Field +from enum import Enum +import json +from agno.agent import Agent, RunResponse +from agno.models.anthropic import Claude + +# Model Constants +DEEPSEEK_MODEL: str = "deepseek-reasoner" +CLAUDE_MODEL: str = "claude-3-5-sonnet-20241022" + +class ArchitecturePattern(str, Enum): + """Architectural patterns for system design.""" + MICROSERVICES = "microservices" # Decomposed into small, independent services + MONOLITHIC = "monolithic" # Single, unified codebase + SERVERLESS = "serverless" # Function-as-a-Service architecture + EVENT_DRIVEN = "event_driven" # Asynchronous event-based communication + +class DatabaseType(str, Enum): + """Types of database systems.""" + SQL = "sql" # Relational databases with ACID properties + NOSQL = "nosql" # Non-relational databases for flexible schemas + HYBRID = "hybrid" # Combined SQL and NoSQL approach + +class ComplianceStandard(str, Enum): + """Regulatory compliance standards.""" + HIPAA = "hipaa" # Healthcare data protection + GDPR = "gdpr" # EU data privacy regulation + SOC2 = "soc2" # Service organization security controls + ISO27001 = "iso27001" # Information security management + +class ArchitectureDecision(BaseModel): + """Represents architectural decisions and their justifications.""" + pattern: ArchitecturePattern + rationale: str = Field(..., min_length=50) # Detailed explanation for the choice + trade_offs: Dict[str, List[str]] = Field(..., alias="trade_offs") # Pros and cons + estimated_cost: Dict[str, float] # Cost breakdown + +class SecurityMeasure(BaseModel): + """Security controls and implementation details.""" + measure_type: str # Type of security measure + implementation_priority: int = Field(..., ge=1, le=5) # Priority level 1-5 + compliance_standards: List[ComplianceStandard] # Applicable standards + data_classification: str # Data sensitivity level + +class InfrastructureResource(BaseModel): + """Infrastructure components and specifications.""" + resource_type: str # Type of infrastructure resource + specifications: Dict[str, str] # Technical specifications + scaling_policy: Dict[str, str] # Scaling rules and thresholds + estimated_cost: float # Estimated cost per resource + +class TechnicalAnalysis(BaseModel): + """Complete technical analysis of the system architecture.""" + architecture_decision: ArchitectureDecision # Core architecture choices + infrastructure_resources: List[InfrastructureResource] # Required resources + security_measures: List[SecurityMeasure] # Security controls + database_choice: DatabaseType # Database architecture + compliance_requirements: List[ComplianceStandard] = [] # Required standards + performance_requirements: List[Dict[str, Union[str, float]]] = [] # Performance metrics + risk_assessment: Dict[str, str] = {} # Identified risks and mitigations + + +class ModelChain: + def __init__(self, deepseek_api_key: str, anthropic_api_key: str) -> None: + self.client = OpenAI( + api_key=deepseek_api_key, + base_url="https://api.deepseek.com" + ) + self.claude_client = anthropic.Anthropic(api_key=anthropic_api_key) + + # Create Claude model with system prompt + claude_model = Claude( + id="claude-3-5-sonnet-20241022", + api_key=anthropic_api_key, + system_prompt="""Given the user's query and the DeepSeek reasoning: + 1. Provide a detailed analysis of the architecture decisions + 2. Generate a project implementation roadmap + 3. Create a comprehensive technical specification document + 4. Format the output in clean markdown with proper sections + 5. Include diagrams descriptions in mermaid.js format""" + ) + + # Initialize agent with configured model + self.agent = Agent( + model=claude_model, + markdown=True + ) + + self.deepseek_messages: List[Dict[str, str]] = [] + self.claude_messages: List[Dict[str, Any]] = [] + self.current_model: str = CLAUDE_MODEL + def get_deepseek_reasoning(self, user_input: str) -> tuple[str, str]: + start_time = time.time() + + system_prompt = """You are an expert software architect and technical advisor. Analyze the user's project requirements + and provide structured reasoning about architecture, tools, and implementation strategies. + + IMPORTANT: Reason why you are choosing a particular architecture pattern, database type, etc. for user understanding in your reasoning. + + IMPORTANT: Your response must be a valid JSON object (not a string or any other format) that matches the schema provided below. + Do not include any explanatory text, markdown formatting, or code blocks - only return the JSON object. + + Schema: + { + "architecture_decision": { + "pattern": "one of: microservices|monolithic|serverless|event_driven|layered", + "rationale": "string", + "trade_offs": {"advantage": ["list of strings"], "disadvantage": ["list of strings"]}, + "estimated_cost": {"implementation": float, "maintenance": float} + }, + "infrastructure_resources": [{ + "resource_type": "string", + "specifications": {"key": "value"}, + "scaling_policy": {"key": "value"}, + "estimated_cost": float + }], + "security_measures": [{ + "measure_type": "string", + "implementation_priority": "integer 1-5", + "compliance_standards": ["hipaa", "gdpr", "soc2", "hitech", "iso27001", "pci_dss"], + "estimated_setup_time_days": "integer", + "data_classification": "one of: protected_health_information|personally_identifiable_information|confidential|public", + "encryption_requirements": {"key": "value"}, + "access_control_policy": {"role": ["permissions"]}, + "audit_requirements": ["list of strings"] + }], + "database_choice": "one of: sql|nosql|graph|time_series|hybrid", + "ml_capabilities": [{ + "model_type": "string", + "training_frequency": "string", + "input_data_types": ["list of strings"], + "performance_requirements": {"metric": float}, + "hardware_requirements": {"resource": "specification"}, + "regulatory_constraints": ["list of strings"] + }], + "data_integrations": [{ + "integration_type": "one of: hl7|fhir|dicom|rest|soap|custom", + "data_format": "string", + "frequency": "string", + "volume": "string", + "security_requirements": {"key": "value"} + }], + "performance_requirements": [{ + "metric_name": "string", + "target_value": float, + "measurement_unit": "string", + "priority": "integer 1-5" + }], + "audit_config": { + "log_retention_period": "integer", + "audit_events": ["list of strings"], + "compliance_mapping": {"standard": ["requirements"]} + }, + "api_config": { + "version": "string", + "auth_method": "string", + "rate_limits": {"role": "requests_per_minute"}, + "documentation_url": "string" + }, + "error_handling": { + "retry_policy": {"key": "value"}, + "fallback_strategies": ["list of strings"], + "notification_channels": ["list of strings"] + }, + "estimated_team_size": "integer", + "critical_path_components": ["list of strings"], + "risk_assessment": {"risk": "mitigation"}, + "maintenance_considerations": ["list of strings"], + "compliance_requirements": ["list of compliance standards"], + "data_retention_policy": {"data_type": "retention_period"}, + "disaster_recovery": {"key": "value"}, + "interoperability_standards": ["list of strings"] + } + + Consider scalability, security, maintenance, and technical debt in your analysis. + Focus on practical, modern solutions while being mindful of trade-offs.""" + + try: + deepseek_response = self.client.chat.completions.create( + model="deepseek-reasoner", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_input} + ], + max_tokens=3000, + stream=False + ) + + reasoning_content = deepseek_response.choices[0].message.reasoning_content + normal_content = deepseek_response.choices[0].message.content + + # Display the reasoning separately + with st.expander("DeepSeek Reasoning", expanded=True): + st.markdown(reasoning_content) + + + with st.expander("๐Ÿ’ญ Technical Analysis", expanded=True): + st.markdown(normal_content) + elapsed_time = time.time() - start_time + time_str = f"{elapsed_time/60:.1f} minutes" if elapsed_time >= 60 else f"{elapsed_time:.1f} seconds" + st.caption(f"โฑ๏ธ Analysis completed in {time_str}") + + # Return both reasoning and normal content + return reasoning_content, normal_content + + except Exception as e: + st.error(f"Error in DeepSeek analysis: {str(e)}") + return "Error occurred while analyzing", "" + + def get_claude_response(self, user_input: str, deepseek_output: tuple[str, str]) -> str: + try: + reasoning_content, normal_content = deepseek_output + + # Create expander for Claude's response + with st.expander("๐Ÿค– Claude's Response", expanded=True): + response_placeholder = st.empty() + + # Prepare the message with user input, reasoning and normal output + message = f"""User Query: {user_input} + + DeepSeek Reasoning: {reasoning_content} + + DeepSeek Technical Analysis: {normal_content} + Give detailed explanation for each key value pair in brief in the JSON object, and why we chose it clearly. Dont use your own opinions, use the reasoning and the structured output to explain the choices.""" + + # Use Phi Agent to get response + response: RunResponse = self.agent.run( + message=message + ) + + dub = response.content + st.markdown(dub) + return dub + + except Exception as e: + st.error(f"Error in Claude response: {str(e)}") + return "Error occurred while getting response" + +def main() -> None: + """Main function to run the Streamlit app.""" + st.title("๐Ÿค– AI System Architect Advisor with R1") + + # Add prompt guidance + st.info(""" + ๐Ÿ“ For best results, structure your prompt with: + + 1. **Project Context**: Brief description of your project/system + 2. **Requirements**: Key functional and non-functional requirements + 3. **Constraints**: Any technical, budget, or time constraints + 4. **Scale**: Expected user base and growth projections + 5. **Security/Compliance**: Any specific security or regulatory needs + + Example: + ``` + I need to build a healthcare data management system that: + - Handles patient records and appointments + - Needs to scale to 10,000 users + - Must be HIPAA compliant + - Budget constraint of $50k for initial setup + - Should integrate with existing hospital systems + ``` + """) + + # Sidebar for API keys + with st.sidebar: + st.header("โš™๏ธ Configuration") + deepseek_api_key = st.text_input("DeepSeek API Key", type="password") + anthropic_api_key = st.text_input("Anthropic API Key", type="password") + + if st.button("๐Ÿ—‘๏ธ Clear Chat History"): + st.session_state.messages = [] + st.rerun() + + # Initialize session state for messages + if "messages" not in st.session_state: + st.session_state.messages = [] + + # Display chat messages + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + + # Chat input + if prompt := st.chat_input("What would you like to know?"): + if not deepseek_api_key or not anthropic_api_key: + st.error("โš ๏ธ Please enter both API keys in the sidebar.") + return + + # Initialize ModelChain + chain = ModelChain(deepseek_api_key, anthropic_api_key) + + # Add user message to chat + st.session_state.messages.append({"role": "user", "content": prompt}) + with st.chat_message("user"): + st.markdown(prompt) + + # Get AI response + with st.chat_message("assistant"): + with st.spinner("๐Ÿค” Thinking..."): + deepseek_output = chain.get_deepseek_reasoning(prompt) + + + with st.spinner("โœ๏ธ Responding..."): + response = chain.get_claude_response(prompt, deepseek_output) + st.session_state.messages.append({"role": "assistant", "content": response}) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/ai_system_architect_r1/requirements.txt b/ai_agent_tutorials/ai_system_architect_r1/requirements.txt new file mode 100644 index 0000000..e8fce4b --- /dev/null +++ b/ai_agent_tutorials/ai_system_architect_r1/requirements.txt @@ -0,0 +1,4 @@ +streamlit +openai +anthropic +agno \ No newline at end of file diff --git a/ai_agent_tutorials/ai_teaching_agent_team/README.md b/ai_agent_tutorials/ai_teaching_agent_team/README.md new file mode 100644 index 0000000..5a67383 --- /dev/null +++ b/ai_agent_tutorials/ai_teaching_agent_team/README.md @@ -0,0 +1,75 @@ +# ๐Ÿ‘จโ€๐Ÿซ AI Teaching Agent Team + +A Streamlit application that brings together a team of specialized AI teaching agents who collaborate like a professional teaching faculty. Each agent acts as a specialized educator: a curriculum designer, learning path expert, resource librarian, and practice instructor - working together to create a complete educational experience through Google Docs. + +## ๐Ÿช„ Meet your AI Teaching Agent Team + +#### ๐Ÿง  Professor Agent +- Creates fundamental knowledge base in Google Docs +- Organizes content with proper headings and sections +- Includes detailed explanations and examples +- Output: Comprehensive knowledge base document with table of contents + +#### ๐Ÿ—บ๏ธ Academic Advisor Agent +- Designs learning path in a structured Google Doc +- Creates progressive milestone markers +- Includes time estimates and prerequisites +- Output: Visual roadmap document with clear progression paths + +#### ๐Ÿ“š Research Librarian Agent +- Compiles resources in an organized Google Doc +- Includes links to academic papers and tutorials +- Adds descriptions and difficulty levels +- Output: Categorized resource list with quality ratings + +#### โœ๏ธ Teaching Assistant Agent +- Develops exercises in an interactive Google Doc +- Creates structured practice sections +- Includes solution guides +- Output: Complete practice workbook with answers + + +## How to Run + +1. Clone the repository + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_personal_learning_agent + + # Install dependencies + pip install -r requirements.txt + ``` + +## Configuration - IMPORTANT STEP + +1. Get your OpenAI API Key +- Create an account on [OpenAI Platform](https://platform.openai.com/) +- Navigate to API Keys section +- Create a new API key + +2. Get your Composio API Key +- Create an account on [Composio Platform](https://composio.ai/) +- **IMPORTANT** - For you to use the app, you need to make new connection ID with google docs and composio.Follow the below two steps to do so: + - composio add googledocs (IN THE TERMINAL) + - Create a new connection + - Select OAUTH2 + - Select Google Account and Done. + - On the composio account website, go to apps, select google docs tool, and [click create integration](https://app.composio.dev/app/googledocs) (violet button) and click Try connecting defaultโ€™s googldocs button and we are done. + +3. Sign up and get the [SerpAPI Key](https://serpapi.com/) + +## How to Use? + +1. Start the Streamlit app +```bash +streamlit run teaching_agent_team.py +``` + +2. Use the application +- Enter your OpenAI API key in the sidebar (if not set in environment) +- Enter your Composio API key in the sidebar +- Type a topic you want to learn about (e.g., "Python Programming", "Machine Learning") +- Click "Generate Learning Plan" +- Wait for the agents to generate your personalized learning plan +- View the results and terminal output in the interface diff --git a/ai_agent_tutorials/ai_teaching_agent_team/requirements.txt b/ai_agent_tutorials/ai_teaching_agent_team/requirements.txt new file mode 100644 index 0000000..7b16c6c --- /dev/null +++ b/ai_agent_tutorials/ai_teaching_agent_team/requirements.txt @@ -0,0 +1,9 @@ +streamlit==1.41.1 +openai==1.58.1 +duckduckgo-search==6.4.1 +typing-extensions>=4.5.0 +agno +composio-phidata==0.6.9 +composio_core +composio==0.1.1 +google-search-results==2.4.2 \ No newline at end of file diff --git a/ai_agent_tutorials/ai_teaching_agent_team/teaching_agent_team.py b/ai_agent_tutorials/ai_teaching_agent_team/teaching_agent_team.py new file mode 100644 index 0000000..8306ae3 --- /dev/null +++ b/ai_agent_tutorials/ai_teaching_agent_team/teaching_agent_team.py @@ -0,0 +1,210 @@ +import streamlit as st +from agno.agent import Agent, RunResponse +from agno.models.openai import OpenAIChat +from composio_phidata import Action, ComposioToolSet +import os +from agno.tools.arxiv import ArxivTools +from agno.utils.pprint import pprint_run_response +from agno.tools.serpapi import SerpApiTools + +# Set page configuration +st.set_page_config(page_title="๐Ÿ‘จโ€๐Ÿซ AI Teaching Agent Team", layout="centered") + +# Initialize session state for API keys and topic +if 'openai_api_key' not in st.session_state: + st.session_state['openai_api_key'] = '' +if 'composio_api_key' not in st.session_state: + st.session_state['composio_api_key'] = '' +if 'serpapi_api_key' not in st.session_state: + st.session_state['serpapi_api_key'] = '' +if 'topic' not in st.session_state: + st.session_state['topic'] = '' + +# Streamlit sidebar for API keys +with st.sidebar: + st.title("API Keys Configuration") + st.session_state['openai_api_key'] = st.text_input("Enter your OpenAI API Key", type="password").strip() + st.session_state['composio_api_key'] = st.text_input("Enter your Composio API Key", type="password").strip() + st.session_state['serpapi_api_key'] = st.text_input("Enter your SerpAPI Key", type="password").strip() + + # Add info about terminal responses + st.info("Note: You can also view detailed agent responses\nin your terminal after execution.") + +# Validate API keys +if not st.session_state['openai_api_key'] or not st.session_state['composio_api_key'] or not st.session_state['serpapi_api_key']: + st.error("Please enter OpenAI, Composio, and SerpAPI keys in the sidebar.") + st.stop() + +# Set the OpenAI API key and Composio API key from session state +os.environ["OPENAI_API_KEY"] = st.session_state['openai_api_key'] + +try: + composio_toolset = ComposioToolSet(api_key=st.session_state['composio_api_key']) + google_docs_tool = composio_toolset.get_tools(actions=[Action.GOOGLEDOCS_CREATE_DOCUMENT])[0] + google_docs_tool_update = composio_toolset.get_tools(actions=[Action.GOOGLEDOCS_UPDATE_EXISTING_DOCUMENT])[0] +except Exception as e: + st.error(f"Error initializing ComposioToolSet: {e}") + st.stop() + +# Create the Professor agent (formerly KnowledgeBuilder) +professor_agent = Agent( + name="Professor", + role="Research and Knowledge Specialist", + model=OpenAIChat(id="gpt-4o-mini", api_key=st.session_state['openai_api_key']), + tools=[google_docs_tool], + instructions=[ + "Create a comprehensive knowledge base that covers fundamental concepts, advanced topics, and current developments of the given topic.", + "Exlain the topic from first principles first. Include key terminology, core principles, and practical applications and make it as a detailed report that anyone who's starting out can read and get maximum value out of it.", + "Make sure it is formatted in a way that is easy to read and understand. DONT FORGET TO CREATE THE GOOGLE DOCUMENT.", + "Open a new Google Doc and write down the response of the agent neatly with great formatting and structure in it. **Include the Google Doc link in your response.**", + ], + show_tool_calls=True, + markdown=True, +) + +# Create the Academic Advisor agent (formerly RoadmapArchitect) +academic_advisor_agent = Agent( + name="Academic Advisor", + role="Learning Path Designer", + model=OpenAIChat(id="gpt-4o-mini", api_key=st.session_state['openai_api_key']), + tools=[google_docs_tool], + instructions=[ + "Using the knowledge base for the given topic, create a detailed learning roadmap.", + "Break down the topic into logical subtopics and arrange them in order of progression, a detailed report of roadmap that includes all the subtopics in order to be an expert in this topic.", + "Include estimated time commitments for each section.", + "Present the roadmap in a clear, structured format. DONT FORGET TO CREATE THE GOOGLE DOCUMENT.", + "Open a new Google Doc and write down the response of the agent neatly with great formatting and structure in it. **Include the Google Doc link in your response.**", + + ], + show_tool_calls=True, + markdown=True +) + +# Create the Research Librarian agent (formerly ResourceCurator) +research_librarian_agent = Agent( + name="Research Librarian", + role="Learning Resource Specialist", + model=OpenAIChat(id="gpt-4o-mini", api_key=st.session_state['openai_api_key']), + tools=[google_docs_tool, SerpApiTools(api_key=st.session_state['serpapi_api_key']) ], + instructions=[ + "Make a list of high-quality learning resources for the given topic.", + "Use the SerpApi search tool to find current and relevant learning materials.", + "Using SerpApi search tool, Include technical blogs, GitHub repositories, official documentation, video tutorials, and courses.", + "Present the resources in a curated list with descriptions and quality assessments. DONT FORGET TO CREATE THE GOOGLE DOCUMENT.", + "Open a new Google Doc and write down the response of the agent neatly with great formatting and structure in it. **Include the Google Doc link in your response.**", + ], + show_tool_calls=True, + markdown=True, +) + +# Create the Teaching Assistant agent (formerly PracticeDesigner) +teaching_assistant_agent = Agent( + name="Teaching Assistant", + role="Exercise Creator", + model=OpenAIChat(id="gpt-4o-mini", api_key=st.session_state['openai_api_key']), + tools=[google_docs_tool, SerpApiTools(api_key=st.session_state['serpapi_api_key'])], + instructions=[ + "Create comprehensive practice materials for the given topic.", + "Use the SerpApi search tool to find example problems and real-world applications.", + "Include progressive exercises, quizzes, hands-on projects, and real-world application scenarios.", + "Ensure the materials align with the roadmap progression.", + "Provide detailed solutions and explanations for all practice materials.DONT FORGET TO CREATE THE GOOGLE DOCUMENT.", + "Open a new Google Doc and write down the response of the agent neatly with great formatting and structure in it. **Include the Google Doc link in your response.**", + ], + show_tool_calls=True, + markdown=True, +) + +# Streamlit main UI +st.title("๐Ÿ‘จโ€๐Ÿซ AI Teaching Agent Team") +st.markdown("Enter a topic to generate a detailed learning path and resources") + +# Add info message about Google Docs +st.info("๐Ÿ“ The agents will create detailed Google Docs for each section (Professor, Academic Advisor, Research Librarian, and Teaching Assistant). The links to these documents will be displayed below after processing.") + +# Query bar for topic input +st.session_state['topic'] = st.text_input("Enter the topic you want to learn about:", placeholder="e.g., Machine Learning, LoRA, etc.") + +# Start button +if st.button("Start"): + if not st.session_state['topic']: + st.error("Please enter a topic.") + else: + # Display loading animations while generating responses + with st.spinner("Generating Knowledge Base..."): + professor_response: RunResponse = professor_agent.run( + f"the topic is: {st.session_state['topic']},Don't forget to add the Google Doc link in your response.", + stream=False + ) + + with st.spinner("Generating Learning Roadmap..."): + academic_advisor_response: RunResponse = academic_advisor_agent.run( + f"the topic is: {st.session_state['topic']},Don't forget to add the Google Doc link in your response.", + stream=False + ) + + with st.spinner("Curating Learning Resources..."): + research_librarian_response: RunResponse = research_librarian_agent.run( + f"the topic is: {st.session_state['topic']},Don't forget to add the Google Doc link in your response.", + stream=False + ) + + with st.spinner("Creating Practice Materials..."): + teaching_assistant_response: RunResponse = teaching_assistant_agent.run( + f"the topic is: {st.session_state['topic']},Don't forget to add the Google Doc link in your response.", + stream=False + ) + + # Extract Google Doc links from the responses + def extract_google_doc_link(response_content): + # Assuming the Google Doc link is embedded in the response content + # You may need to adjust this logic based on the actual response format + if "https://docs.google.com" in response_content: + return response_content.split("https://docs.google.com")[1].split()[0] + return None + + professor_doc_link = extract_google_doc_link(professor_response.content) + academic_advisor_doc_link = extract_google_doc_link(academic_advisor_response.content) + research_librarian_doc_link = extract_google_doc_link(research_librarian_response.content) + teaching_assistant_doc_link = extract_google_doc_link(teaching_assistant_response.content) + + # Display Google Doc links at the top of the Streamlit UI + st.markdown("### Google Doc Links:") + if professor_doc_link: + st.markdown(f"- **Professor Document:** [View Document](https://docs.google.com{professor_doc_link})") + if academic_advisor_doc_link: + st.markdown(f"- **Academic Advisor Document:** [View Document](https://docs.google.com{academic_advisor_doc_link})") + if research_librarian_doc_link: + st.markdown(f"- **Research Librarian Document:** [View Document](https://docs.google.com{research_librarian_doc_link})") + if teaching_assistant_doc_link: + st.markdown(f"- **Teaching Assistant Document:** [View Document](https://docs.google.com{teaching_assistant_doc_link})") + + # Display responses in the Streamlit UI using pprint_run_response + st.markdown("### Professor Response:") + st.markdown(professor_response.content) + pprint_run_response(professor_response, markdown=True) + st.divider() + + st.markdown("### Academic Advisor Response:") + st.markdown(academic_advisor_response.content) + pprint_run_response(academic_advisor_response, markdown=True) + st.divider() + + st.markdown("### Research Librarian Response:") + st.markdown(research_librarian_response.content) + pprint_run_response(research_librarian_response, markdown=True) + st.divider() + + st.markdown("### Teaching Assistant Response:") + st.markdown(teaching_assistant_response.content) + pprint_run_response(teaching_assistant_response, markdown=True) + st.divider() +# Information about the agents +st.markdown("---") +st.markdown("### About the Agents:") +st.markdown(""" +- **Professor**: Researches the topic and creates a detailed knowledge base. +- **Academic Advisor**: Designs a structured learning roadmap for the topic. +- **Research Librarian**: Curates high-quality learning resources. +- **Teaching Assistant**: Creates practice materials, exercises, and projects. +""") diff --git a/ai_agent_tutorials/ai_tic_tac_toe_agent/README.md b/ai_agent_tutorials/ai_tic_tac_toe_agent/README.md new file mode 100644 index 0000000..8ba563e --- /dev/null +++ b/ai_agent_tutorials/ai_tic_tac_toe_agent/README.md @@ -0,0 +1,106 @@ +# ๐ŸŽฎ Agent X vs Agent O: Tic-Tac-Toe Game + +An interactive Tic-Tac-Toe game where two AI agents powered by different language models compete against each other built on Agno Agent Framework and Streamlit as UI. + +This example shows how to build an interactive Tic Tac Toe game where AI agents compete against each other. The application showcases how to: +- Coordinate multiple AI agents in a turn-based game +- Use different language models for different players +- Create an interactive web interface with Streamlit +- Handle game state and move validation +- Display real-time game progress and move history + +## Features +- Multiple AI models support (GPT-4, Claude, Gemini, etc.) +- Real-time game visualization +- Move history tracking with board states +- Interactive player selection +- Game state management +- Move validation and coordination + +## How to Run? + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd ai_agent_tutorials/ai_tic_tac_toe_agent + + # Install dependencies + pip install -r requirements.txt + ``` + +### 2. Install dependencies + +```shell +pip install -r requirements.txt +``` + +### 3. Export API Keys + +The game supports multiple AI models. Export the API keys for the models you want to use: + +```shell +# Required for OpenAI models +export OPENAI_API_KEY=*** + +# Optional - for additional models +export ANTHROPIC_API_KEY=*** # For Claude models +export GOOGLE_API_KEY=*** # For Gemini models +export GROQ_API_KEY=*** # For Groq models +``` + +### 4. Run the Game + +```shell +streamlit run app.py +``` + +- Open [localhost:8501](http://localhost:8501) to view the game interface + +## How It Works + +The game consists of three agents: + +1. **Master Agent (Referee)** + - Coordinates the game + - Validates moves + - Maintains game state + - Determines game outcome + +2. **Two Player Agents** + - Make strategic moves + - Analyze board state + - Follow game rules + - Respond to opponent moves + +## Available Models + +The game supports various AI models: +- GPT-4o (OpenAI) +- GPT-o3-mini (OpenAI) +- Gemini (Google) +- Llama 3 (Groq) +- Claude (Anthropic) + +## Game Features + +1. **Interactive Board** + - Real-time updates + - Visual move tracking + - Clear game status display + +2. **Move History** + - Detailed move tracking + - Board state visualization + - Player action timeline + +3. **Game Controls** + - Start/Pause game + - Reset board + - Select AI models + - View game history + +4. **Performance Analysis** + - Move timing + - Strategy tracking + - Game statistics diff --git a/ai_agent_tutorials/ai_tic_tac_toe_agent/agents.py b/ai_agent_tutorials/ai_tic_tac_toe_agent/agents.py new file mode 100644 index 0000000..6be0dc8 --- /dev/null +++ b/ai_agent_tutorials/ai_tic_tac_toe_agent/agents.py @@ -0,0 +1,165 @@ +""" +Tic Tac Toe Battle +--------------------------------- +This example shows how to build a Tic Tac Toe game where two AI agents play against each other. +The game features a referee agent coordinating between two player agents using different +language models. + +Usage Examples: +--------------- +1. Quick game with default settings: + referee_agent = get_tic_tac_toe_referee() + play_tic_tac_toe() + +2. Game with debug mode off: + referee_agent = get_tic_tac_toe_referee(debug_mode=False) + play_tic_tac_toe(debug_mode=False) + +The game integrates: + - Multiple AI models (Claude, GPT-4, etc.) + - Turn-based gameplay coordination + - Move validation and game state management +""" + +import sys +from pathlib import Path +from textwrap import dedent +from typing import Tuple + +from agno.agent import Agent +from agno.models.anthropic import Claude +from agno.models.google import Gemini +from agno.models.groq import Groq +from agno.models.openai import OpenAIChat + +project_root = str(Path(__file__).parent.parent.parent.parent) +if project_root not in sys.path: + sys.path.append(project_root) + + +def get_model_for_provider(provider: str, model_name: str): + """ + Creates and returns the appropriate model instance based on the provider. + + Args: + provider: The model provider (e.g., 'openai', 'google', 'anthropic', 'groq') + model_name: The specific model name/ID + + Returns: + An instance of the appropriate model class + + Raises: + ValueError: If the provider is not supported + """ + if provider == "openai": + return OpenAIChat(id=model_name) + elif provider == "google": + return Gemini(id=model_name) + elif provider == "anthropic": + if model_name == "claude-3-5-sonnet": + return Claude(id="claude-3-5-sonnet-20241022", max_tokens=8192) + elif model_name == "claude-3-7-sonnet": + return Claude( + id="claude-3-7-sonnet-20250219", + max_tokens=8192, + ) + elif model_name == "claude-3-7-sonnet-thinking": + return Claude( + id="claude-3-7-sonnet-20250219", + max_tokens=8192, + thinking={"type": "enabled", "budget_tokens": 4096}, + ) + else: + return Claude(id=model_name) + elif provider == "groq": + return Groq(id=model_name) + else: + raise ValueError(f"Unsupported model provider: {provider}") + + +def get_tic_tac_toe_players( + model_x: str = "openai:gpt-4o", + model_o: str = "openai:o3-mini", + debug_mode: bool = True, +) -> Tuple[Agent, Agent]: + """ + Returns an instance of the Tic Tac Toe Referee Agent that coordinates the game. + + Args: + model_x: ModelConfig for player X + model_o: ModelConfig for player O + model_referee: ModelConfig for the referee agent + debug_mode: Enable logging and debug features + + Returns: + An instance of the configured Referee Agent + """ + # Parse model provider and name + provider_x, model_name_x = model_x.split(":") + provider_o, model_name_o = model_o.split(":") + + # Create model instances using the helper function + model_x = get_model_for_provider(provider_x, model_name_x) + model_o = get_model_for_provider(provider_o, model_name_o) + + player_x = Agent( + name="Player X", + description=dedent("""\ + You are Player X in a Tic Tac Toe game. Your goal is to win by placing three X's in a row (horizontally, vertically, or diagonally). + + BOARD LAYOUT: + - The board is a 3x3 grid with coordinates from (0,0) to (2,2) + - Top-left is (0,0), bottom-right is (2,2) + + RULES: + - You can only place X in empty spaces (shown as " " on the board) + - Players take turns placing their marks + - First to get 3 marks in a row (horizontal, vertical, or diagonal) wins + - If all spaces are filled with no winner, the game is a draw + + YOUR RESPONSE: + - Provide ONLY two numbers separated by a space (row column) + - Example: "1 2" places your X in row 1, column 2 + - Choose only from the valid moves list provided to you + + STRATEGY TIPS: + - Study the board carefully and make strategic moves + - Block your opponent's potential winning moves + - Create opportunities for multiple winning paths + - Pay attention to the valid moves and avoid illegal moves + """), + model=model_x, + debug_mode=debug_mode, + ) + + player_o = Agent( + name="Player O", + description=dedent("""\ + You are Player O in a Tic Tac Toe game. Your goal is to win by placing three O's in a row (horizontally, vertically, or diagonally). + + BOARD LAYOUT: + - The board is a 3x3 grid with coordinates from (0,0) to (2,2) + - Top-left is (0,0), bottom-right is (2,2) + + RULES: + - You can only place X in empty spaces (shown as " " on the board) + - Players take turns placing their marks + - First to get 3 marks in a row (horizontal, vertical, or diagonal) wins + - If all spaces are filled with no winner, the game is a draw + + YOUR RESPONSE: + - Provide ONLY two numbers separated by a space (row column) + - Example: "1 2" places your X in row 1, column 2 + - Choose only from the valid moves list provided to you + + STRATEGY TIPS: + - Study the board carefully and make strategic moves + - Block your opponent's potential winning moves + - Create opportunities for multiple winning paths + - Pay attention to the valid moves and avoid illegal moves + """), + model=model_o, + debug_mode=debug_mode, + ) + + return player_x, player_o diff --git a/ai_agent_tutorials/ai_tic_tac_toe_agent/app.py b/ai_agent_tutorials/ai_tic_tac_toe_agent/app.py new file mode 100644 index 0000000..17faeff --- /dev/null +++ b/ai_agent_tutorials/ai_tic_tac_toe_agent/app.py @@ -0,0 +1,261 @@ +import nest_asyncio +import streamlit as st +from agents import get_tic_tac_toe_players +from agno.utils.log import logger +from utils import ( + CUSTOM_CSS, + TicTacToeBoard, + display_board, + display_move_history, + show_agent_status, +) + +nest_asyncio.apply() + +# Page configuration +st.set_page_config( + page_title="Agent Tic Tac Toe", + page_icon="๐ŸŽฎ", + layout="wide", + initial_sidebar_state="expanded", +) + +# Load custom CSS with dark mode support +st.markdown(CUSTOM_CSS, unsafe_allow_html=True) + + +def main(): + #################################################################### + # App header + #################################################################### + st.markdown( + "

Watch Agents play Tic Tac Toe

", + unsafe_allow_html=True, + ) + + #################################################################### + # Initialize session state + #################################################################### + if "game_started" not in st.session_state: + st.session_state.game_started = False + st.session_state.game_paused = False + st.session_state.move_history = [] + + with st.sidebar: + st.markdown("### Game Controls") + model_options = { + "gpt-4o": "openai:gpt-4o", + "o3-mini": "openai:o3-mini", + "claude-3.5": "anthropic:claude-3-5-sonnet", + "claude-3.7": "anthropic:claude-3-7-sonnet", + "claude-3.7-thinking": "anthropic:claude-3-7-sonnet-thinking", + "gemini-flash": "google:gemini-2.0-flash", + "gemini-pro": "google:gemini-2.0-pro-exp-02-05", + "llama-3.3": "groq:llama-3.3-70b-versatile", + } + ################################################################ + # Model selection + ################################################################ + selected_p_x = st.selectbox( + "Select Player X", + list(model_options.keys()), + index=list(model_options.keys()).index("claude-3.7-thinking"), + key="model_p1", + ) + selected_p_o = st.selectbox( + "Select Player O", + list(model_options.keys()), + index=list(model_options.keys()).index("o3-mini"), + key="model_p2", + ) + + ################################################################ + # Game controls + ################################################################ + col1, col2 = st.columns(2) + with col1: + if not st.session_state.game_started: + if st.button("โ–ถ๏ธ Start Game"): + st.session_state.player_x, st.session_state.player_o = ( + get_tic_tac_toe_players( + model_x=model_options[selected_p_x], + model_o=model_options[selected_p_o], + debug_mode=True, + ) + ) + st.session_state.game_board = TicTacToeBoard() + st.session_state.game_started = True + st.session_state.game_paused = False + st.session_state.move_history = [] + st.rerun() + else: + game_over, _ = st.session_state.game_board.get_game_state() + if not game_over: + if st.button( + "โธ๏ธ Pause" if not st.session_state.game_paused else "โ–ถ๏ธ Resume" + ): + st.session_state.game_paused = not st.session_state.game_paused + st.rerun() + with col2: + if st.session_state.game_started: + if st.button("๐Ÿ”„ New Game"): + st.session_state.player_x, st.session_state.player_o = ( + get_tic_tac_toe_players( + model_x=model_options[selected_p_x], + model_o=model_options[selected_p_o], + debug_mode=True, + ) + ) + st.session_state.game_board = TicTacToeBoard() + st.session_state.game_paused = False + st.session_state.move_history = [] + st.rerun() + + #################################################################### + # Header showing current models + #################################################################### + if st.session_state.game_started: + st.markdown( + f"

{selected_p_x} vs {selected_p_o}

", + unsafe_allow_html=True, + ) + + #################################################################### + # Main game area + #################################################################### + if st.session_state.game_started: + game_over, status = st.session_state.game_board.get_game_state() + + display_board(st.session_state.game_board) + + # Show game status (winner/draw/current player) + if game_over: + winner_player = ( + "X" if "X wins" in status else "O" if "O wins" in status else None + ) + if winner_player: + winner_num = "1" if winner_player == "X" else "2" + winner_model = selected_p_x if winner_player == "X" else selected_p_o + st.success(f"๐Ÿ† Game Over! Player {winner_num} ({winner_model}) wins!") + else: + st.info("๐Ÿค Game Over! It's a draw!") + else: + # Show current player status + current_player = st.session_state.game_board.current_player + player_num = "1" if current_player == "X" else "2" + current_model_name = selected_p_x if current_player == "X" else selected_p_o + + show_agent_status( + f"Player {player_num} ({current_model_name})", + "It's your turn", + ) + + display_move_history() + + if not st.session_state.game_paused and not game_over: + # Thinking indicator + st.markdown( + f"""
+
+
๐Ÿ”„
+ Player {player_num} ({current_model_name}) is thinking... +
+
""", + unsafe_allow_html=True, + ) + + valid_moves = st.session_state.game_board.get_valid_moves() + + current_agent = ( + st.session_state.player_x + if current_player == "X" + else st.session_state.player_o + ) + response = current_agent.run( + f"""\ +Current board state:\n{st.session_state.game_board.get_board_state()}\n +Available valid moves (row, col): {valid_moves}\n +Choose your next move from the valid moves above. +Respond with ONLY two numbers for row and column, e.g. "1 2".""", + stream=False, + ) + + try: + import re + + numbers = re.findall(r"\d+", response.content if response else "") + row, col = map(int, numbers[:2]) + success, message = st.session_state.game_board.make_move(row, col) + + if success: + move_number = len(st.session_state.move_history) + 1 + st.session_state.move_history.append( + { + "number": move_number, + "player": f"Player {player_num} ({current_model_name})", + "move": f"{row},{col}", + } + ) + + logger.info( + f"Move {move_number}: Player {player_num} ({current_model_name}) placed at position ({row}, {col})" + ) + logger.info( + f"Board state:\n{st.session_state.game_board.get_board_state()}" + ) + + # Check game state after move + game_over, status = st.session_state.game_board.get_game_state() + if game_over: + logger.info(f"Game Over - {status}") + if "wins" in status: + st.success(f"๐Ÿ† Game Over! {status}") + else: + st.info(f"๐Ÿค Game Over! {status}") + st.session_state.game_paused = True + st.rerun() + else: + logger.error(f"Invalid move attempt: {message}") + response = current_agent.run( + f"""\ +Invalid move: {message} + +Current board state:\n{st.session_state.game_board.get_board_state()}\n +Available valid moves (row, col): {valid_moves}\n +Please choose a valid move from the list above. +Respond with ONLY two numbers for row and column, e.g. "1 2".""", + stream=False, + ) + st.rerun() + + except Exception as e: + logger.error(f"Error processing move: {str(e)}") + st.error(f"Error processing move: {str(e)}") + st.rerun() + else: + st.info("๐Ÿ‘ˆ Press 'Start Game' to begin!") + + #################################################################### + # About section + #################################################################### + st.sidebar.markdown(f""" + ### ๐ŸŽฎ Agent Tic Tac Toe Battle + Watch two agents compete in real-time! + + **Current Players:** + * ๐Ÿ”ต Player X: `{selected_p_x}` + * ๐Ÿ”ด Player O: `{selected_p_o}` + + **How it Works:** + Each Agent analyzes the board and employs strategic thinking to: + * ๐Ÿ† Find winning moves + * ๐Ÿ›ก๏ธ Block opponent victories + * โญ Control strategic positions + * ๐Ÿค” Plan multiple moves ahead + + Built with Streamlit and Agno + """) + + +if __name__ == "__main__": + main() diff --git a/ai_agent_tutorials/ai_tic_tac_toe_agent/requirements.txt b/ai_agent_tutorials/ai_tic_tac_toe_agent/requirements.txt new file mode 100644 index 0000000..c275c18 --- /dev/null +++ b/ai_agent_tutorials/ai_tic_tac_toe_agent/requirements.txt @@ -0,0 +1,238 @@ +# This file was autogenerated by uv via the following command: +# ./generate_requirements.sh +agno==1.1.6 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +altair==5.5.0 + # via streamlit +annotated-types==0.7.0 + # via pydantic +anthropic==0.47.1 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +anyio==4.8.0 + # via + # anthropic + # groq + # httpx + # openai +attrs==25.1.0 + # via + # jsonschema + # referencing +blinker==1.9.0 + # via streamlit +build==1.2.2.post1 + # via pip-tools +cachetools==5.5.2 + # via + # google-auth + # streamlit +certifi==2025.1.31 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.4.1 + # via requests +click==8.1.8 + # via + # pip-tools + # streamlit + # typer +distro==1.9.0 + # via + # anthropic + # groq + # openai +docstring-parser==0.16 + # via agno +gitdb==4.0.12 + # via gitpython +gitpython==3.1.44 + # via + # agno + # streamlit +google-auth==2.38.0 + # via google-genai +google-genai==1.3.0 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +groq==0.18.0 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +h11==0.14.0 + # via httpcore +httpcore==1.0.7 + # via httpx +httpx==0.28.1 + # via + # agno + # anthropic + # google-genai + # groq + # ollama + # openai +idna==3.10 + # via + # anyio + # httpx + # requests +jinja2==3.1.5 + # via + # altair + # pydeck +jiter==0.8.2 + # via + # anthropic + # openai +jsonschema==4.23.0 + # via altair +jsonschema-specifications==2024.10.1 + # via jsonschema +markdown-it-py==3.0.0 + # via rich +markupsafe==3.0.2 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +narwhals==1.28.0 + # via altair +nest-asyncio==1.6.0 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +numpy==2.2.3 + # via + # pandas + # pydeck + # streamlit +ollama==0.4.7 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +openai==1.64.0 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +packaging==24.2 + # via + # altair + # build + # streamlit +pandas==2.2.3 + # via streamlit +pathlib==1.0.1 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +pillow==11.1.0 + # via + # -r cookbook/examples/apps/tic_tac_toe/requirements.in + # streamlit +pip==25.0.1 + # via pip-tools +pip-tools==7.4.1 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +protobuf==5.29.3 + # via streamlit +pyarrow==19.0.1 + # via streamlit +pyasn1==0.6.1 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.4.1 + # via google-auth +pydantic==2.10.6 + # via + # agno + # anthropic + # google-genai + # groq + # ollama + # openai + # pydantic-settings +pydantic-core==2.27.2 + # via pydantic +pydantic-settings==2.8.0 + # via agno +pydeck==0.9.1 + # via streamlit +pygments==2.19.1 + # via rich +pyproject-hooks==1.2.0 + # via + # build + # pip-tools +python-dateutil==2.9.0.post0 + # via pandas +python-dotenv==1.0.1 + # via + # -r cookbook/examples/apps/tic_tac_toe/requirements.in + # agno + # pydantic-settings +python-multipart==0.0.20 + # via agno +pytz==2025.1 + # via pandas +pyyaml==6.0.2 + # via agno +referencing==0.36.2 + # via + # jsonschema + # jsonschema-specifications +requests==2.32.3 + # via + # google-genai + # streamlit +rich==13.9.4 + # via + # -r cookbook/examples/apps/tic_tac_toe/requirements.in + # agno + # streamlit + # typer +rpds-py==0.23.1 + # via + # jsonschema + # referencing +rsa==4.9 + # via google-auth +setuptools==75.8.0 + # via pip-tools +shellingham==1.5.4 + # via typer +six==1.17.0 + # via python-dateutil +smmap==5.0.2 + # via gitdb +sniffio==1.3.1 + # via + # anthropic + # anyio + # groq + # openai +streamlit==1.42.2 + # via -r cookbook/examples/apps/tic_tac_toe/requirements.in +tenacity==9.0.0 + # via streamlit +toml==0.10.2 + # via streamlit +tomli==2.2.1 + # via agno +tornado==6.4.2 + # via streamlit +tqdm==4.67.1 + # via openai +typer==0.15.1 + # via agno +typing-extensions==4.12.2 + # via + # agno + # altair + # anthropic + # anyio + # google-genai + # groq + # openai + # pydantic + # pydantic-core + # referencing + # streamlit + # typer +tzdata==2025.1 + # via pandas +urllib3==2.3.0 + # via requests +websockets==14.2 + # via google-genai +wheel==0.45.1 + # via pip-tools diff --git a/ai_agent_tutorials/ai_tic_tac_toe_agent/utils.py b/ai_agent_tutorials/ai_tic_tac_toe_agent/utils.py new file mode 100644 index 0000000..ffc97dc --- /dev/null +++ b/ai_agent_tutorials/ai_tic_tac_toe_agent/utils.py @@ -0,0 +1,416 @@ +from typing import List, Optional, Tuple + +import streamlit as st + +# Define constants for players +X_PLAYER = "X" +O_PLAYER = "O" +EMPTY = " " + + +class TicTacToeBoard: + def __init__(self): + # Initialize empty 3x3 board + self.board = [[EMPTY for _ in range(3)] for _ in range(3)] + self.current_player = X_PLAYER + + def make_move(self, row: int, col: int) -> Tuple[bool, str]: + """ + Make a move on the board. + + Args: + row (int): Row index (0-2) + col (int): Column index (0-2) + + Returns: + Tuple[bool, str]: (Success status, Message with current board state or error) + """ + # Validate move coordinates + if not (0 <= row <= 2 and 0 <= col <= 2): + return ( + False, + "Invalid move: Position out of bounds. Please choose row and column between 0 and 2.", + ) + + # Check if position is already occupied + if self.board[row][col] != EMPTY: + return False, f"Invalid move: Position ({row}, {col}) is already occupied." + + # Make the move + self.board[row][col] = self.current_player + + # Get board state + board_state = self.get_board_state() + + # Switch player + self.current_player = O_PLAYER if self.current_player == X_PLAYER else X_PLAYER + + return True, f"Move successful!\n{board_state}" + + def get_board_state(self) -> str: + """ + Returns a string representation of the current board state. + """ + board_str = "\n-------------\n" + for row in self.board: + board_str += f"| {' | '.join(row)} |\n-------------\n" + return board_str + + def check_winner(self) -> Optional[str]: + """ + Check if there's a winner. + + Returns: + Optional[str]: The winning player (X or O) or None if no winner + """ + # Check rows + for row in self.board: + if row.count(row[0]) == 3 and row[0] != EMPTY: + return row[0] + + # Check columns + for col in range(3): + column = [self.board[row][col] for row in range(3)] + if column.count(column[0]) == 3 and column[0] != EMPTY: + return column[0] + + # Check diagonals + diagonal1 = [self.board[i][i] for i in range(3)] + if diagonal1.count(diagonal1[0]) == 3 and diagonal1[0] != EMPTY: + return diagonal1[0] + + diagonal2 = [self.board[i][2 - i] for i in range(3)] + if diagonal2.count(diagonal2[0]) == 3 and diagonal2[0] != EMPTY: + return diagonal2[0] + + return None + + def is_board_full(self) -> bool: + """ + Check if the board is full (draw condition). + """ + return all(cell != EMPTY for row in self.board for cell in row) + + def get_valid_moves(self) -> List[Tuple[int, int]]: + """ + Get a list of valid moves (empty positions). + + Returns: + List[Tuple[int, int]]: List of (row, col) tuples representing valid moves + """ + valid_moves = [] + for row in range(3): + for col in range(3): + if self.board[row][col] == EMPTY: + valid_moves.append((row, col)) + return valid_moves + + def get_game_state(self) -> Tuple[bool, str]: + """ + Get the current game state. + + Returns: + Tuple[bool, str]: (is_game_over, status_message) + """ + winner = self.check_winner() + if winner: + return True, f"Player {winner} wins!" + + if self.is_board_full(): + return True, "It's a draw!" + + return False, "Game in progress" + + +def display_board(board: TicTacToeBoard): + """Display the Tic Tac Toe board using Streamlit""" + board_html = '
' + + for i in range(3): + for j in range(3): + cell_value = board.board[i][j] + board_html += f'
{cell_value}
' + + board_html += "
" + st.markdown(board_html, unsafe_allow_html=True) + + +def show_agent_status(agent_name: str, status: str): + """Display the current agent status""" + st.markdown( + f"""
+ ๐Ÿค– {agent_name}: {status} +
""", + unsafe_allow_html=True, + ) + + +def create_mini_board_html( + board_state: list, highlight_pos: tuple = None, is_player1: bool = True +) -> str: + """Create HTML for a mini board with player-specific highlighting""" + html = '
' + for i in range(3): + for j in range(3): + highlight = ( + f"highlight player{1 if is_player1 else 2}" + if highlight_pos and (i, j) == highlight_pos + else "" + ) + html += f'
{board_state[i][j]}
' + html += "
" + return html + + +def display_move_history(): + """Display the move history with mini boards in two columns""" + st.markdown( + '

๐Ÿ“œ Game History

', + unsafe_allow_html=True, + ) + history_container = st.empty() + + if "move_history" in st.session_state and st.session_state.move_history: + # Split moves into player 1 and player 2 moves + p1_moves = [] + p2_moves = [] + current_board = [[" " for _ in range(3)] for _ in range(3)] + + # Process all moves first + for move in st.session_state.move_history: + row, col = map(int, move["move"].split(",")) + is_player1 = "Player 1" in move["player"] + symbol = "X" if is_player1 else "O" + current_board[row][col] = symbol + board_copy = [row[:] for row in current_board] + + move_html = f"""
+ {create_mini_board_html(board_copy, (row, col), is_player1)} +
+
Move #{move["number"]}
+
{move["player"]}
+
Position: ({row}, {col})
+
+
""" + + if is_player1: + p1_moves.append(move_html) + else: + p2_moves.append(move_html) + + max_moves = max(len(p1_moves), len(p2_moves)) + history_content = '
' + + # Left column (Player 1) + history_content += '
' + for i in range(max_moves): + entry_html = "" + # Player 1 move + if i < len(p1_moves): + entry_html += p1_moves[i] + history_content += entry_html + history_content += "
" + + # Right column (Player 2) + history_content += '
' + for i in range(max_moves): + entry_html = "" + # Player 2 move + if i < len(p2_moves): + entry_html += p2_moves[i] + history_content += entry_html + history_content += "
" + + history_content += "
" + + # Display the content + history_container.markdown(history_content, unsafe_allow_html=True) + else: + history_container.markdown( + """
+ No moves yet. Start the game to see the history! +
""", + unsafe_allow_html=True, + ) + + +CUSTOM_CSS = """ + +""" diff --git a/ai_agent_tutorials/ai_travel_agent/README.MD b/ai_agent_tutorials/ai_travel_agent/README.MD index 9117ad7..d9b492f 100644 --- a/ai_agent_tutorials/ai_travel_agent/README.MD +++ b/ai_agent_tutorials/ai_travel_agent/README.MD @@ -12,6 +12,7 @@ This Streamlit app is an AI-powered travel Agent that generates personalized tra ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/ai_travel_agent ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/ai_travel_agent/local_travel_agent.py b/ai_agent_tutorials/ai_travel_agent/local_travel_agent.py index fb0b856..e56a91c 100644 --- a/ai_agent_tutorials/ai_travel_agent/local_travel_agent.py +++ b/ai_agent_tutorials/ai_travel_agent/local_travel_agent.py @@ -1,21 +1,21 @@ from textwrap import dedent -from phi.assistant import Assistant -from phi.tools.serpapi_tools import SerpApiTools +from agno.agent import Agent +from agno.tools.serpapi import SerpApiTools import streamlit as st -from phi.llm.ollama import Ollama +from agno.models.ollama import Ollama # Set up the Streamlit app -st.title("AI Travel Planner using Llama-3 โœˆ๏ธ") +st.title("AI Travel Planner using Llama-3.2 โœˆ๏ธ") st.caption("Plan your next adventure with AI Travel Planner by researching and planning a personalized itinerary on autopilot using local Llama-3") # Get SerpAPI key from the user serp_api_key = st.text_input("Enter Serp API Key for Search functionality", type="password") if serp_api_key: - researcher = Assistant( + researcher = Agent( name="Researcher", role="Searches for travel destinations, activities, and accommodations based on user preferences", - llm=Ollama(model="llama3:instruct", max_tokens=1024), + model=Ollama(id="llama3.2", max_tokens=1024), description=dedent( """\ You are a world-class travel researcher. Given a travel destination and the number of days the user wants to travel for, @@ -32,10 +32,10 @@ if serp_api_key: tools=[SerpApiTools(api_key=serp_api_key)], add_datetime_to_instructions=True, ) - planner = Assistant( + planner = Agent( name="Planner", role="Generates a draft itinerary based on user preferences and research results", - llm=Ollama(model="llama3:instruct", max_tokens=1024), + model=Ollama(id="llama3.2", max_tokens=1024), description=dedent( """\ You are a senior travel planner. Given a travel destination, the number of days the user wants to travel for, and a list of research results, @@ -51,8 +51,6 @@ if serp_api_key: "Never make up facts or plagiarize. Always provide proper attribution.", ], add_datetime_to_instructions=True, - add_chat_history_to_prompt=True, - num_history_messages=3, ) # Input fields for the user's destination and the number of days they want to travel for @@ -63,4 +61,4 @@ if serp_api_key: with st.spinner("Processing..."): # Get the response from the assistant response = planner.run(f"{destination} for {num_days} days", stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/ai_agent_tutorials/ai_travel_agent/requirements.txt b/ai_agent_tutorials/ai_travel_agent/requirements.txt index 549573b..ffff278 100644 --- a/ai_agent_tutorials/ai_travel_agent/requirements.txt +++ b/ai_agent_tutorials/ai_travel_agent/requirements.txt @@ -1,4 +1,4 @@ streamlit -phidata +agno openai google-search-results \ No newline at end of file diff --git a/ai_agent_tutorials/ai_travel_agent/travel_agent.py b/ai_agent_tutorials/ai_travel_agent/travel_agent.py index 5a3fee6..eab9bbf 100644 --- a/ai_agent_tutorials/ai_travel_agent/travel_agent.py +++ b/ai_agent_tutorials/ai_travel_agent/travel_agent.py @@ -1,8 +1,8 @@ from textwrap import dedent -from phi.assistant import Assistant -from phi.tools.serpapi_tools import SerpApiTools +from agno.agent import Agent +from agno.tools.serpapi import SerpApiTools import streamlit as st -from phi.llm.openai import OpenAIChat +from agno.models.openai import OpenAIChat # Set up the Streamlit app st.title("AI Travel Planner โœˆ๏ธ") @@ -15,10 +15,10 @@ openai_api_key = st.text_input("Enter OpenAI API Key to access GPT-4o", type="pa serp_api_key = st.text_input("Enter Serp API Key for Search functionality", type="password") if openai_api_key and serp_api_key: - researcher = Assistant( + researcher = Agent( name="Researcher", role="Searches for travel destinations, activities, and accommodations based on user preferences", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), description=dedent( """\ You are a world-class travel researcher. Given a travel destination and the number of days the user wants to travel for, @@ -35,10 +35,10 @@ if openai_api_key and serp_api_key: tools=[SerpApiTools(api_key=serp_api_key)], add_datetime_to_instructions=True, ) - planner = Assistant( + planner = Agent( name="Planner", role="Generates a draft itinerary based on user preferences and research results", - llm=OpenAIChat(model="gpt-4o", api_key=openai_api_key), + model=OpenAIChat(id="gpt-4o", api_key=openai_api_key), description=dedent( """\ You are a senior travel planner. Given a travel destination, the number of days the user wants to travel for, and a list of research results, @@ -54,8 +54,6 @@ if openai_api_key and serp_api_key: "Never make up facts or plagiarize. Always provide proper attribution.", ], add_datetime_to_instructions=True, - add_chat_history_to_prompt=True, - num_history_messages=3, ) # Input fields for the user's destination and the number of days they want to travel for @@ -63,7 +61,21 @@ if openai_api_key and serp_api_key: num_days = st.number_input("How many days do you want to travel for?", min_value=1, max_value=30, value=7) if st.button("Generate Itinerary"): - with st.spinner("Processing..."): - # Get the response from the assistant - response = planner.run(f"{destination} for {num_days} days", stream=False) - st.write(response) \ No newline at end of file + with st.spinner("Researching your destination..."): + # First get research results + research_results = researcher.run(f"Research {destination} for a {num_days} day trip", stream=False) + + # Show research progress + st.write("โœ“ Research completed") + + with st.spinner("Creating your personalized itinerary..."): + # Pass research results to planner + prompt = f""" + Destination: {destination} + Duration: {num_days} days + Research Results: {research_results.content} + + Please create a detailed itinerary based on this research. + """ + response = planner.run(prompt, stream=False) + st.write(response.content) \ No newline at end of file diff --git a/ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py b/ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py new file mode 100644 index 0000000..1a6a3cd --- /dev/null +++ b/ai_agent_tutorials/gemini_multimodal_agent_demo/multimodal_ai_agent.py @@ -0,0 +1,33 @@ +from agno.agent import Agent +from agno.models.google import Gemini +from agno.tools.duckduckgo import DuckDuckGoTools +from google.generativeai import upload_file, get_file +import time + +# 1. Initialize the Multimodal Agent +agent = Agent(model=Gemini(id="gemini-2.0-flash-exp"), tools=[DuckDuckGoTools()], markdown=True) + +# 2. Image Input +image_url = "https://example.com/sample_image.jpg" + +# 3. Audio Input +audio_file = "sample_audio.mp3" + +# 4. Video Input +video_file = upload_file("sample_video.mp4") +while video_file.state.name == "PROCESSING": + time.sleep(2) + video_file = get_file(video_file.name) + +# 5. Multimodal Query +query = """ +Combine insights from the inputs: +1. **Image**: Describe the scene and its significance. +2. **Audio**: Extract key messages that relate to the visual. +3. **Video**: Look at the video input and provide insights that connect with the image and audio context. +4. **Web Search**: Find the latest updates or events linking all these topics. +Summarize the overall theme or story these inputs convey. +""" + +# 6. Multimodal Agent generates unified response +agent.print_response(query, images=[image_url], audio=audio_file, videos=[video_file], stream=True) \ No newline at end of file diff --git a/ai_agent_tutorials/legal_ai_agent/legal_agent.py b/ai_agent_tutorials/legal_ai_agent/legal_agent.py deleted file mode 100644 index 7bdb400..0000000 --- a/ai_agent_tutorials/legal_ai_agent/legal_agent.py +++ /dev/null @@ -1,36 +0,0 @@ -from phi.agent import Agent -from phi.model.openai import OpenAIChat -from phi.knowledge.pdf import PDFKnowledgeBase, PDFReader -from phi.vectordb.lancedb import LanceDb, SearchType -from phi.playground import Playground, serve_playground_app -from phi.tools.duckduckgo import DuckDuckGo - -# Set up configurations -DB_URI = "tmp/legal_docs_db" - -# Create a knowledge base for legal documents -knowledge_base = PDFKnowledgeBase( - path="tmp/legal_docs", - vector_db=LanceDb( - table_name="legal_documents", - uri=DB_URI, - search_type=SearchType.vector - ), - reader=PDFReader(chunk=True), - num_documents=5 -) - -# Create the agent -agent = Agent( - model=OpenAIChat(id="gpt-4"), - agent_id="legal-analysis-agent", - knowledge=knowledge_base, - tools=[DuckDuckGo()], - show_tool_calls=True, - markdown=True, -) - -app = Playground(agents=[agent]).get_app() - -if __name__ == "__main__": - serve_playground_app("legal_agent:app", reload=True) \ No newline at end of file diff --git a/ai_agent_tutorials/legal_ai_agent/requirements.txt b/ai_agent_tutorials/legal_ai_agent/requirements.txt deleted file mode 100644 index 53c213c..0000000 --- a/ai_agent_tutorials/legal_ai_agent/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -streamlit -phidata -openai -lancedb -tantivy -pypdf -duckduckgo-search \ No newline at end of file diff --git a/ai_agent_tutorials/local_news_agent_openai_swarm/README.md b/ai_agent_tutorials/local_news_agent_openai_swarm/README.md index 904f02c..3f460e5 100644 --- a/ai_agent_tutorials/local_news_agent_openai_swarm/README.md +++ b/ai_agent_tutorials/local_news_agent_openai_swarm/README.md @@ -18,7 +18,7 @@ This Streamlit application implements a sophisticated news processing pipeline u 1. Clone the GitHub repository ```bash git clone https://github.com/your-username/ai-news-processor.git -cd local_news_agent_openai_swarm +cd awesome-llm-apps/ai_agent_tutorials/local_news_agent_openai_swarm ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/multi_agent_researcher/README.md b/ai_agent_tutorials/multi_agent_researcher/README.md index 0b81011..d8276e4 100644 --- a/ai_agent_tutorials/multi_agent_researcher/README.md +++ b/ai_agent_tutorials/multi_agent_researcher/README.md @@ -12,6 +12,7 @@ This Streamlit app empowers you to research top stories and users on HackerNews ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/multi_agent_researcher ``` 2. Install the required dependencies: diff --git a/ai_agent_tutorials/multi_agent_researcher/requirements.txt b/ai_agent_tutorials/multi_agent_researcher/requirements.txt index a0e8efb..c54384d 100644 --- a/ai_agent_tutorials/multi_agent_researcher/requirements.txt +++ b/ai_agent_tutorials/multi_agent_researcher/requirements.txt @@ -1,3 +1,3 @@ streamlit -phidata +agno openai \ No newline at end of file diff --git a/ai_agent_tutorials/multi_agent_researcher/research_agent.py b/ai_agent_tutorials/multi_agent_researcher/research_agent.py index a5a6fcb..e1c06dd 100644 --- a/ai_agent_tutorials/multi_agent_researcher/research_agent.py +++ b/ai_agent_tutorials/multi_agent_researcher/research_agent.py @@ -1,8 +1,8 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.tools.hackernews import HackerNews -from phi.llm.openai import OpenAIChat +from agno.agent import Agent +from agno.tools.hackernews import HackerNewsTools +from agno.models.openai import OpenAIChat # Set up the Streamlit app st.title("Multi-Agent AI Researcher ๐Ÿ”๐Ÿค–") @@ -13,23 +13,23 @@ openai_api_key = st.text_input("OpenAI API Key", type="password") if openai_api_key: # Create instances of the Assistant - story_researcher = Assistant( + story_researcher = Agent( name="HackerNews Story Researcher", role="Researches hackernews stories and users.", - tools=[HackerNews()], + tools=[HackerNewsTools()], ) - user_researcher = Assistant( + user_researcher = Agent( name="HackerNews User Researcher", role="Reads articles from URLs.", - tools=[HackerNews()], + tools=[HackerNewsTools()], ) - hn_assistant = Assistant( + hn_assistant = Agent( name="Hackernews Team", team=[story_researcher, user_researcher], - llm=OpenAIChat( - model="gpt-4o", + model=OpenAIChat( + id="gpt-4o", max_tokens=1024, temperature=0.5, api_key=openai_api_key @@ -42,4 +42,4 @@ if openai_api_key: if query: # Get the response from the assistant response = hn_assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/ai_agent_tutorials/multi_agent_researcher/research_agent_llama3.py b/ai_agent_tutorials/multi_agent_researcher/research_agent_llama3.py index 96d55e0..1be2d52 100644 --- a/ai_agent_tutorials/multi_agent_researcher/research_agent_llama3.py +++ b/ai_agent_tutorials/multi_agent_researcher/research_agent_llama3.py @@ -1,32 +1,32 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.tools.hackernews import HackerNews -from phi.llm.ollama import Ollama +from agno.agent import Agent +from agno.tools.hackernews import HackerNews +from agno.models.ollama import Ollama # Set up the Streamlit app st.title("Multi-Agent AI Researcher using Llama-3 ๐Ÿ”๐Ÿค–") st.caption("This app allows you to research top stories and users on HackerNews and write blogs, reports and social posts.") # Create instances of the Assistant -story_researcher = Assistant( +story_researcher = Agent( name="HackerNews Story Researcher", role="Researches hackernews stories and users.", tools=[HackerNews()], - llm=Ollama(model="llama3:instruct", max_tokens=1024) + model=Ollama(id="llama3.2", max_tokens=1024) ) -user_researcher = Assistant( +user_researcher = Agent( name="HackerNews User Researcher", role="Reads articles from URLs.", tools=[HackerNews()], - llm=Ollama(model="llama3:instruct", max_tokens=1024) + model=Ollama(id="llama3.2", max_tokens=1024) ) -hn_assistant = Assistant( +hn_assistant = Agent( name="Hackernews Team", team=[story_researcher, user_researcher], - llm=Ollama(model="llama3:instruct", max_tokens=1024) + model=Ollama(id="llama3.2", max_tokens=1024) ) # Input field for the report query @@ -35,4 +35,4 @@ query = st.text_input("Enter your report query") if query: # Get the response from the assistant response = hn_assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/ai_agent_tutorials/multimodal_ai_agent/README.md b/ai_agent_tutorials/multimodal_ai_agent/README.md new file mode 100644 index 0000000..1cb9b4d --- /dev/null +++ b/ai_agent_tutorials/multimodal_ai_agent/README.md @@ -0,0 +1,39 @@ +## ๐Ÿงฌ Multimodal AI Agent + +A Streamlit application that combines video analysis and web search capabilities using Google's Gemini 2.0 model. This agent can analyze uploaded videos and answer questions by combining visual understanding with web-search. + +### Features + +- Video analysis using Gemini 2.0 Flash +- Web research integration via DuckDuckGo +- Support for multiple video formats (MP4, MOV, AVI) +- Real-time video processing +- Combined visual and textual analysis + +### How to get Started? + +1. Clone the GitHub repository + +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd ai_agent_tutorials/multimodal_ai_agent +``` +2. Install the required dependencies: + +```bash +pip install -r requirements.txt +``` +3. Get your Google Gemini API Key + +- Sign up for an [Google AI Studio account](https://aistudio.google.com/apikey) and obtain your API key. + +4. Set up your Gemini API Key as the environment variable + +```bash +GOOGLE_API_KEY=your_api_key_here +``` + +5. Run the Streamlit App +```bash +streamlit run multimodal_agent.py +``` diff --git a/ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py b/ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py new file mode 100644 index 0000000..428805a --- /dev/null +++ b/ai_agent_tutorials/multimodal_ai_agent/multimodal_reasoning_agent.py @@ -0,0 +1,62 @@ +import streamlit as st +from agno.agent import Agent +from agno.models.google import Gemini +import tempfile +import os + +def main(): + # Set up the reasoning agent + agent = Agent( + model=Gemini(id="gemini-2.0-flash-thinking-exp-1219"), + markdown=True + ) + + # Streamlit app title + st.title("Multimodal Reasoning AI Agent ๐Ÿง ") + + # Instruction + st.write( + "Upload an image and provide a reasoning-based task for the AI Agent. " + "The AI Agent will analyze the image and respond based on your input." + ) + + # File uploader for image + uploaded_file = st.file_uploader("Upload Image", type=["jpg", "jpeg", "png"]) + + if uploaded_file is not None: + try: + # Save uploaded file to temporary file + with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as tmp_file: + tmp_file.write(uploaded_file.getvalue()) + temp_path = tmp_file.name + + # Display the uploaded image + st.image(uploaded_file, caption="Uploaded Image", use_container_width=True) + + # Input for dynamic task + task_input = st.text_area( + "Enter your task/question for the AI Agent:" + ) + + # Button to process the image and task + if st.button("Analyze Image") and task_input: + with st.spinner("AI is thinking... ๐Ÿค–"): + try: + # Call the agent with the dynamic task and image path + response = agent.run(task_input, images=[temp_path]) + + # Display the response from the model + st.markdown("### AI Response:") + st.markdown(response.content) + except Exception as e: + st.error(f"An error occurred during analysis: {str(e)}") + finally: + # Clean up temp file + if os.path.exists(temp_path): + os.unlink(temp_path) + + except Exception as e: + st.error(f"An error occurred while processing the image: {str(e)}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ai_agent_tutorials/multimodal_ai_agent/mutimodal_agent.py b/ai_agent_tutorials/multimodal_ai_agent/mutimodal_agent.py new file mode 100644 index 0000000..23b77c1 --- /dev/null +++ b/ai_agent_tutorials/multimodal_ai_agent/mutimodal_agent.py @@ -0,0 +1,83 @@ +import streamlit as st +from agno.agent import Agent +from agno.models.google import Gemini +from agno.media import Video +import time +from pathlib import Path +import tempfile + +st.set_page_config( + page_title="Multimodal AI Agent", + page_icon="๐Ÿงฌ", + layout="wide" +) + +st.title("Multimodal AI Agent ๐Ÿงฌ") + +# Get Gemini API key from user +gemini_api_key = st.text_input("Enter your Gemini API Key", type="password") + +# Initialize single agent with both capabilities +@st.cache_resource +def initialize_agent(api_key): + return Agent( + name="Multimodal Analyst", + model=Gemini(id="gemini-2.0-flash", api_key=api_key), + markdown=True, + ) + +if gemini_api_key: + agent = initialize_agent(gemini_api_key) + + # File uploader + uploaded_file = st.file_uploader("Upload a video file", type=['mp4', 'mov', 'avi']) + + if uploaded_file: + with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file: + tmp_file.write(uploaded_file.read()) + video_path = tmp_file.name + + st.video(video_path) + + user_prompt = st.text_area( + "What would you like to know?", + placeholder="Ask any question related to the video - the AI Agent will analyze it and search the web if needed", + help="You can ask questions about the video content and get relevant information from the web" + ) + + if st.button("Analyze & Research"): + if not user_prompt: + st.warning("Please enter your question.") + else: + try: + with st.spinner("Processing video and researching..."): + video = Video(filepath=video_path) + + prompt = f""" + First analyze this video and then answer the following question using both + the video analysis and web research: {user_prompt} + + Provide a comprehensive response focusing on practical, actionable information. + """ + + result = agent.run(prompt, videos=[video]) + + st.subheader("Result") + st.markdown(result.content) + + except Exception as e: + st.error(f"An error occurred: {str(e)}") + finally: + Path(video_path).unlink(missing_ok=True) + else: + st.info("Please upload a video to begin analysis.") +else: + st.warning("Please enter your Gemini API key to continue.") + +st.markdown(""" + + """, unsafe_allow_html=True) \ No newline at end of file diff --git a/ai_agent_tutorials/multimodal_ai_agent/requirements.txt b/ai_agent_tutorials/multimodal_ai_agent/requirements.txt new file mode 100644 index 0000000..fad806f --- /dev/null +++ b/ai_agent_tutorials/multimodal_ai_agent/requirements.txt @@ -0,0 +1,3 @@ +agno +google-generativeai==0.8.3 +streamlit==1.40.2 \ No newline at end of file diff --git a/ai_agent_tutorials/multimodal_design_agent_team/README.md b/ai_agent_tutorials/multimodal_design_agent_team/README.md new file mode 100644 index 0000000..cb8e620 --- /dev/null +++ b/ai_agent_tutorials/multimodal_design_agent_team/README.md @@ -0,0 +1,69 @@ +# Multimodal AI Design Agent Team + +A Streamlit application that provides comprehensive design analysis using a team of specialized AI agents powered by Google's Gemini model. + +This application leverages multiple specialized AI agents to provide comprehensive analysis of UI/UX designs of your product and your competitors, combining visual understanding, user experience evaluation, and market research insights. + +## Features + +- **Specialized Legal AI Agent Team** + + - ๐ŸŽจ **Visual Design Agent**: Evaluates design elements, patterns, color schemes, typography, and visual hierarchy + - ๐Ÿ”„ **UX Analysis Agent**: Assesses user flows, interaction patterns, usability, and accessibility + - ๐Ÿ“Š **Market Analysis Agent**: Provides market insights, competitor analysis, and positioning recommendations + +- **Multiple Analysis Types**: Choose from Visual Design, UX, and Market Analysis +- **Comparative Analysis**: Upload competitor designs for comparative insights +- **Customizable Focus Areas**: Select specific aspects for detailed analysis +- **Context-Aware**: Provide additional context for more relevant insights +- **Real-time Processing**: Get instant analysis with progress indicators +- **Structured Output**: Receive well-organized, actionable insights + +## How to Run + +1. **Setup Environment** + ```bash + # Clone the repository + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/ai_agent_tutorials/multimodal_design_agent_team + + # Create and activate virtual environment (optional) + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + + # Install dependencies + pip install -r requirements.txt + ``` + +2. **Get API Key** + - Visit [Google AI Studio](https://aistudio.google.com/apikey) + - Generate an API key + +3. **Run the Application** + ```bash + streamlit run design_agent_team.py + ``` + +4. **Use the Application** + - Enter your Gemini API key in the sidebar + - Upload design files (supported formats: JPG, JPEG, PNG) + - Select analysis types and focus areas + - Add context if needed + - Click "Run Analysis" to get insights + + +## Technical Stack + +- **Frontend**: Streamlit +- **AI Model**: Google Gemini 2.0 +- **Image Processing**: Pillow +- **Market Research**: DuckDuckGo Search API +- **Framework**: Phidata for agent orchestration + +## Tips for Best Results + +- Upload clear, high-resolution images +- Include multiple views/screens for better context +- Add competitor designs for comparative analysis +- Provide specific context about your target audience + diff --git a/ai_agent_tutorials/multimodal_design_agent_team/design_agent_team.py b/ai_agent_tutorials/multimodal_design_agent_team/design_agent_team.py new file mode 100644 index 0000000..32145d5 --- /dev/null +++ b/ai_agent_tutorials/multimodal_design_agent_team/design_agent_team.py @@ -0,0 +1,269 @@ +from agno.agent import Agent +from agno.models.google import Gemini +from agno.tools.duckduckgo import DuckDuckGoTools +import streamlit as st +from PIL import Image +from typing import List, Optional + +def initialize_agents(api_key: str) -> tuple[Agent, Agent, Agent]: + try: + model = Gemini(id="gemini-2.0-flash-exp", api_key=api_key) + + vision_agent = Agent( + model=model, + instructions=[ + "You are a visual analysis expert that:", + "1. Identifies design elements, patterns, and visual hierarchy", + "2. Analyzes color schemes, typography, and layouts", + "3. Detects UI components and their relationships", + "4. Evaluates visual consistency and branding", + "Be specific and technical in your analysis" + ], + markdown=True + ) + + ux_agent = Agent( + model=model, + instructions=[ + "You are a UX analysis expert that:", + "1. Evaluates user flows and interaction patterns", + "2. Identifies usability issues and opportunities", + "3. Suggests UX improvements based on best practices", + "4. Analyzes accessibility and inclusive design", + "Focus on user-centric insights and practical improvements" + ], + markdown=True + ) + + market_agent = Agent( + model=model, + tools=[DuckDuckGoTools()], + instructions=[ + "You are a market research expert that:", + "1. Identifies market trends and competitor patterns", + "2. Analyzes similar products and features", + "3. Suggests market positioning and opportunities", + "4. Provides industry-specific insights", + "Focus on actionable market intelligence" + ], + markdown=True + ) + + return vision_agent, ux_agent, market_agent + except Exception as e: + st.error(f"Error initializing agents: {str(e)}") + return None, None, None + +# Sidebar for API key input +with st.sidebar: + st.header("๐Ÿ”‘ API Configuration") + + if "api_key_input" not in st.session_state: + st.session_state.api_key_input = "" + + api_key = st.text_input( + "Enter your Gemini API Key", + value=st.session_state.api_key_input, + type="password", + help="Get your API key from Google AI Studio", + key="api_key_widget" + ) + + if api_key != st.session_state.api_key_input: + st.session_state.api_key_input = api_key + + if api_key: + st.success("API Key provided! โœ…") + else: + st.warning("Please enter your API key to proceed") + st.markdown(""" + To get your API key: + 1. Go to [Google AI Studio](https://makersuite.google.com/app/apikey) + """) + +st.title("Multimodal AI Design Agent Team") + +if st.session_state.api_key_input: + vision_agent, ux_agent, market_agent = initialize_agents(st.session_state.api_key_input) + + if all([vision_agent, ux_agent, market_agent]): + # File Upload Section + st.header("๐Ÿ“ค Upload Content") + col1, space, col2 = st.columns([1, 0.1, 1]) + + with col1: + design_files = st.file_uploader( + "Upload UI/UX Designs", + type=["jpg", "jpeg", "png"], + accept_multiple_files=True, + key="designs" + ) + + if design_files: + for file in design_files: + image = Image.open(file) + st.image(image, caption=file.name, use_container_width=True) + + with col2: + competitor_files = st.file_uploader( + "Upload Competitor Designs (Optional)", + type=["jpg", "jpeg", "png"], + accept_multiple_files=True, + key="competitors" + ) + + if competitor_files: + for file in competitor_files: + image = Image.open(file) + st.image(image, caption=f"Competitor: {file.name}", use_container_width=True) + + # Analysis Configuration + st.header("๐ŸŽฏ Analysis Configuration") + + analysis_types = st.multiselect( + "Select Analysis Types", + ["Visual Design", "User Experience", "Market Analysis"], + default=["Visual Design"] + ) + + specific_elements = st.multiselect( + "Focus Areas", + ["Color Scheme", "Typography", "Layout", "Navigation", + "Interactions", "Accessibility", "Branding", "Market Fit"] + ) + + context = st.text_area( + "Additional Context", + placeholder="Describe your product, target audience, or specific concerns..." + ) + + # Analysis Process + if st.button("๐Ÿš€ Run Analysis", type="primary"): + if design_files: + try: + st.header("๐Ÿ“Š Analysis Results") + + # Process images once + def process_images(files): + processed_images = [] + for file in files: + try: + # Create a temporary file path for the image + import tempfile + import os + + temp_dir = tempfile.gettempdir() + temp_path = os.path.join(temp_dir, f"temp_{file.name}") + + # Save the uploaded file to temp location + with open(temp_path, "wb") as f: + f.write(file.getvalue()) + + # Add the path to processed images + processed_images.append(temp_path) + + except Exception as e: + st.error(f"Error processing image {file.name}: {str(e)}") + continue + return processed_images + + design_images = process_images(design_files) + competitor_images = process_images(competitor_files) if competitor_files else [] + all_images = design_images + competitor_images + + # Visual Design Analysis + if "Visual Design" in analysis_types and design_files: + with st.spinner("๐ŸŽจ Analyzing visual design..."): + if all_images: + vision_prompt = f""" + Analyze these designs focusing on: {', '.join(specific_elements)} + Additional context: {context} + Provide specific insights about visual design elements. + + Please format your response with clear headers and bullet points. + Focus on concrete observations and actionable insights. + """ + + response = vision_agent.run( + message=vision_prompt, + images=all_images + ) + + st.subheader("๐ŸŽจ Visual Design Analysis") + st.markdown(response.content) + + # UX Analysis + if "User Experience" in analysis_types: + with st.spinner("๐Ÿ”„ Analyzing user experience..."): + if all_images: + ux_prompt = f""" + Evaluate the user experience considering: {', '.join(specific_elements)} + Additional context: {context} + Focus on user flows, interactions, and accessibility. + + Please format your response with clear headers and bullet points. + Focus on concrete observations and actionable improvements. + """ + + response = ux_agent.run( + message=ux_prompt, + images=all_images + ) + + st.subheader("๐Ÿ”„ UX Analysis") + st.markdown(response.content) + + # Market Analysis + if "Market Analysis" in analysis_types: + with st.spinner("๐Ÿ“Š Conducting market analysis..."): + market_prompt = f""" + Analyze market positioning and trends based on these designs. + Context: {context} + Compare with competitor designs if provided. + Suggest market opportunities and positioning. + + Please format your response with clear headers and bullet points. + Focus on concrete market insights and actionable recommendations. + """ + + response = market_agent.run( + message=market_prompt, + images=all_images + ) + + st.subheader("๐Ÿ“Š Market Analysis") + st.markdown(response.content) + + # Combined Insights + if len(analysis_types) > 1: + st.subheader("๐ŸŽฏ Key Takeaways") + st.info(""" + Above you'll find detailed analysis from multiple specialized AI agents, each focusing on their area of expertise: + - Visual Design Agent: Analyzes design elements and patterns + - UX Agent: Evaluates user experience and interactions + - Market Research Agent: Provides market context and opportunities + """) + + except Exception as e: + st.error(f"An error occurred during analysis: {str(e)}") + st.error("Please check your API key and try again.") + else: + st.warning("Please upload at least one design to analyze.") + else: + st.info("๐Ÿ‘ˆ Please enter your API key in the sidebar to get started") +else: + st.info("๐Ÿ‘ˆ Please enter your API key in the sidebar to get started") + +# Footer with usage tips +st.markdown("---") +st.markdown(""" +
+

Tips for Best Results

+

+ โ€ข Upload clear, high-resolution images
+ โ€ข Include multiple views/screens for better context
+ โ€ข Add competitor designs for comparative analysis
+ โ€ข Provide specific context about your target audience +

+
+""", unsafe_allow_html=True) \ No newline at end of file diff --git a/ai_agent_tutorials/multimodal_design_agent_team/requirements.txt b/ai_agent_tutorials/multimodal_design_agent_team/requirements.txt new file mode 100644 index 0000000..6cb878b --- /dev/null +++ b/ai_agent_tutorials/multimodal_design_agent_team/requirements.txt @@ -0,0 +1,6 @@ +google-generativeai==0.8.3 +streamlit==1.41.1 +agno +Pillow==11.0.0 +duckduckgo-search==6.3.7 + diff --git a/ai_agent_tutorials/opeani_research_agent/README.md b/ai_agent_tutorials/opeani_research_agent/README.md new file mode 100644 index 0000000..d9a5747 --- /dev/null +++ b/ai_agent_tutorials/opeani_research_agent/README.md @@ -0,0 +1,50 @@ +# OpenAI Researcher Agent +A multi-agent research application built with OpenAI's Agents SDK and Streamlit. This application enables users to conduct comprehensive research on any topic by leveraging multiple specialized AI agents. + +### Features + +- Multi-Agent Architecture: + - Triage Agent: Plans the research approach and coordinates the workflow + - Research Agent: Searches the web and gathers relevant information + - Editor Agent: Compiles collected facts into a comprehensive report + +- Automatic Fact Collection: Captures important facts from research with source attribution +- Structured Report Generation: Creates well-organized reports with titles, outlines, and source citations +- Interactive UI: Built with Streamlit for easy research topic input and results viewing +- Tracing and Monitoring: Integrated tracing for the entire research workflow + +### How to get Started? + +1. Clone the GitHub repository +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/openai_researcher_agent +``` + +2. Install the required dependencies: + +```bash +cd awesome-llm-apps/ai_agent_tutorials/openai_researcher_agent +pip install -r requirements.txt +``` + +3. Get your OpenAI API Key + +- - Sign up for an [OpenAI account](https://platform.openai.com/) and obtain your API key. +- Set your OPENAI_API_KEY environment variable. +```bash +export OPENAI_API_KEY='your-api-key-here' +``` + +4. Run the team of AI Agents +```bash +streamlit run openai_researcher_agent.py +``` + +Then open your browser and navigate to the URL shown in the terminal (typically http://localhost:8501). + +### Research Process: +- Enter a research topic in the sidebar or select one of the provided examples +- Click "Start Research" to begin the process +- View the research process in real-time on the "Research Process" tab +- Once complete, switch to the "Report" tab to view and download the generated report \ No newline at end of file diff --git a/ai_agent_tutorials/opeani_research_agent/requirements.txt b/ai_agent_tutorials/opeani_research_agent/requirements.txt new file mode 100644 index 0000000..883ae6d --- /dev/null +++ b/ai_agent_tutorials/opeani_research_agent/requirements.txt @@ -0,0 +1,7 @@ +openai-agents +openai +streamlit +uuid +pydantic +python-dotenv +asyncio \ No newline at end of file diff --git a/ai_agent_tutorials/opeani_research_agent/research_agent.py b/ai_agent_tutorials/opeani_research_agent/research_agent.py new file mode 100644 index 0000000..fdf5e9d --- /dev/null +++ b/ai_agent_tutorials/opeani_research_agent/research_agent.py @@ -0,0 +1,331 @@ +import os +import uuid +import asyncio +import streamlit as st +from datetime import datetime +from dotenv import load_dotenv + +from agents import ( + Agent, + Runner, + WebSearchTool, + function_tool, + handoff, + trace, +) + +from pydantic import BaseModel + +# Load environment variables +load_dotenv() + +# Set up page configuration +st.set_page_config( + page_title="OpenAI Researcher Agent", + page_icon="๐Ÿ“ฐ", + layout="wide", + initial_sidebar_state="expanded" +) + +# Make sure API key is set +if not os.environ.get("OPENAI_API_KEY"): + st.error("Please set your OPENAI_API_KEY environment variable") + st.stop() + +# App title and description +st.title("๐Ÿ“ฐ OpenAI Researcher Agent") +st.subheader("Powered by OpenAI Agents SDK") +st.markdown(""" +This app demonstrates the power of OpenAI's Agents SDK by creating a multi-agent system +that researches news topics and generates comprehensive research reports. +""") + +# Define data models +class ResearchPlan(BaseModel): + topic: str + search_queries: list[str] + focus_areas: list[str] + +class ResearchReport(BaseModel): + title: str + outline: list[str] + report: str + sources: list[str] + word_count: int + +# Custom tool for saving facts found during research +@function_tool +def save_important_fact(fact: str, source: str = None) -> str: + """Save an important fact discovered during research. + + Args: + fact: The important fact to save + source: Optional source of the fact + + Returns: + Confirmation message + """ + if "collected_facts" not in st.session_state: + st.session_state.collected_facts = [] + + st.session_state.collected_facts.append({ + "fact": fact, + "source": source or "Not specified", + "timestamp": datetime.now().strftime("%H:%M:%S") + }) + + return f"Fact saved: {fact}" + +# Define the agents +research_agent = Agent( + name="Research Agent", + instructions="You are a research assistant. Given a search term, you search the web for that term and" + "produce a concise summary of the results. The summary must 2-3 paragraphs and less than 300" + "words. Capture the main points. Write succintly, no need to have complete sentences or good" + "grammar. This will be consumed by someone synthesizing a report, so its vital you capture the" + "essence and ignore any fluff. Do not include any additional commentary other than the summary" + "itself.", + model="gpt-4o-mini", + tools=[ + WebSearchTool(), + save_important_fact + ], +) + +editor_agent = Agent( + name="Editor Agent", + handoff_description="A senior researcher who writes comprehensive research reports", + instructions="You are a senior researcher tasked with writing a cohesive report for a research query. " + "You will be provided with the original query, and some initial research done by a research " + "assistant.\n" + "You should first come up with an outline for the report that describes the structure and " + "flow of the report. Then, generate the report and return that as your final output.\n" + "The final output should be in markdown format, and it should be lengthy and detailed. Aim " + "for 5-10 pages of content, at least 1000 words.", + model="gpt-4o-mini", + output_type=ResearchReport, +) + +triage_agent = Agent( + name="Triage Agent", + instructions="""You are the coordinator of this research operation. Your job is to: + 1. Understand the user's research topic + 2. Create a research plan with the following elements: + - topic: A clear statement of the research topic + - search_queries: A list of 3-5 specific search queries that will help gather information + - focus_areas: A list of 3-5 key aspects of the topic to investigate + 3. Hand off to the Research Agent to collect information + 4. After research is complete, hand off to the Editor Agent who will write a comprehensive report + + Make sure to return your plan in the expected structured format with topic, search_queries, and focus_areas. + """, + handoffs=[ + handoff(research_agent), + handoff(editor_agent) + ], + model="gpt-4o-mini", + output_type=ResearchPlan, +) + +# Create sidebar for input and controls +with st.sidebar: + st.header("Research Topic") + user_topic = st.text_input( + "Enter a topic to research:", + ) + + start_button = st.button("Start Research", type="primary", disabled=not user_topic) + + st.divider() + st.subheader("Example Topics") + example_topics = [ + "What are the best cruise lines in USA for first-time travelers who have never been on a cruise?", + "What are the best affordable espresso machines for someone upgrading from a French press?", + "What are the best off-the-beaten-path destinations in India for a first-time solo traveler?" + ] + + for topic in example_topics: + if st.button(topic): + user_topic = topic + start_button = True + +# Main content area with two tabs +tab1, tab2 = st.tabs(["Research Process", "Report"]) + +# Initialize session state for storing results +if "conversation_id" not in st.session_state: + st.session_state.conversation_id = str(uuid.uuid4().hex[:16]) +if "collected_facts" not in st.session_state: + st.session_state.collected_facts = [] +if "research_done" not in st.session_state: + st.session_state.research_done = False +if "report_result" not in st.session_state: + st.session_state.report_result = None + +# Main research function +async def run_research(topic): + # Reset state for new research + st.session_state.collected_facts = [] + st.session_state.research_done = False + st.session_state.report_result = None + + with tab1: + message_container = st.container() + + # Create error handling container + error_container = st.empty() + + # Create a trace for the entire workflow + with trace("News Research", group_id=st.session_state.conversation_id): + # Start with the triage agent + with message_container: + st.write("๐Ÿ” **Triage Agent**: Planning research approach...") + + triage_result = await Runner.run( + triage_agent, + f"Research this topic thoroughly: {topic}. This research will be used to create a comprehensive research report." + ) + + # Check if the result is a ResearchPlan object or a string + if hasattr(triage_result.final_output, 'topic'): + research_plan = triage_result.final_output + plan_display = { + "topic": research_plan.topic, + "search_queries": research_plan.search_queries, + "focus_areas": research_plan.focus_areas + } + else: + # Fallback if we don't get the expected output type + research_plan = { + "topic": topic, + "search_queries": ["Researching " + topic], + "focus_areas": ["General information about " + topic] + } + plan_display = research_plan + + with message_container: + st.write("๐Ÿ“‹ **Research Plan**:") + st.json(plan_display) + + # Display facts as they're collected + fact_placeholder = message_container.empty() + + # Check for new facts periodically + previous_fact_count = 0 + for i in range(15): # Check more times to allow for more comprehensive research + current_facts = len(st.session_state.collected_facts) + if current_facts > previous_fact_count: + with fact_placeholder.container(): + st.write("๐Ÿ“š **Collected Facts**:") + for fact in st.session_state.collected_facts: + st.info(f"**Fact**: {fact['fact']}\n\n**Source**: {fact['source']}") + previous_fact_count = current_facts + await asyncio.sleep(1) + + # Editor Agent phase + with message_container: + st.write("๐Ÿ“ **Editor Agent**: Creating comprehensive research report...") + + try: + report_result = await Runner.run( + editor_agent, + triage_result.to_input_list() + ) + + st.session_state.report_result = report_result.final_output + + with message_container: + st.write("โœ… **Research Complete! Report Generated.**") + + # Preview a snippet of the report + if hasattr(report_result.final_output, 'report'): + report_preview = report_result.final_output.report[:300] + "..." + else: + report_preview = str(report_result.final_output)[:300] + "..." + + st.write("๐Ÿ“„ **Report Preview**:") + st.markdown(report_preview) + st.write("*See the Report tab for the full document.*") + + except Exception as e: + st.error(f"Error generating report: {str(e)}") + # Fallback to display raw agent response + if hasattr(triage_result, 'new_items'): + messages = [item for item in triage_result.new_items if hasattr(item, 'content')] + if messages: + raw_content = "\n\n".join([str(m.content) for m in messages if m.content]) + st.session_state.report_result = raw_content + + with message_container: + st.write("โš ๏ธ **Research completed but there was an issue generating the structured report.**") + st.write("Raw research results are available in the Report tab.") + + st.session_state.research_done = True + +# Run the research when the button is clicked +if start_button: + with st.spinner(f"Researching: {user_topic}"): + try: + asyncio.run(run_research(user_topic)) + except Exception as e: + st.error(f"An error occurred during research: {str(e)}") + # Set a basic report result so the user gets something + st.session_state.report_result = f"# Research on {user_topic}\n\nUnfortunately, an error occurred during the research process. Please try again later or with a different topic.\n\nError details: {str(e)}" + st.session_state.research_done = True + +# Display results in the Report tab +with tab2: + if st.session_state.research_done and st.session_state.report_result: + report = st.session_state.report_result + + # Handle different possible types of report results + if hasattr(report, 'title'): + # We have a properly structured ResearchReport object + title = report.title + + # Display outline if available + if hasattr(report, 'outline') and report.outline: + with st.expander("Report Outline", expanded=True): + for i, section in enumerate(report.outline): + st.markdown(f"{i+1}. {section}") + + # Display word count if available + if hasattr(report, 'word_count'): + st.info(f"Word Count: {report.word_count}") + + # Display the full report in markdown + if hasattr(report, 'report'): + report_content = report.report + st.markdown(report_content) + else: + report_content = str(report) + st.markdown(report_content) + + # Display sources if available + if hasattr(report, 'sources') and report.sources: + with st.expander("Sources"): + for i, source in enumerate(report.sources): + st.markdown(f"{i+1}. {source}") + + # Add download button for the report + st.download_button( + label="Download Report", + data=report_content, + file_name=f"{title.replace(' ', '_')}.md", + mime="text/markdown" + ) + else: + # Handle string or other type of response + report_content = str(report) + title = user_topic.title() + + st.title(f"{title}") + st.markdown(report_content) + + # Add download button for the report + st.download_button( + label="Download Report", + data=report_content, + file_name=f"{title.replace(' ', '_')}.md", + mime="text/markdown" + ) \ No newline at end of file diff --git a/ai_agent_tutorials/xai_finance_agent/README.md b/ai_agent_tutorials/xai_finance_agent/README.md new file mode 100644 index 0000000..3423e9d --- /dev/null +++ b/ai_agent_tutorials/xai_finance_agent/README.md @@ -0,0 +1,40 @@ +## ๐Ÿ“Š AI Finance Agent with xAI Grok +This application creates a financial analysis agent powered by xAI's Grok model, combining real-time stock data with web search capabilities. It provides structured financial insights through an interactive playground interface. + +### Features + +- Powered by xAI's Grok-beta model +- Real-time stock data analysis via YFinance +- Web search capabilities through DuckDuckGo +- Formatted output with tables for financial data +- Interactive playground interface + +### How to get Started? + +1. Clone the GitHub repository +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/ai_agent_tutorials/xai_finance_agent +``` + +2. Install the required dependencies: + +```bash +cd awesome-llm-apps/ai_agent_tutorials/xai_finance_agent +pip install -r requirements.txt +``` + +3. Get your OpenAI API Key + +- Sign up for an [xAI API account](https://console.x.ai/) +- Set your XAI_API_KEY environment variable. +```bash +export XAI_API_KEY='your-api-key-here' +``` + +4. Run the team of AI Agents +```bash +python xai_finance_agent.py +``` + +5. Open your web browser and navigate to the URL provided in the console output to interact with the AI financial agent through the playground interface. diff --git a/ai_agent_tutorials/xai_finance_agent/requirements.txt b/ai_agent_tutorials/xai_finance_agent/requirements.txt new file mode 100644 index 0000000..d0527a4 --- /dev/null +++ b/ai_agent_tutorials/xai_finance_agent/requirements.txt @@ -0,0 +1,5 @@ +agno +duckduckgo-search +yfinance +fastapi[standard] +openai \ No newline at end of file diff --git a/ai_agent_tutorials/xai_finance_agent/xai_finance_agent.py b/ai_agent_tutorials/xai_finance_agent/xai_finance_agent.py new file mode 100644 index 0000000..ccde613 --- /dev/null +++ b/ai_agent_tutorials/xai_finance_agent/xai_finance_agent.py @@ -0,0 +1,22 @@ +# import necessary python libraries +from agno.agent import Agent +from agno.models.xai import xAI +from agno.tools.yfinance import YFinanceTools +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.playground import Playground, serve_playground_app + +# create the AI finance agent +agent = Agent( + name="xAI Finance Agent", + model = xAI(id="grok-beta"), + tools=[DuckDuckGoTools(), YFinanceTools(stock_price=True, analyst_recommendations=True, stock_fundamentals=True)], + instructions = ["Always use tables to display financial/numerical data. For text data use bullet points and small paragrpahs."], + show_tool_calls = True, + markdown = True, + ) + +# UI for finance agent +app = Playground(agents=[agent]).get_app() + +if __name__ == "__main__": + serve_playground_app("xai_finance_agent:app", reload=True) \ No newline at end of file diff --git a/chat_with_X_tutorials/chat_with_github/README.md b/chat_with_X_tutorials/chat_with_github/README.md index 423c6fb..a0c8e0b 100644 --- a/chat_with_X_tutorials/chat_with_github/README.md +++ b/chat_with_X_tutorials/chat_with_github/README.md @@ -14,6 +14,7 @@ LLM app with RAG to chat with GitHub Repo in just 30 lines of Python Code. The a ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/chat_with_X_tutorials/chat_with_github ``` 2. Install the required dependencies: diff --git a/chat_with_X_tutorials/chat_with_gmail/README.md b/chat_with_X_tutorials/chat_with_gmail/README.md index efb9da2..6666d80 100644 --- a/chat_with_X_tutorials/chat_with_gmail/README.md +++ b/chat_with_X_tutorials/chat_with_gmail/README.md @@ -14,6 +14,7 @@ LLM app with RAG to chat with Gmail in just 30 lines of Python Code. The app use ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/chat_with_X_tutorials/chat_with_gmail ``` 2. Install the required dependencies diff --git a/chat_with_X_tutorials/chat_with_pdf/README.md b/chat_with_X_tutorials/chat_with_pdf/README.md index 4994d4e..30a11e5 100644 --- a/chat_with_X_tutorials/chat_with_pdf/README.md +++ b/chat_with_X_tutorials/chat_with_pdf/README.md @@ -14,6 +14,7 @@ LLM app with RAG to chat with PDF in just 30 lines of Python Code. The app uses ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/chat_with_X_tutorials/chat_with_pdf ``` 2. Install the required dependencies diff --git a/chat_with_X_tutorials/chat_with_pdf/requirements.txt b/chat_with_X_tutorials/chat_with_pdf/requirements.txt index ca9e4b6..47fe46a 100644 --- a/chat_with_X_tutorials/chat_with_pdf/requirements.txt +++ b/chat_with_X_tutorials/chat_with_pdf/requirements.txt @@ -1,2 +1,3 @@ streamlit -embedchain \ No newline at end of file +embedchain +streamlit-chat \ No newline at end of file diff --git a/chat_with_X_tutorials/chat_with_research_papers/README.md b/chat_with_X_tutorials/chat_with_research_papers/README.md index b5fa6ce..93d3f18 100644 --- a/chat_with_X_tutorials/chat_with_research_papers/README.md +++ b/chat_with_X_tutorials/chat_with_research_papers/README.md @@ -12,6 +12,7 @@ This Streamlit app enables you to engage in interactive conversations with arXiv ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/chat_with_X_tutorials/chat_with_research_papers ``` 2. Install the required dependencies: diff --git a/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv.py b/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv.py index a06e956..0467829 100644 --- a/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv.py +++ b/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv.py @@ -1,8 +1,8 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.llm.openai import OpenAIChat -from phi.tools.arxiv_toolkit import ArxivToolkit +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from agno.tools.arxiv import ArxivTools # Set up the Streamlit app st.title("Chat with Research Papers ๐Ÿ”Ž๐Ÿค–") @@ -14,12 +14,12 @@ openai_access_token = st.text_input("OpenAI API Key", type="password") # If OpenAI API key is provided, create an instance of Assistant if openai_access_token: # Create an instance of the Assistant - assistant = Assistant( - llm=OpenAIChat( - model="gpt-4o", + assistant = Agent( + model=OpenAIChat( + id="gpt-4o", max_tokens=1024, temperature=0.9, - api_key=openai_access_token) , tools=[ArxivToolkit()] + api_key=openai_access_token) , tools=[ArxivTools()] ) # Get the search query from the user @@ -28,4 +28,4 @@ if openai_access_token: if query: # Search the web using the AI Assistant response = assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv_llama3.py b/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv_llama3.py index 5e1181d..8de78b8 100644 --- a/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv_llama3.py +++ b/chat_with_X_tutorials/chat_with_research_papers/chat_arxiv_llama3.py @@ -1,17 +1,17 @@ # Import the required libraries import streamlit as st -from phi.assistant import Assistant -from phi.llm.ollama import Ollama -from phi.tools.arxiv_toolkit import ArxivToolkit +from agno.agent import Agent +from agno.models.ollama import Ollama +from agno.tools.arxiv import ArxivTools # Set up the Streamlit app st.title("Chat with Research Papers ๐Ÿ”Ž๐Ÿค–") st.caption("This app allows you to chat with arXiv research papers using Llama-3 running locally.") # Create an instance of the Assistant -assistant = Assistant( -llm=Ollama( - model="llama3:instruct") , tools=[ArxivToolkit()], show_tool_calls=True +assistant = Agent( +model=Ollama( + id="llama3.1:8b") , tools=[ArxivTools()], show_tool_calls=True ) # Get the search query from the user @@ -20,4 +20,4 @@ query= st.text_input("Enter the Search Query", type="default") if query: # Search the web using the AI Assistant response = assistant.run(query, stream=False) - st.write(response) \ No newline at end of file + st.write(response.content) \ No newline at end of file diff --git a/chat_with_X_tutorials/chat_with_research_papers/requirements.txt b/chat_with_X_tutorials/chat_with_research_papers/requirements.txt index 88f1d68..bcb7b91 100644 --- a/chat_with_X_tutorials/chat_with_research_papers/requirements.txt +++ b/chat_with_X_tutorials/chat_with_research_papers/requirements.txt @@ -1,5 +1,5 @@ streamlit -phidata +agno arxiv openai pypdf \ No newline at end of file diff --git a/chat_with_X_tutorials/chat_with_substack/README.md b/chat_with_X_tutorials/chat_with_substack/README.md index 140444c..7f3d5f1 100644 --- a/chat_with_X_tutorials/chat_with_substack/README.md +++ b/chat_with_X_tutorials/chat_with_substack/README.md @@ -12,6 +12,7 @@ Streamlit app that allows you to chat with a Substack newsletter using OpenAI's ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/chat_with_X_tutorials/chat_with_substack ``` 2. Install the required dependencies: diff --git a/chat_with_X_tutorials/chat_with_youtube_videos/README.md b/chat_with_X_tutorials/chat_with_youtube_videos/README.md index 69e8d8e..848bb8d 100644 --- a/chat_with_X_tutorials/chat_with_youtube_videos/README.md +++ b/chat_with_X_tutorials/chat_with_youtube_videos/README.md @@ -1,6 +1,6 @@ ## ๐Ÿ“ฝ๏ธ Chat with YouTube Videos -LLM app with RAG to chat with YouTube Videos in just 30 lines of Python Code. The app uses Retrieval Augmented Generation (RAG) to provide accurate answers to questions based on the content of the uploaded video. +LLM app with RAG to chat with YouTube Videos with OpenAI's gpt-4o, mem0/embedchain as memory and the youtube-transcript-api. The app uses Retrieval Augmented Generation (RAG) to provide accurate answers to questions based on the content of the uploaded video. ### Features @@ -14,6 +14,7 @@ LLM app with RAG to chat with YouTube Videos in just 30 lines of Python Code. Th ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/chat_with_X_tutorials/chat_with_youtube_videos ``` 2. Install the required dependencies: diff --git a/chat_with_X_tutorials/chat_with_youtube_videos/chat_youtube.py b/chat_with_X_tutorials/chat_with_youtube_videos/chat_youtube.py index 7246e46..297176a 100644 --- a/chat_with_X_tutorials/chat_with_youtube_videos/chat_youtube.py +++ b/chat_with_X_tutorials/chat_with_youtube_videos/chat_youtube.py @@ -1,18 +1,36 @@ -# Import the required libraries import tempfile import streamlit as st from embedchain import App +from youtube_transcript_api import YouTubeTranscriptApi +from typing import Tuple -# Define the embedchain_bot function -def embedchain_bot(db_path, api_key): +def embedchain_bot(db_path: str, api_key: str) -> App: return App.from_config( config={ - "llm": {"provider": "openai", "config": {"model": "gpt-4o", "temperature": 0.5, "api_key": api_key}}, + "llm": {"provider": "openai", "config": {"model": "gpt-4", "temperature": 0.5, "api_key": api_key}}, "vectordb": {"provider": "chroma", "config": {"dir": db_path}}, "embedder": {"provider": "openai", "config": {"api_key": api_key}}, } ) +def extract_video_id(video_url: str) -> str: + if "youtube.com/watch?v=" in video_url: + return video_url.split("v=")[-1].split("&")[0] + elif "youtube.com/shorts/" in video_url: + return video_url.split("/shorts/")[-1].split("?")[0] + else: + raise ValueError("Invalid YouTube URL") + +def fetch_video_data(video_url: str) -> Tuple[str, str]: + try: + video_id = extract_video_id(video_url) + transcript = YouTubeTranscriptApi.get_transcript(video_id) + transcript_text = " ".join([entry["text"] for entry in transcript]) + return "Unknown", transcript_text # Title is set to "Unknown" since we're not fetching it + except Exception as e: + st.error(f"Error fetching transcript: {e}") + return "Unknown", "No transcript available for this video." + # Create Streamlit app st.title("Chat with YouTube Video ๐Ÿ“บ") st.caption("This app allows you to chat with a YouTube video using OpenAI API") @@ -30,13 +48,21 @@ if openai_access_token: video_url = st.text_input("Enter YouTube Video URL", type="default") # Add the video to the knowledge base if video_url: - app.add(video_url, data_type="youtube_video") - st.success(f"Added {video_url} to knowledge base!") + try: + title, transcript = fetch_video_data(video_url) + if transcript != "No transcript available for this video.": + app.add(transcript, data_type="text", metadata={"title": title, "url": video_url}) + st.success(f"Added video '{title}' to knowledge base!") + else: + st.warning(f"No transcript available for video '{title}'. Cannot add to knowledge base.") + except Exception as e: + st.error(f"Error adding video: {e}") # Ask a question about the video prompt = st.text_input("Ask any question about the YouTube Video") # Chat with the video if prompt: - answer = app.chat(prompt) - st.write(answer) - - \ No newline at end of file + try: + answer = app.chat(prompt) + st.write(answer) + except Exception as e: + st.error(f"Error chatting with the video: {e}") \ No newline at end of file diff --git a/chat_with_X_tutorials/chat_with_youtube_videos/requirements.txt b/chat_with_X_tutorials/chat_with_youtube_videos/requirements.txt index 3d083f6..298d034 100644 --- a/chat_with_X_tutorials/chat_with_youtube_videos/requirements.txt +++ b/chat_with_X_tutorials/chat_with_youtube_videos/requirements.txt @@ -1,2 +1,3 @@ streamlit -embedchain[youtube] \ No newline at end of file +embedchain[youtube] +youtube-transcript-api==0.6.3 \ No newline at end of file diff --git a/docs/banner/unwind.png b/docs/banner/unwind.png deleted file mode 100644 index 12bf59d..0000000 Binary files a/docs/banner/unwind.png and /dev/null differ diff --git a/docs/banner/unwind_black.png b/docs/banner/unwind_black.png new file mode 100644 index 0000000..f2effbf Binary files /dev/null and b/docs/banner/unwind_black.png differ diff --git a/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/README.md b/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/README.md index a6788c0..74c6395 100644 --- a/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/README.md +++ b/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/README.md @@ -14,6 +14,7 @@ This Streamlit app implements an AI-powered research assistant that helps users 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory ``` 2. Install the required dependencies: diff --git a/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/ai_arxiv_agent_memory.py b/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/ai_arxiv_agent_memory.py index 098c3bd..2fef9f1 100644 --- a/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/ai_arxiv_agent_memory.py +++ b/llm_apps_with_memory_tutorials/ai_arxiv_agent_memory/ai_arxiv_agent_memory.py @@ -29,6 +29,17 @@ if all(api_keys.values()): search_query = st.text_input("Research paper search query") def process_with_gpt4(result): + """Processes an arXiv search result to produce a structured markdown output. + + This function takes a search result from arXiv and generates a markdown-formatted + table containing details about each paper. The table includes columns for the + paper's title, authors, a brief abstract, and a link to the paper on arXiv. + + Args: + result (str): The raw search result from arXiv, typically in a text format. + + Returns: + str: A markdown-formatted string containing a table with paper details.""" prompt = f""" Based on the following arXiv search result, provide a proper structured output in markdown that is readable by the users. Each paper should have a title, authors, abstract, and link. diff --git a/llm_apps_with_memory_tutorials/ai_travel_agent_memory/README.md b/llm_apps_with_memory_tutorials/ai_travel_agent_memory/README.md index bd3795e..c96b857 100644 --- a/llm_apps_with_memory_tutorials/ai_travel_agent_memory/README.md +++ b/llm_apps_with_memory_tutorials/ai_travel_agent_memory/README.md @@ -13,6 +13,7 @@ This Streamlit app implements an AI-powered travel assistant that remembers user 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/llm_apps_with_memory_tutorials/ai_travel_agent_memory ``` 2. Install the required dependencies: diff --git a/llm_apps_with_memory_tutorials/ai_travel_agent_memory/requirements.txt b/llm_apps_with_memory_tutorials/ai_travel_agent_memory/requirements.txt index 088b5ab..c7be07b 100644 --- a/llm_apps_with_memory_tutorials/ai_travel_agent_memory/requirements.txt +++ b/llm_apps_with_memory_tutorials/ai_travel_agent_memory/requirements.txt @@ -1,3 +1,3 @@ streamlit openai -mem0ai \ No newline at end of file +mem0ai==0.1.29 \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/ai_travel_agent_memory/travel_agent_memory.py b/llm_apps_with_memory_tutorials/ai_travel_agent_memory/travel_agent_memory.py index f257a1e..ed57cfa 100644 --- a/llm_apps_with_memory_tutorials/ai_travel_agent_memory/travel_agent_memory.py +++ b/llm_apps_with_memory_tutorials/ai_travel_agent_memory/travel_agent_memory.py @@ -34,17 +34,19 @@ if openai_api_key: st.session_state.messages = [] st.session_state.previous_user_id = user_id - if st.sidebar.button("View Memory Info"): - if user_id: - memories = memory.get_all(user_id=user_id) - if memories: - st.sidebar.write(f"Memory for user **{user_id}**:") - for mem in memories: - st.sidebar.write(f"- {mem['text']}") - else: - st.sidebar.info("No memory found for this user ID.") + # Sidebar option to show memory + st.sidebar.title("Memory Info") + if st.button("View My Memory"): + memories = memory.get_all(user_id=user_id) + if memories and "results" in memories: + st.write(f"Memory history for **{user_id}**:") + for mem in memories["results"]: + if "memory" in mem: + st.write(f"- {mem['memory']}") else: - st.sidebar.error("Please enter a username to view memory info.") + st.sidebar.info("No learning history found for this user ID.") + else: + st.sidebar.error("Please enter a username to view memory info.") # Initialize the chat history if "messages" not in st.session_state: @@ -67,8 +69,10 @@ if openai_api_key: # Retrieve relevant memories relevant_memories = memory.search(query=prompt, user_id=user_id) context = "Relevant past information:\n" - for mem in relevant_memories: - context += f"- {mem['text']}\n" + if relevant_memories and "results" in relevant_memories: + for memory in relevant_memories["results"]: + if "memory" in memory: + context += f"- {memory['memory']}\n" # Prepare the full prompt full_prompt = f"{context}\nHuman: {prompt}\nAI:" diff --git a/llm_apps_with_memory_tutorials/llama3_stateful_chat/local_llama3_chat.py b/llm_apps_with_memory_tutorials/llama3_stateful_chat/local_llama3_chat.py new file mode 100644 index 0000000..e0cca8f --- /dev/null +++ b/llm_apps_with_memory_tutorials/llama3_stateful_chat/local_llama3_chat.py @@ -0,0 +1,37 @@ +import streamlit as st +from openai import OpenAI + +# Set up the Streamlit App +st.title("Local ChatGPT with Memory ๐Ÿฆ™") +st.caption("Chat with locally hosted memory-enabled Llama-3 using the LM Studio ๐Ÿ’ฏ") + +# Point to the local server setup using LM Studio +client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio") + +# Initialize the chat history +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Display the chat history +for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + +# Accept user input +if prompt := st.chat_input("What is up?"): + st.session_state.messages.append({"role": "system", "content": "When the input starts with /add, don't follow up with a prompt."}) + # Add user message to chat history + st.session_state.messages.append({"role": "user", "content": prompt}) + # Display user message in chat message container + with st.chat_message("user"): + st.markdown(prompt) + # Generate response + response = client.chat.completions.create( + model="lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF", + messages=st.session_state.messages, temperature=0.7 + ) + # Add assistant response to chat history + st.session_state.messages.append({"role": "assistant", "content": response.choices[0].message.content}) + # Display assistant response in chat message container + with st.chat_message("assistant"): + st.markdown(response.choices[0].message.content) diff --git a/llm_apps_with_memory_tutorials/llama3_stateful_chat/requirements.txt b/llm_apps_with_memory_tutorials/llama3_stateful_chat/requirements.txt new file mode 100644 index 0000000..959b0d7 --- /dev/null +++ b/llm_apps_with_memory_tutorials/llama3_stateful_chat/requirements.txt @@ -0,0 +1,2 @@ +streamlit +openai \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/README.md b/llm_apps_with_memory_tutorials/llm_app_personalized_memory/README.md index 2a2e995..6d0c0e1 100644 --- a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/README.md +++ b/llm_apps_with_memory_tutorials/llm_app_personalized_memory/README.md @@ -14,6 +14,7 @@ This Streamlit app is an AI-powered chatbot that uses OpenAI's GPT-4o model with 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/llm_apps_with_memory_tutorials/llm_app_personalized_memory ``` 2. Install the required dependencies: diff --git a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/llm_app_memory.py b/llm_apps_with_memory_tutorials/llm_app_personalized_memory/llm_app_memory.py index 4712147..c355131 100644 --- a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/llm_app_memory.py +++ b/llm_apps_with_memory_tutorials/llm_app_personalized_memory/llm_app_memory.py @@ -1,3 +1,4 @@ +import os import streamlit as st from mem0 import Memory from openai import OpenAI @@ -6,6 +7,7 @@ st.title("LLM App with Memory ๐Ÿง ") st.caption("LLM App with personalized memory layer that remembers ever user's choice and interests") openai_api_key = st.text_input("Enter OpenAI API Key", type="password") +os.environ["OPENAI_API_KEY"] = openai_api_key if openai_api_key: # Initialize OpenAI client @@ -16,6 +18,7 @@ if openai_api_key: "vector_store": { "provider": "qdrant", "config": { + "collection_name": "llm_app_memory", "host": "localhost", "port": 6333, } @@ -59,11 +62,12 @@ if openai_api_key: # Sidebar option to show memory st.sidebar.title("Memory Info") - if st.sidebar.button("View Memory Info"): - memories = memory.get_all(user_id=user_id) - if memories: - st.sidebar.write(f"You are viewing memory for user **{user_id}**") - for mem in memories: - st.sidebar.write(f"- {mem['text']}") - else: - st.sidebar.info("No learning history found for this user ID.") \ No newline at end of file + if st.button("View My Memory"): + memories = memory.get_all(user_id=user_id) + if memories and "results" in memories: + st.write(f"Memory history for **{user_id}**:") + for mem in memories["results"]: + if "memory" in mem: + st.write(f"- {mem['memory']}") + else: + st.sidebar.info("No learning history found for this user ID.") \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/requirements.txt b/llm_apps_with_memory_tutorials/llm_app_personalized_memory/requirements.txt index 88649cc..c7be07b 100644 --- a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/requirements.txt +++ b/llm_apps_with_memory_tutorials/llm_app_personalized_memory/requirements.txt @@ -1,4 +1,3 @@ streamlit openai -mem0ai -litellm \ No newline at end of file +mem0ai==0.1.29 \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/README.md b/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/README.md new file mode 100644 index 0000000..252c984 --- /dev/null +++ b/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/README.md @@ -0,0 +1,41 @@ +## ๐Ÿง  Local ChatGPT using Llama 3.1 with Personal Memory +This Streamlit application implements a fully local ChatGPT-like experience using Llama 3.1, featuring personalized memory storage for each user. All components, including the language model, embeddings, and vector store, run locally without requiring external API keys. + +### Features +- Fully local implementation with no external API dependencies +- Powered by Llama 3.1 via Ollama +- Personal memory space for each user +- Local embedding generation using Nomic Embed +- Vector storage with Qdrant + +### How to get Started? + +1. Clone the GitHub repository +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/llm_apps_with_memory_tutorials/local_chatgpt_with_memory +``` + +2. Install the required dependencies: + +```bash +cd awesome-llm-apps/rag_tutorials/local_rag_agent +pip install -r requirements.txt +``` + +3. Install and start [Qdrant](https://qdrant.tech/documentation/guides/installation/) vector database locally + +```bash +docker pull qdrant/qdrant +docker run -p 6333:6333 qdrant/qdrant +``` + +4. Install [Ollama](https://ollama.com/download) and pull Llama 3.1 +```bash +ollama pull llama3.1 +``` + +5. Run the Streamlit App +```bash +streamlit run local_chatgpt_memory.py +``` \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/local_chatgpt_memory.py b/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/local_chatgpt_memory.py index e0cca8f..6b14e11 100644 --- a/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/local_chatgpt_memory.py +++ b/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/local_chatgpt_memory.py @@ -1,37 +1,137 @@ import streamlit as st -from openai import OpenAI +from mem0 import Memory +from litellm import completion -# Set up the Streamlit App -st.title("Local ChatGPT with Memory ๐Ÿฆ™") -st.caption("Chat with locally hosted memory-enabled Llama-3 using the LM Studio ๐Ÿ’ฏ") +# Configuration for Memory +config = { + "vector_store": { + "provider": "qdrant", + "config": { + "collection_name": "local-chatgpt-memory", + "host": "localhost", + "port": 6333, + "embedding_model_dims": 768, + }, + }, + "llm": { + "provider": "ollama", + "config": { + "model": "llama3.1:latest", + "temperature": 0, + "max_tokens": 8000, + "ollama_base_url": "http://localhost:11434", # Ensure this URL is correct + }, + }, + "embedder": { + "provider": "ollama", + "config": { + "model": "nomic-embed-text:latest", + # Alternatively, you can use "snowflake-arctic-embed:latest" + "ollama_base_url": "http://localhost:11434", + }, + }, + "version": "v1.1" +} -# Point to the local server setup using LM Studio -client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio") +st.title("Local ChatGPT using Llama 3.1 with Personal Memory ๐Ÿง ") +st.caption("Each user gets their own personalized memory space!") -# Initialize the chat history +# Initialize session state for chat history and previous user ID if "messages" not in st.session_state: st.session_state.messages = [] +if "previous_user_id" not in st.session_state: + st.session_state.previous_user_id = None -# Display the chat history -for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.markdown(message["content"]) +# Sidebar for user authentication +with st.sidebar: + st.title("User Settings") + user_id = st.text_input("Enter your Username", key="user_id") + + # Check if user ID has changed + if user_id != st.session_state.previous_user_id: + st.session_state.messages = [] # Clear chat history + st.session_state.previous_user_id = user_id # Update previous user ID + + if user_id: + st.success(f"Logged in as: {user_id}") + + # Initialize Memory with the configuration + m = Memory.from_config(config) + + # Memory viewing section + st.header("Memory Context") + if st.button("View My Memory"): + memories = m.get_all(user_id=user_id) + if memories and "results" in memories: + st.write(f"Memory history for **{user_id}**:") + for memory in memories["results"]: + if "memory" in memory: + st.write(f"- {memory['memory']}") -# Accept user input -if prompt := st.chat_input("What is up?"): - st.session_state.messages.append({"role": "system", "content": "When the input starts with /add, don't follow up with a prompt."}) - # Add user message to chat history - st.session_state.messages.append({"role": "user", "content": prompt}) - # Display user message in chat message container - with st.chat_message("user"): - st.markdown(prompt) - # Generate response - response = client.chat.completions.create( - model="lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF", - messages=st.session_state.messages, temperature=0.7 - ) - # Add assistant response to chat history - st.session_state.messages.append({"role": "assistant", "content": response.choices[0].message.content}) - # Display assistant response in chat message container - with st.chat_message("assistant"): - st.markdown(response.choices[0].message.content) +# Main chat interface +if user_id: # Only show chat interface if user is "logged in" + # Display chat history + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + + # User input + if prompt := st.chat_input("What is your message?"): + # Add user message to chat history + st.session_state.messages.append({"role": "user", "content": prompt}) + + # Display user message + with st.chat_message("user"): + st.markdown(prompt) + + # Add to memory + m.add(prompt, user_id=user_id) + + # Get context from memory + memories = m.get_all(user_id=user_id) + context = "" + if memories and "results" in memories: + for memory in memories["results"]: + if "memory" in memory: + context += f"- {memory['memory']}\n" + + # Generate assistant response + with st.chat_message("assistant"): + message_placeholder = st.empty() + full_response = "" + + # Stream the response + try: + response = completion( + model="ollama/llama3.1:latest", + messages=[ + {"role": "system", "content": "You are a helpful assistant with access to past conversations. Use the context provided to give personalized responses."}, + {"role": "user", "content": f"Context from previous conversations with {user_id}: {context}\nCurrent message: {prompt}"} + ], + api_base="http://localhost:11434", + stream=True + ) + + # Process streaming response + for chunk in response: + if hasattr(chunk, 'choices') and len(chunk.choices) > 0: + content = chunk.choices[0].delta.get('content', '') + if content: + full_response += content + message_placeholder.markdown(full_response + "โ–Œ") + + # Final update + message_placeholder.markdown(full_response) + except Exception as e: + st.error(f"Error generating response: {str(e)}") + full_response = "I apologize, but I encountered an error generating the response." + message_placeholder.markdown(full_response) + + # Add assistant response to chat history + st.session_state.messages.append({"role": "assistant", "content": full_response}) + + # Add response to memory + m.add(f"Assistant: {full_response}", user_id=user_id) + +else: + st.info("๐Ÿ‘ˆ Please enter your username in the sidebar to start chatting!") \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/requirements.txt b/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/requirements.txt index 959b0d7..0999609 100644 --- a/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/requirements.txt +++ b/llm_apps_with_memory_tutorials/local_chatgpt_with_memory/requirements.txt @@ -1,2 +1,4 @@ streamlit -openai \ No newline at end of file +openai +mem0ai==0.1.29 +litellm \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/multi_llm_memory/README.md b/llm_apps_with_memory_tutorials/multi_llm_memory/README.md new file mode 100644 index 0000000..a16766e --- /dev/null +++ b/llm_apps_with_memory_tutorials/multi_llm_memory/README.md @@ -0,0 +1,40 @@ +## ๐Ÿง  Multi-LLM App with Shared Memory +This Streamlit application demonstrates a multi-LLM system with a shared memory layer, allowing users to interact with different language models while maintaining conversation history and context across sessions. + +### Features + +- Support for multiple LLMs: + - OpenAI's GPT-4o + - Anthropic's Claude 3.5 Sonnet + +- Persistent memory using Qdrant vector store +- User-specific conversation history +- Memory retrieval for contextual responses +- User-friendly interface with LLM selection + +### How to get Started? + +1. Clone the GitHub repository +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/llm_apps_with_memory_tutorials/multi_llm_memory +``` + +2. Install the required dependencies: + +```bash +pip install -r requirements.txt +``` + +3. Ensure Qdrant is running: +The app expects Qdrant to be running on localhost:6333. Adjust the configuration in the code if your setup is different. + +```bash +docker pull qdrant/qdrant +docker run -p 6333:6333 qdrant/qdrant +``` + +4. Run the Streamlit App +```bash +streamlit run multi_llm_memory.py +``` \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/multi_llm_memory.py b/llm_apps_with_memory_tutorials/multi_llm_memory/multi_llm_memory.py similarity index 78% rename from llm_apps_with_memory_tutorials/llm_app_personalized_memory/multi_llm_memory.py rename to llm_apps_with_memory_tutorials/multi_llm_memory/multi_llm_memory.py index c62b7ea..a251c1e 100644 --- a/llm_apps_with_memory_tutorials/llm_app_personalized_memory/multi_llm_memory.py +++ b/llm_apps_with_memory_tutorials/multi_llm_memory/multi_llm_memory.py @@ -4,7 +4,7 @@ from openai import OpenAI import os from litellm import completion -st.title("LLM App with Shared Memory ๐Ÿง ") +st.title("Multi-LLM App with Shared Memory ๐Ÿง ") st.caption("LLM App with a personalized memory layer that remembers each user's choices and interests across multiple users and LLMs") openai_api_key = st.text_input("Enter OpenAI API Key", type="password") @@ -50,9 +50,10 @@ if openai_api_key and anthropic_api_key: with st.spinner('Searching...'): relevant_memories = memory.search(query=prompt, user_id=user_id) context = "Relevant past information:\n" - - for mem in relevant_memories: - context += f"- {mem['text']}\n" + if relevant_memories and "results" in relevant_memories: + for memory in relevant_memories["results"]: + if "memory" in memory: + context += f"- {memory['memory']}\n" full_prompt = f"{context}\nHuman: {prompt}\nAI:" @@ -76,12 +77,14 @@ if openai_api_key and anthropic_api_key: memory.add(answer, user_id=user_id) + # Sidebar option to show memory st.sidebar.title("Memory Info") - if st.sidebar.button("View Memory Info"): - memories = memory.get_all(user_id=user_id) - if memories: - st.sidebar.write(f"You are viewing memory for user **{user_id}**") - for mem in memories: - st.sidebar.write(f"- {mem['text']}") - else: - st.sidebar.info("No learning history found for this user ID.") \ No newline at end of file + if st.button("View My Memory"): + memories = memory.get_all(user_id=user_id) + if memories and "results" in memories: + st.write(f"Memory history for **{user_id}**:") + for mem in memories["results"]: + if "memory" in mem: + st.write(f"- {mem['memory']}") + else: + st.sidebar.info("No learning history found for this user ID.") \ No newline at end of file diff --git a/llm_apps_with_memory_tutorials/multi_llm_memory/requirements.txt b/llm_apps_with_memory_tutorials/multi_llm_memory/requirements.txt new file mode 100644 index 0000000..0999609 --- /dev/null +++ b/llm_apps_with_memory_tutorials/multi_llm_memory/requirements.txt @@ -0,0 +1,4 @@ +streamlit +openai +mem0ai==0.1.29 +litellm \ No newline at end of file diff --git a/llm_finetuning_tutorials/llama3.2_finetuning/README.md b/llm_finetuning_tutorials/llama3.2_finetuning/README.md index 6d5ef16..ab4e310 100644 --- a/llm_finetuning_tutorials/llama3.2_finetuning/README.md +++ b/llm_finetuning_tutorials/llama3.2_finetuning/README.md @@ -15,7 +15,7 @@ This script demonstrates how to finetune the Llama 3.2 model using the [Unsloth] ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git -cd llama3.2_finetuning +cd awesome-llm-apps/llm_finetuning_tutorials/llama3.2_finetuning ``` 2. Install the required dependencies: diff --git a/mcp_ai_agents/github_mcp_agent/README.md b/mcp_ai_agents/github_mcp_agent/README.md new file mode 100644 index 0000000..4c2698f --- /dev/null +++ b/mcp_ai_agents/github_mcp_agent/README.md @@ -0,0 +1,85 @@ +# ๐Ÿ™ MCP GitHub Agent + +A Streamlit application that allows you to explore and analyze GitHub repositories using natural language queries through the Model Context Protocol (MCP). + +## Features + +- **Natural Language Interface**: Ask questions about repositories in plain English +- **Comprehensive Analysis**: Explore issues, pull requests, repository activity, and code statistics +- **Interactive UI**: User-friendly interface with example queries and custom input +- **MCP Integration**: Leverages the Model Context Protocol to interact with GitHub's API +- **Real-time Results**: Get immediate insights on repository activity and health + +## Setup + +### Requirements + +- Python 3.8+ +- Node.js and npm (for MCP GitHub server) + - This is a critical requirement! The app uses `npx` to run the MCP GitHub server + - Download and install from [nodejs.org](https://nodejs.org/) +- GitHub Personal Access Token with appropriate permissions +- OpenAI API Key + +### Installation + +1. Clone this repository: + ```bash + git clone https://github.com/yourusername/mcp-github-agent.git + cd mcp-github-agent + ``` + +2. Install the required Python packages: + ```bash + pip install -r requirements.txt + ``` + +3. Verify Node.js and npm are installed: + ```bash + node --version + npm --version + npx --version + ``` + All of these commands should return version numbers. If they don't, please install Node.js. + +4. Set up your API keys: + - Set OpenAI API Key as an environment variable: + ```bash + export OPENAI_API_KEY=your-openai-api-key + ``` + - GitHub token will be entered directly in the app interface + +5. Create a GitHub Personal Access Token: + - Visit https://github.com/settings/tokens + - Create a new token with `repo` and `user` scopes + - Save the token somewhere secure + +### Running the App + +1. Start the Streamlit app: + ```bash + streamlit run app.py + ``` + +2. In the app interface: + - Enter your GitHub token in the sidebar + - Specify a repository to analyze + - Select a query type or write your own + - Click "Run Query" + +### Example Queries + +#### Issues +- "Show me issues by label" +- "What issues are being actively discussed?" +- "Find issues labeled as bugs" + +#### Pull Requests +- "What PRs need review?" +- "Show me recent merged PRs" +- "Find PRs with conflicts" + +#### Repository +- "Show repository health metrics" +- "Show repository activity patterns" +- "Analyze code quality trends" \ No newline at end of file diff --git a/mcp_ai_agents/github_mcp_agent/github_agent.py b/mcp_ai_agents/github_mcp_agent/github_agent.py new file mode 100644 index 0000000..212e5f2 --- /dev/null +++ b/mcp_ai_agents/github_mcp_agent/github_agent.py @@ -0,0 +1,149 @@ +import asyncio +import os +import streamlit as st +from textwrap import dedent +from agno.agent import Agent +from agno.tools.mcp import MCPTools +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + +# Page config +st.set_page_config(page_title="๐Ÿ™ GitHub MCP Agent", page_icon="๐Ÿ™", layout="wide") + +# Title and description +st.markdown("

๐Ÿ™ GitHub MCP Agent

", unsafe_allow_html=True) +st.markdown("Explore GitHub repositories with natural language using the Model Context Protocol") + +# Setup sidebar for API key +with st.sidebar: + st.header("๐Ÿ”‘ Authentication") + github_token = st.text_input("GitHub Token", type="password", + help="Create a token with repo scope at github.com/settings/tokens") + + if github_token: + os.environ["GITHUB_TOKEN"] = github_token + + st.markdown("---") + st.markdown("### Example Queries") + + st.markdown("**Issues**") + st.markdown("- Show me issues by label") + st.markdown("- What issues are being actively discussed?") + + st.markdown("**Pull Requests**") + st.markdown("- What PRs need review?") + st.markdown("- Show me recent merged PRs") + + st.markdown("**Repository**") + st.markdown("- Show repository health metrics") + st.markdown("- Show repository activity patterns") + + st.markdown("---") + st.caption("Note: Always specify the repository in your query if not already selected in the main input.") + +# Query input +col1, col2 = st.columns([3, 1]) +with col1: + repo = st.text_input("Repository", value="Shubhamsaboo/awesome-llm-apps", help="Format: owner/repo") +with col2: + query_type = st.selectbox("Query Type", [ + "Issues", "Pull Requests", "Repository Activity", "Custom" + ]) + +# Create predefined queries based on type +if query_type == "Issues": + query_template = f"Find issues labeled as bugs in {repo}" +elif query_type == "Pull Requests": + query_template = f"Show me recent merged PRs in {repo}" +elif query_type == "Repository Activity": + query_template = f"Analyze code quality trends in {repo}" +else: + query_template = "" + +query = st.text_area("Your Query", value=query_template, + placeholder="What would you like to know about this repository?") + +# Main function to run agent +async def run_github_agent(message): + if not os.getenv("GITHUB_TOKEN"): + return "Error: GitHub token not provided" + + try: + server_params = StdioServerParameters( + command="npx", + args=["-y", "@modelcontextprotocol/server-github"], + ) + + # Create client session + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # Initialize MCP toolkit + mcp_tools = MCPTools(session=session) + await mcp_tools.initialize() + + # Create agent + agent = Agent( + tools=[mcp_tools], + instructions=dedent("""\ + You are a GitHub assistant. Help users explore repositories and their activity. + - Provide organized, concise insights about the repository + - Focus on facts and data from the GitHub API + - Use markdown formatting for better readability + - Present numerical data in tables when appropriate + - Include links to relevant GitHub pages when helpful + """), + markdown=True, + show_tool_calls=True, + ) + + # Run agent + response = await agent.arun(message) + return response.content + except Exception as e: + return f"Error: {str(e)}" + +# Run button +if st.button("๐Ÿš€ Run Query", type="primary", use_container_width=True): + if not github_token: + st.error("Please enter your GitHub token in the sidebar") + elif not query: + st.error("Please enter a query") + else: + with st.spinner("Analyzing GitHub repository..."): + # Ensure the repository is explicitly mentioned in the query + if repo and repo not in query: + full_query = f"{query} in {repo}" + else: + full_query = query + + result = asyncio.run(run_github_agent(full_query)) + + # Display results in a nice container + st.markdown("### Results") + st.markdown(result) + +# Display help text for first-time users +if 'result' not in locals(): + st.markdown( + """
+

How to use this app:

+
    +
  1. Enter your GitHub token in the sidebar
  2. +
  3. Specify a repository (e.g., Shubhamsaboo/awesome-llm-apps)
  4. +
  5. Select a query type or write your own
  6. +
  7. Click 'Run Query' to see results
  8. +
+

Important Notes:

+ +
""", + unsafe_allow_html=True + ) + +# Footer +st.markdown("---") +st.write("Built with Streamlit, Agno, and Model Context Protocol โค๏ธ") \ No newline at end of file diff --git a/mcp_ai_agents/github_mcp_agent/requirements.txt b/mcp_ai_agents/github_mcp_agent/requirements.txt new file mode 100644 index 0000000..6880686 --- /dev/null +++ b/mcp_ai_agents/github_mcp_agent/requirements.txt @@ -0,0 +1,5 @@ +streamlit>=1.28.0 +agno>=1.1.0 +mcp>=0.1.0 +openai>=1.0.0 +asyncio>=3.4.3 \ No newline at end of file diff --git a/rag_tutorials/agentic_rag/README.md b/rag_tutorials/agentic_rag/README.md index 5f28f62..88b0695 100644 --- a/rag_tutorials/agentic_rag/README.md +++ b/rag_tutorials/agentic_rag/README.md @@ -14,6 +14,7 @@ This script demonstrates how to build a Retrieval-Augmented Generation (RAG) age 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/rag_tutorials/agentic_rag ``` 2. Install the required dependencies: diff --git a/rag_tutorials/agentic_rag/rag_agent.py b/rag_tutorials/agentic_rag/rag_agent.py index 118bcaa..6c4ebaf 100644 --- a/rag_tutorials/agentic_rag/rag_agent.py +++ b/rag_tutorials/agentic_rag/rag_agent.py @@ -1,9 +1,9 @@ -from phi.agent import Agent -from phi.model.openai import OpenAIChat -from phi.knowledge.pdf import PDFUrlKnowledgeBase -from phi.vectordb.lancedb import LanceDb, SearchType -from phi.playground import Playground, serve_playground_app -from phi.tools.duckduckgo import DuckDuckGo +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from agno.knowledge.pdf_url import PDFUrlKnowledgeBase +from agno.vectordb.lancedb import LanceDb, SearchType +from agno.playground import Playground, serve_playground_app +from agno.tools.duckduckgo import DuckDuckGoTools db_uri = "tmp/lancedb" # Create a knowledge base from a PDF @@ -19,7 +19,7 @@ rag_agent = Agent( model=OpenAIChat(id="gpt-4o"), agent_id="rag-agent", knowledge=knowledge_base, # Add the knowledge base to the agent - tools=[DuckDuckGo()], + tools=[DuckDuckGoTools()], show_tool_calls=True, markdown=True, ) diff --git a/rag_tutorials/agentic_rag/requirements.txt b/rag_tutorials/agentic_rag/requirements.txt index 845536c..9a87d2a 100644 --- a/rag_tutorials/agentic_rag/requirements.txt +++ b/rag_tutorials/agentic_rag/requirements.txt @@ -1,4 +1,4 @@ -phidata +agno openai lancedb tantivy diff --git a/rag_tutorials/ai_blog_search/README.md b/rag_tutorials/ai_blog_search/README.md new file mode 100644 index 0000000..abf2d34 --- /dev/null +++ b/rag_tutorials/ai_blog_search/README.md @@ -0,0 +1,56 @@ +# Agentic RAG with LangGraph: AI Blog Search + +## Overview +AI Blog Search is an Agentic RAG application designed to enhance information retrieval from AI-related blog posts. This system leverages LangChain, LangGraph, and Google's Gemini model to fetch, process, and analyze blog content, providing users with accurate and contextually relevant answers. + +## LangGraph Workflow +![LangGraph-Workflow](https://github.com/user-attachments/assets/07d8a6b5-f1ef-4b7e-b47a-4f14a192bd8a) + +## Demo +https://github.com/user-attachments/assets/cee07380-d3dc-45f4-ad26-7d944ba9c32b + +## Features +- **Document Retrieval:** Uses Qdrant as a vector database to store and retrieve blog content based on embeddings. +- **Agentic Query Processing:** Uses an AI-powered agent to determine whether a query should be rewritten, answered, or require more retrieval. +- **Relevance Assessment:** Implements an automated relevance grading system using Google's Gemini model. +- **Query Refinement:** Enhances poorly structured queries for better retrieval results. +- **Streamlit UI:** Provides a user-friendly interface for entering blog URLs, queries and retrieving insightful responses. +- **Graph-Based Workflow:** Implements a structured state graph using LangGraph for efficient decision-making. + +## Technologies Used +- **Programming Language**: [Python 3.10+](https://www.python.org/downloads/release/python-31011/) +- **Framework**: [LangChain](https://www.langchain.com/) and [LangGraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/) +- **Database**: [Qdrant](https://qdrant.tech/) +- **Models**: + - Embeddings: [Google Gemini API (embedding-001)](https://ai.google.dev/gemini-api/docs/embeddings) + - Chat: [Google Gemini API (gemini-2.0-flash)](https://ai.google.dev/gemini-api/docs/models/gemini#gemini-2.0-flash) +- **Blogs Loader**: [Langchain WebBaseLoader](https://python.langchain.com/docs/integrations/document_loaders/web_base/) +- **Document Splitter**: [RecursiveCharacterTextSplitter](https://python.langchain.com/v0.1/docs/modules/data_connection/document_transformers/recursive_text_splitter/) +- **User Interface (UI)**: [Streamlit](https://docs.streamlit.io/) + +## Requirements +1. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +2. **Run the Application**: + ```bash + streamlit run app.py + ``` + +3. **Use the Application**: + - Paste your Google API Key in the sidebar. + - Paste the blog link. + - Enter your query about the blog post. + +## :mailbox: Connect With Me +handshake gif + +

+ codewithcharan + __mr.__.unique + codewithcharan +

+ + \ No newline at end of file diff --git a/rag_tutorials/ai_blog_search/app.py b/rag_tutorials/ai_blog_search/app.py new file mode 100644 index 0000000..36006a4 --- /dev/null +++ b/rag_tutorials/ai_blog_search/app.py @@ -0,0 +1,373 @@ +from langchain_google_genai import GoogleGenerativeAIEmbeddings +from langchain_qdrant import QdrantVectorStore +from qdrant_client import QdrantClient +from uuid import uuid4 +from langchain_community.document_loaders import WebBaseLoader +from langchain_text_splitters import RecursiveCharacterTextSplitter +from langchain.tools.retriever import create_retriever_tool + +from typing import Annotated, Literal, Sequence +from typing_extensions import TypedDict +from functools import partial + +from langchain import hub +from langchain_core.messages import BaseMessage, HumanMessage +from langgraph.graph.message import add_messages +from langchain_core.output_parsers import StrOutputParser +from langchain_core.prompts import PromptTemplate +from langchain_google_genai import ChatGoogleGenerativeAI + +from pydantic import BaseModel, Field + +from langgraph.graph import END, StateGraph, START +from langgraph.prebuilt import ToolNode, tools_condition + +import streamlit as st + +st.set_page_config(page_title="AI Blog Search", page_icon=":mag_right:") +st.header(":blue[Agentic RAG with LangGraph:] :green[AI Blog Search]") + +# Initialize session state variables if they don't exist +if 'qdrant_host' not in st.session_state: + st.session_state.qdrant_host = "" +if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = "" +if 'gemini_api_key' not in st.session_state: + st.session_state.gemini_api_key = "" + +def set_sidebar(): + """Setup sidebar for API keys and configuration.""" + with st.sidebar: + st.subheader("API Configuration") + + qdrant_host = st.text_input("Enter your Qdrant Host URL:", type="password") + qdrant_api_key = st.text_input("Enter your Qdrant API key:", type="password") + gemini_api_key = st.text_input("Enter your Gemini API key:", type="password") + + if st.button("Done"): + if qdrant_host and qdrant_api_key and gemini_api_key: + st.session_state.qdrant_host = qdrant_host + st.session_state.qdrant_api_key = qdrant_api_key + st.session_state.gemini_api_key = gemini_api_key + st.success("API keys saved!") + else: + st.warning("Please fill all API fields") + +def initialize_components(): + """Initialize components that require API keys""" + if not all([st.session_state.qdrant_host, + st.session_state.qdrant_api_key, + st.session_state.gemini_api_key]): + return None, None, None + + try: + # Initialize embedding model with API key + embedding_model = GoogleGenerativeAIEmbeddings( + model="models/embedding-001", + google_api_key=st.session_state.gemini_api_key + ) + + # Initialize Qdrant client + client = QdrantClient( + st.session_state.qdrant_host, + api_key=st.session_state.qdrant_api_key + ) + + # Initialize vector store + db = QdrantVectorStore( + client=client, + collection_name="qdrant_db", + embedding=embedding_model + ) + + return embedding_model, client, db + + except Exception as e: + st.error(f"Initialization error: {str(e)}") + return None, None, None + +class AgentState(TypedDict): + messages: Annotated[Sequence[BaseMessage], add_messages] + +# Edges +## Check Relevance +def grade_documents(state) -> Literal["generate", "rewrite"]: + """ + Determines whether the retrieved documents are relevant to the question. + + Args: + state (messages): The current state + + Returns: + str: A decision for whether the documents are relevant or not + """ + + print("---CHECK RELEVANCE---") + + # Data model + class grade(BaseModel): + """Binary score for relevance check.""" + + binary_score: str = Field(description="Relevance score 'yes' or 'no'") + + # LLM + model = ChatGoogleGenerativeAI(api_key=st.session_state.gemini_api_key, temperature=0, model="gemini-2.0-flash", streaming=True) + + # LLM with tool and validation + llm_with_tool = model.with_structured_output(grade) + + # Prompt + prompt = PromptTemplate( + template="""You are a grader assessing relevance of a retrieved document to a user question. \n + Here is the retrieved document: \n\n {context} \n\n + Here is the user question: {question} \n + If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n + Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.""", + input_variables=["context", "question"], + ) + + # Chain + chain = prompt | llm_with_tool + + messages = state["messages"] + last_message = messages[-1] + + question = messages[0].content + docs = last_message.content + + scored_result = chain.invoke({"question": question, "context": docs}) + + score = scored_result.binary_score + + if score == "yes": + print("---DECISION: DOCS RELEVANT---") + return "generate" + + else: + print("---DECISION: DOCS NOT RELEVANT---") + print(score) + return "rewrite" + +# Nodes +## agent node +def agent(state, tools): + """ + Invokes the agent model to generate a response based on the current state. Given + the question, it will decide to retrieve using the retriever tool, or simply end. + + Args: + state (messages): The current state + + Returns: + dict: The updated state with the agent response appended to messages + """ + print("---CALL AGENT---") + messages = state["messages"] + model = ChatGoogleGenerativeAI(api_key=st.session_state.gemini_api_key, temperature=0, streaming=True, model="gemini-2.0-flash") + model = model.bind_tools(tools) + response = model.invoke(messages) + + # We return a list, because this will get added to the existing list + return {"messages": [response]} + +## rewrite node +def rewrite(state): + """ + Transform the query to produce a better question. + + Args: + state (messages): The current state + + Returns: + dict: The updated state with re-phrased question + """ + + print("---TRANSFORM QUERY---") + messages = state["messages"] + question = messages[0].content + + msg = [ + HumanMessage( + content=f""" \n + Look at the input and try to reason about the underlying semantic intent / meaning. \n + Here is the initial question: + \n ------- \n + {question} + \n ------- \n + Formulate an improved question: """, + ) + ] + + # Grader + model = ChatGoogleGenerativeAI(api_key=st.session_state.gemini_api_key, temperature=0, model="gemini-2.0-flash", streaming=True) + response = model.invoke(msg) + return {"messages": [response]} + +## generate node +def generate(state): + """ + Generate answer + + Args: + state (messages): The current state + + Returns: + dict: The updated state with re-phrased question + """ + print("---GENERATE---") + messages = state["messages"] + question = messages[0].content + last_message = messages[-1] + + docs = last_message.content + + # Initialize a Chat Prompt Template + prompt_template = hub.pull("rlm/rag-prompt") + + # Initialize a Generator (i.e. Chat Model) + chat_model = ChatGoogleGenerativeAI(api_key=st.session_state.gemini_api_key, model="gemini-2.0-flash", temperature=0, streaming=True) + + # Initialize a Output Parser + output_parser = StrOutputParser() + + # RAG Chain + rag_chain = prompt_template | chat_model | output_parser + + response = rag_chain.invoke({"context": docs, "question": question}) + + return {"messages": [response]} + +# graph function +def get_graph(retriever_tool): + tools = [retriever_tool] # Create tools list here + + # Define a new graph + workflow = StateGraph(AgentState) + + # Use partial to pass tools to the agent function + workflow.add_node("agent", partial(agent, tools=tools)) + + # Rest of the graph setup remains the same + retrieve = ToolNode(tools) + workflow.add_node("retrieve", retrieve) + workflow.add_node("rewrite", rewrite) # Re-writing the question + workflow.add_node( + "generate", generate + ) # Generating a response after we know the documents are relevant + # Call agent node to decide to retrieve or not + workflow.add_edge(START, "agent") + + # Decide whether to retrieve + workflow.add_conditional_edges( + "agent", + # Assess agent decision + tools_condition, + { + # Translate the condition outputs to nodes in our graph + "tools": "retrieve", + END: END, + }, + ) + + # Edges taken after the `action` node is called. + workflow.add_conditional_edges( + "retrieve", + # Assess agent decision + grade_documents, + ) + workflow.add_edge("generate", END) + workflow.add_edge("rewrite", "agent") + + # Compile + graph = workflow.compile() + + return graph + +def generate_message(graph, inputs): + generated_message = "" + + for output in graph.stream(inputs): + for key, value in output.items(): + if key == "generate" and isinstance(value, dict): + generated_message = value.get("messages", [""])[0] + + return generated_message + +def add_documents_to_qdrant(url, db): + try: + docs = WebBaseLoader(url).load() + text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( + chunk_size=100, chunk_overlap=50 + ) + doc_chunks = text_splitter.split_documents(docs) + uuids = [str(uuid4()) for _ in range(len(doc_chunks))] + db.add_documents(documents=doc_chunks, ids=uuids) + return True + except Exception as e: + st.error(f"Error adding documents: {str(e)}") + return False + +def main(): + set_sidebar() + + # Check if API keys are set + if not all([st.session_state.qdrant_host, + st.session_state.qdrant_api_key, + st.session_state.gemini_api_key]): + st.warning("Please configure your API keys in the sidebar first") + return + + # Initialize components + embedding_model, client, db = initialize_components() + if not all([embedding_model, client, db]): + return + + # Initialize retriever and tools + retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": 5}) + retriever_tool = create_retriever_tool( + retriever, + "retrieve_blog_posts", + "Search and return information about blog posts on LLMs, LLM agents, prompt engineering, and adversarial attacks on LLMs.", + ) + tools = [retriever_tool] + + # URL input section + url = st.text_input( + ":link: Paste the blog link:", + placeholder="e.g., https://lilianweng.github.io/posts/2023-06-23-agent/" + ) + if st.button("Enter URL"): + if url: + with st.spinner("Processing documents..."): + if add_documents_to_qdrant(url, db): + st.success("Documents added successfully!") + else: + st.error("Failed to add documents") + else: + st.warning("Please enter a URL") + + # Query section + graph = get_graph(retriever_tool) + query = st.text_area( + ":bulb: Enter your query about the blog post:", + placeholder="e.g., What does Lilian Weng say about the types of agent memory?" + ) + + if st.button("Submit Query"): + if not query: + st.warning("Please enter a query") + return + + inputs = {"messages": [HumanMessage(content=query)]} + with st.spinner("Generating response..."): + try: + response = generate_message(graph, inputs) + st.write(response) + except Exception as e: + st.error(f"Error generating response: {str(e)}") + + st.markdown("---") + st.write("Built with :blue-background[LangChain] | :blue-background[LangGraph] by [Charan](https://www.linkedin.com/in/codewithcharan/)") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rag_tutorials/ai_blog_search/requirements.txt b/rag_tutorials/ai_blog_search/requirements.txt new file mode 100644 index 0000000..82e15a7 --- /dev/null +++ b/rag_tutorials/ai_blog_search/requirements.txt @@ -0,0 +1,10 @@ +langchain +langgraph +langchainhub +langchain-community +langchain-google-genai +langchain-qdrant +langchain-text-splitters +tiktoken +beautifulsoup4 +python-dotenv \ No newline at end of file diff --git a/rag_tutorials/autonomous_rag/README.md b/rag_tutorials/autonomous_rag/README.md index ab7a27e..f68dc13 100644 --- a/rag_tutorials/autonomous_rag/README.md +++ b/rag_tutorials/autonomous_rag/README.md @@ -14,6 +14,7 @@ Features 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/rag_tutorials/autonomous_rag ``` 2. Install the required dependencies: diff --git a/rag_tutorials/autonomous_rag/autorag.py b/rag_tutorials/autonomous_rag/autorag.py index 76d8a19..29ff443 100644 --- a/rag_tutorials/autonomous_rag/autorag.py +++ b/rag_tutorials/autonomous_rag/autorag.py @@ -1,14 +1,14 @@ import streamlit as st import nest_asyncio from io import BytesIO -from phi.assistant import Assistant -from phi.document.reader.pdf import PDFReader -from phi.llm.openai import OpenAIChat -from phi.knowledge import AssistantKnowledge -from phi.tools.duckduckgo import DuckDuckGo -from phi.embedder.openai import OpenAIEmbedder -from phi.vectordb.pgvector import PgVector2 -from phi.storage.assistant.postgres import PgAssistantStorage +from agno.agent import Agent +from agno.document.reader.pdf_reader import PDFReader +from agno.models.openai import OpenAIChat +from agno.knowledge.pdf_url import PDFUrlKnowledgeBase +from agno.tools.duckduckgo import DuckDuckGoTools +from agno.embedder.openai import OpenAIEmbedder +from agno.vectordb.pgvector import PgVector, SearchType +from agno.storage.agent.postgres import PostgresAgentStorage # Apply nest_asyncio to allow nested event loops, required for running async functions in Streamlit nest_asyncio.apply() @@ -18,22 +18,35 @@ DB_URL = "postgresql+psycopg://ai:ai@localhost:5532/ai" # Function to set up the Assistant, utilizing caching for resource efficiency @st.cache_resource -def setup_assistant(api_key: str) -> Assistant: - llm = OpenAIChat(model="gpt-4o-mini", api_key=api_key) +def setup_assistant(api_key: str) -> Agent: + """Initializes and returns an AI Assistant agent with caching for efficiency. + + This function sets up an AI Assistant agent using the OpenAI GPT-4o-mini model + and configures it with a knowledge base, storage, and web search tools. The + assistant is designed to first search its knowledge base before querying the + internet, providing clear and concise answers. + + Args: + api_key (str): The API key required to access the OpenAI services. + + Returns: + Agent: An initialized Assistant agent configured with a language model, + knowledge base, storage, and additional tools for enhanced functionality.""" + llm = OpenAIChat(id="gpt-4o-mini", api_key=api_key) # Set up the Assistant with storage, knowledge base, and tools - return Assistant( - name="auto_rag_assistant", # Name of the Assistant - llm=llm, # Language model to be used - storage=PgAssistantStorage(table_name="auto_rag_storage", db_url=DB_URL), - knowledge_base=AssistantKnowledge( - vector_db=PgVector2( + return Agent( + id="auto_rag_agent", # Name of the Assistant + model=llm, # Language model to be used + storage=PostgresAgentStorage(table_name="auto_rag_storage", db_url=DB_URL), + knowledge_base=PDFUrlKnowledgeBase( + vector_db=PgVector( db_url=DB_URL, collection="auto_rag_docs", - embedder=OpenAIEmbedder(model="text-embedding-ada-002", dimensions=1536, api_key=api_key), + embedder=OpenAIEmbedder(id="text-embedding-ada-002", dimensions=1536, api_key=api_key), ), num_documents=3, ), - tools=[DuckDuckGo()], # Additional tool for web search via DuckDuckGo + tools=[DuckDuckGoTools()], # Additional tool for web search via DuckDuckGo instructions=[ "Search your knowledge base first.", "If not found, search the internet.", @@ -41,27 +54,61 @@ def setup_assistant(api_key: str) -> Assistant: ], show_tool_calls=True, search_knowledge=True, - read_chat_history=True, markdown=True, debug_mode=True, ) # Function to add a PDF document to the knowledge base -def add_document(assistant: Assistant, file: BytesIO): +def add_document(agent: Agent, file: BytesIO): + """Add a PDF document to the agent's knowledge base. + + This function reads a PDF document from a file-like object and adds its contents to the specified agent's knowledge base. If the document is successfully read, the contents are loaded into the knowledge base with the option to upsert existing data. + + Args: + agent (Agent): The agent whose knowledge base will be updated. + file (BytesIO): A file-like object containing the PDF document to be added. + + Returns: + None: The function does not return a value but provides feedback on whether the operation was successful.""" reader = PDFReader() docs = reader.read(file) if docs: - assistant.knowledge_base.load_documents(docs, upsert=True) + agent.knowledge_base.load_documents(docs, upsert=True) st.success("Document added to the knowledge base.") else: st.error("Failed to read the document.") # Function to query the Assistant and return a response -def query_assistant(assistant: Assistant, question: str) -> str: - return "".join([delta for delta in assistant.run(question)]) +def query_assistant(agent: Agent, question: str) -> str: + """Queries the Assistant and returns a response. + + Args: + agent (Agent): An instance of the Agent class used to process the query. + question (str): The question to be asked to the Assistant. + + Returns: + str: The response generated by the Assistant for the given question.""" + return "".join([delta for delta in agent.run(question)]) # Main function to handle Streamlit app layout and interactions def main(): + """Main function to handle the layout and interactions for the Streamlit app. + + This function sets up the Streamlit app configuration, handles user inputs such + as OpenAI API key, PDF uploads, and user questions, and interacts with an + autonomous retrieval-augmented generation (RAG) assistant based on GPT-4o. + + The app allows users to upload PDF documents to enhance the knowledge base and + submit questions to receive generated responses. + + Side Effects: + - Configures Streamlit page and title. + - Prompts users to input an OpenAI API key and a question. + - Allows users to upload PDF documents. + - Displays responses generated by querying an assistant. + + Raises: + StreamlitWarning: If the OpenAI API key is not provided.""" st.set_page_config(page_title="AutoRAG", layout="wide") st.title("๐Ÿค– Auto-RAG: Autonomous RAG with GPT-4o") @@ -87,7 +134,7 @@ def main(): with st.spinner("๐Ÿค” Thinking..."): # Query the assistant and display the response answer = query_assistant(assistant, question) - st.write("๐Ÿ“ **Response:**", answer) + st.write("๐Ÿ“ **Response:**", answer.content) else: # Show an error if the question input is empty st.error("Please enter a question.") diff --git a/rag_tutorials/autonomous_rag/requirements.txt b/rag_tutorials/autonomous_rag/requirements.txt index 365f9d3..a8b065e 100644 --- a/rag_tutorials/autonomous_rag/requirements.txt +++ b/rag_tutorials/autonomous_rag/requirements.txt @@ -1,9 +1,10 @@ streamlit -phidata +agno openai psycopg-binary pgvector requests sqlalchemy pypdf -duckduckgo-search \ No newline at end of file +duckduckgo-search +nest_asyncio diff --git a/rag_tutorials/corrective_rag/README.md b/rag_tutorials/corrective_rag/README.md new file mode 100644 index 0000000..ae8c72a --- /dev/null +++ b/rag_tutorials/corrective_rag/README.md @@ -0,0 +1,58 @@ +# ๐Ÿ”„ Corrective RAG Agent +A sophisticated Retrieval-Augmented Generation (RAG) system that implements a corrective multi-stage workflow using LangGraph. This system combines document retrieval, relevance grading, query transformation, and web search to provide comprehensive and accurate responses. + +## Features + +- **Smart Document Retrieval**: Uses Qdrant vector store for efficient document retrieval +- **Document Relevance Grading**: Employs Claude 3.5 sonnet to assess document relevance +- **Query Transformation**: Improves search results by optimizing queries when needed +- **Web Search Fallback**: Uses Tavily API for web search when local documents aren't sufficient +- **Multi-Model Approach**: Combines OpenAI embeddings and Claude 3.5 sonnet for different tasks +- **Interactive UI**: Built with Streamlit for easy document upload and querying + +## How to Run? + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd rag_tutorials/corrective_rag + ``` + +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Set Up API Keys**: + You'll need to obtain the following API keys: + - [OpenAI API key](https://platform.openai.com/api-keys) (for embeddings) + - [Anthropic API key](https://console.anthropic.com/settings/keys) (for Claude 3.5 sonnet as LLM) + - [Tavily API key](https://app.tavily.com/home) (for web search) + - Qdrant Cloud Setup + 1. Visit [Qdrant Cloud](https://cloud.qdrant.io/) + 2. Create an account or sign in + 3. Create a new cluster + 4. Get your credentials: + - Qdrant API Key: Found in API Keys section + - Qdrant URL: Your cluster URL (format: `https://xxx-xxx.aws.cloud.qdrant.io`) + +4. **Run the Application**: + ```bash + streamlit run corrective_rag.py + ``` + +5. **Use the Application**: + - Upload documents or provide URLs + - Enter your questions in the query box + - View the step-by-step Corrective RAG process + - Get comprehensive answers + +## Tech Stack + +- **LangChain**: For RAG orchestration and chains +- **LangGraph**: For workflow management +- **Qdrant**: Vector database for document storage +- **Claude 3.5 sonnet**: Main language model for analysis and generation +- **OpenAI**: For document embeddings +- **Tavily**: For web search capabilities +- **Streamlit**: For the user interface diff --git a/rag_tutorials/corrective_rag/corrective_rag.py b/rag_tutorials/corrective_rag/corrective_rag.py new file mode 100644 index 0000000..3746741 --- /dev/null +++ b/rag_tutorials/corrective_rag/corrective_rag.py @@ -0,0 +1,453 @@ +from langchain import hub +from langchain.output_parsers import PydanticOutputParser +from langchain_core.output_parsers import StrOutputParser +from langchain.schema import Document +from pydantic import BaseModel, Field +import streamlit as st +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_community.document_loaders import PyPDFLoader, TextLoader, WebBaseLoader +from langchain_community.tools import TavilySearchResults +from langchain_community.vectorstores import Qdrant +from langchain_openai import OpenAIEmbeddings, ChatOpenAI +from langchain_core.messages import HumanMessage +from langgraph.graph import END, StateGraph +from typing import Dict, TypedDict +from langchain_core.prompts import PromptTemplate +import pprint +import yaml +import nest_asyncio +from qdrant_client import QdrantClient +from qdrant_client.models import Distance, VectorParams +import tempfile +import os +from langchain_anthropic import ChatAnthropic +from tenacity import retry, stop_after_attempt, wait_exponential + + +nest_asyncio.apply() + +retriever = None + +def initialize_session_state(): + """Initialize session state variables for API keys and URLs.""" + if 'initialized' not in st.session_state: + st.session_state.initialized = False + # Initialize API keys and URLs + st.session_state.anthropic_api_key = "" + st.session_state.openai_api_key = "" + st.session_state.tavily_api_key = "" + st.session_state.qdrant_api_key = "" + st.session_state.qdrant_url = "http://localhost:6333" + st.session_state.doc_url = "https://arxiv.org/pdf/2307.09288.pdf" + +def setup_sidebar(): + """Setup sidebar for API keys and configuration.""" + with st.sidebar: + st.subheader("API Configuration") + st.session_state.anthropic_api_key = st.text_input("Anthropic API Key", value=st.session_state.anthropic_api_key, type="password", help="Required for Claude 3 model") + st.session_state.openai_api_key = st.text_input("OpenAI API Key", value=st.session_state.openai_api_key, type="password") + st.session_state.tavily_api_key = st.text_input("Tavily API Key", value=st.session_state.tavily_api_key, type="password") + st.session_state.qdrant_url = st.text_input("Qdrant URL", value=st.session_state.qdrant_url) + st.session_state.qdrant_api_key = st.text_input("Qdrant API Key", value=st.session_state.qdrant_api_key, type="password") + st.session_state.doc_url = st.text_input("Document URL", value=st.session_state.doc_url) + + if not all([st.session_state.openai_api_key, st.session_state.anthropic_api_key, st.session_state.qdrant_url]): + st.warning("Please provide the required API keys and URLs") + st.stop() + + st.session_state.initialized = True + +initialize_session_state() +setup_sidebar() + +# Use session state variables instead of config +openai_api_key = st.session_state.openai_api_key +tavily_api_key = st.session_state.tavily_api_key +anthropic_api_key = st.session_state.anthropic_api_key + +# Update embeddings initialization +embeddings = OpenAIEmbeddings( + model="text-embedding-3-small", + api_key=st.session_state.openai_api_key +) + +# Update Qdrant client initialization +client = QdrantClient( + url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key +) + +@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) +def execute_tavily_search(tool, query): + return tool.invoke({"query": query}) + +def web_search(state): + """Web search based on the re-phrased question using Tavily API.""" + print("~-web search-~") + state_dict = state["keys"] + question = state_dict["question"] + documents = state_dict["documents"] + + # Create progress placeholder + progress_placeholder = st.empty() + progress_placeholder.info("Initiating web search...") + + try: + # Validate Tavily API key + if not st.session_state.tavily_api_key: + progress_placeholder.warning("Tavily API key not provided - skipping web search") + return {"keys": {"documents": documents, "question": question}} + + progress_placeholder.info("Configuring search tool...") + + # Initialize Tavily search tool + tool = TavilySearchResults( + api_key=st.session_state.tavily_api_key, + max_results=3, + search_depth="advanced" + ) + + # Execute search with retry logic + progress_placeholder.info("Executing search query...") + try: + search_results = execute_tavily_search(tool, question) + except Exception as search_error: + progress_placeholder.error(f"Search failed after retries: {str(search_error)}") + return {"keys": {"documents": documents, "question": question}} + + if not search_results: + progress_placeholder.warning("No search results found") + return {"keys": {"documents": documents, "question": question}} + + # Process results + progress_placeholder.info("Processing search results...") + web_results = [] + for result in search_results: + # Extract and format relevant information + content = ( + f"Title: {result.get('title', 'No title')}\n" + f"Content: {result.get('content', 'No content')}\n" + ) + web_results.append(content) + + # Create document from results + web_document = Document( + page_content="\n\n".join(web_results), + metadata={ + "source": "tavily_search", + "query": question, + "result_count": len(web_results) + } + ) + documents.append(web_document) + + progress_placeholder.success(f"Successfully added {len(web_results)} search results") + + except Exception as error: + error_msg = f"Web search error: {str(error)}" + print(error_msg) + progress_placeholder.error(error_msg) + finally: + progress_placeholder.empty() + return {"keys": {"documents": documents, "question": question}} + + +def load_documents(file_or_url: str, is_url: bool = True) -> list: + try: + if is_url: + loader = WebBaseLoader(file_or_url) + loader.requests_per_second = 1 + else: + file_extension = os.path.splitext(file_or_url)[1].lower() + if file_extension == '.pdf': + loader = PyPDFLoader(file_or_url) + elif file_extension in ['.txt', '.md']: + loader = TextLoader(file_or_url) + else: + raise ValueError(f"Unsupported file type: {file_extension}") + + return loader.load() + except Exception as e: + st.error(f"Error loading document: {str(e)}") + return [] + +st.subheader("Document Input") +input_option = st.radio("Choose input method:", ["URL", "File Upload"]) + +docs = None + +if input_option == "URL": + url = st.text_input("Enter document URL:", value=st.session_state.doc_url) + if url: + docs = load_documents(url, is_url=True) +else: + uploaded_file = st.file_uploader("Upload a document", type=['pdf', 'txt', 'md']) + if uploaded_file: + # Create a temporary file to store the upload + with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(uploaded_file.name)[1]) as tmp_file: + tmp_file.write(uploaded_file.getvalue()) + docs = load_documents(tmp_file.name, is_url=False) + # Clean up the temporary file + os.unlink(tmp_file.name) + +if docs: + text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( + chunk_size=500, chunk_overlap=100 + ) + all_splits = text_splitter.split_documents(docs) + + client = QdrantClient(url=st.session_state.qdrant_url, api_key=st.session_state.qdrant_api_key) + collection_name = "rag-qdrant" + + try: + # Try to delete the collection if it exists + client.delete_collection(collection_name) + except Exception: + pass + + client.create_collection( + collection_name=collection_name, + vectors_config=VectorParams(size=1536, distance=Distance.COSINE), + ) + + # Create vectorstore + vectorstore = Qdrant( + client=client, + collection_name=collection_name, + embeddings=embeddings, + ) + + # Add documents to the vectorstore + vectorstore.add_documents(all_splits) + retriever = vectorstore.as_retriever() + + +class GraphState(TypedDict): + keys: Dict[str, any] + + +def retrieve(state): + print("~-retrieve-~") + state_dict = state["keys"] + question = state_dict["question"] + + if retriever is None: + return {"keys": {"documents": [], "question": question}} + + documents = retriever.get_relevant_documents(question) + return {"keys": {"documents": documents, "question": question}} + + +def generate(state): + """Generate answer using Claude 3 model""" + print("~-generate-~") + state_dict = state["keys"] + question, documents = state_dict["question"], state_dict["documents"] + try: + prompt = PromptTemplate(template="""Based on the following context, please answer the question. + Context: {context} + Question: {question} + Answer:""", input_variables=["context", "question"]) + llm = ChatAnthropic(model="claude-3-5-sonnet-20241022", api_key=st.session_state.anthropic_api_key, + temperature=0, max_tokens=1000) + context = "\n\n".join(doc.page_content for doc in documents) + + # Create and run chain + rag_chain = ( + {"context": lambda x: context, "question": lambda x: question} + | prompt + | llm + | StrOutputParser() + ) + + generation = rag_chain.invoke({}) + + return { + "keys": { + "documents": documents, + "question": question, + "generation": generation + } + } + + except Exception as e: + error_msg = f"Error in generate function: {str(e)}" + print(error_msg) + st.error(error_msg) + return {"keys": {"documents": documents, "question": question, + "generation": "Sorry, I encountered an error while generating the response."}} + +def grade_documents(state): + """Determines whether the retrieved documents are relevant.""" + print("~-check relevance-~") + state_dict = state["keys"] + question = state_dict["question"] + documents = state_dict["documents"] + + llm = ChatAnthropic(model="claude-3-5-sonnet-20241022", api_key=st.session_state.anthropic_api_key, + temperature=0, max_tokens=1000) + + prompt = PromptTemplate(template="""You are grading the relevance of a retrieved document to a user question. + Return ONLY a JSON object with a "score" field that is either "yes" or "no". + Do not include any other text or explanation. + + Document: {context} + Question: {question} + + Rules: + - Check for related keywords or semantic meaning + - Use lenient grading to only filter clear mismatches + - Return exactly like this example: {{"score": "yes"}} or {{"score": "no"}}""", + input_variables=["context", "question"]) + + chain = ( + prompt + | llm + | StrOutputParser() + ) + + filtered_docs = [] + search = "No" + + for d in documents: + try: + response = chain.invoke({"question": question, "context": d.page_content}) + import re + json_match = re.search(r'\{.*\}', response) + if json_match: + response = json_match.group() + + import json + score = json.loads(response) + + if score.get("score") == "yes": + print("~-grade: document relevant-~") + filtered_docs.append(d) + else: + print("~-grade: document not relevant-~") + search = "Yes" + + except Exception as e: + print(f"Error grading document: {str(e)}") + # On error, keep the document to be safe + filtered_docs.append(d) + continue + + return {"keys": {"documents": filtered_docs, "question": question, "run_web_search": search}} + + +def transform_query(state): + """Transform the query to produce a better question.""" + print("~-transform query-~") + state_dict = state["keys"] + question = state_dict["question"] + documents = state_dict["documents"] + + # Create a prompt template + prompt = PromptTemplate( + template="""Generate a search-optimized version of this question by + analyzing its core semantic meaning and intent. + \n ------- \n + {question} + \n ------- \n + Return only the improved question with no additional text:""", + input_variables=["question"], + ) + + # Use Claude instead of Gemini + llm = ChatAnthropic( + model="claude-3-5-sonnet-20240620", + anthropic_api_key=st.session_state.anthropic_api_key, + temperature=0, + max_tokens=1000 + ) + + # Prompt + chain = prompt | llm | StrOutputParser() + better_question = chain.invoke({"question": question}) + + return { + "keys": {"documents": documents, "question": better_question} + } + + +def decide_to_generate(state): + print("~-decide to generate-~") + state_dict = state["keys"] + search = state_dict["run_web_search"] + + if search == "Yes": + + print("~-decision: transform query and run web search-~") + return "transform_query" + else: + print("~-decision: generate-~") + return "generate" + +def format_document(doc: Document) -> str: + return f""" + Source: {doc.metadata.get('source', 'Unknown')} + Title: {doc.metadata.get('title', 'No title')} + Content: {doc.page_content[:200]}... + """ + +def format_state(state: dict) -> str: + formatted = {} + + for key, value in state.items(): + if key == "documents": + formatted[key] = [format_document(doc) for doc in value] + else: + formatted[key] = value + + return formatted + + +workflow = StateGraph(GraphState) + +# Define the nodes by langgraph +workflow.add_node("retrieve", retrieve) +workflow.add_node("grade_documents", grade_documents) +workflow.add_node("generate", generate) +workflow.add_node("transform_query", transform_query) +workflow.add_node("web_search", web_search) + +# Build graph +workflow.set_entry_point("retrieve") +workflow.add_edge("retrieve", "grade_documents") +workflow.add_conditional_edges( + "grade_documents", + decide_to_generate, + { + "transform_query": "transform_query", + "generate": "generate", + }, +) +workflow.add_edge("transform_query", "web_search") +workflow.add_edge("web_search", "generate") +workflow.add_edge("generate", END) + +app = workflow.compile() + +st.title("๐Ÿ”„ Corrective RAG Agent") + +st.text("A possible query: What are the experiment results and ablation studies in this research paper?") + +# User input +user_question = st.text_input("Please enter your question:") + +if user_question: + inputs = { + "keys": { + "question": user_question, + } + } + + for output in app.stream(inputs): + for key, value in output.items(): + with st.expander(f"Step '{key}':"): + st.text(pprint.pformat(format_state(value["keys"]), indent=2, width=80)) + + final_generation = value['keys'].get('generation', 'No final generation produced.') + st.subheader("Final Generation:") + st.write(final_generation) diff --git a/rag_tutorials/corrective_rag/requirements.txt b/rag_tutorials/corrective_rag/requirements.txt new file mode 100644 index 0000000..828c775 --- /dev/null +++ b/rag_tutorials/corrective_rag/requirements.txt @@ -0,0 +1,18 @@ +# Core dependencies +langchain==0.3.12 +langgraph==0.2.53 +qdrant-client==1.12.1 +langchain-openai==0.2.14 +langchain-anthropic==0.3.0 +tavily-python==0.5.0 +langchain-community==0.3.12 +langchain-core==0.3.28 +streamlit==1.41.1 +tenacity==8.5.0 +anthropic>=0.7.0 +openai>=1.12.0 +tiktoken>=0.6.0 +pydantic>=2.0.0 +numpy>=1.24.0 +PyYAML>=6.0.0 +nest-asyncio>=1.5.0 diff --git a/rag_tutorials/deepseek_local_rag_agent/README.md b/rag_tutorials/deepseek_local_rag_agent/README.md new file mode 100644 index 0000000..cd9399c --- /dev/null +++ b/rag_tutorials/deepseek_local_rag_agent/README.md @@ -0,0 +1,84 @@ +# ๐Ÿ‹ Deepseek Local RAG Reasoning Agent + +A powerful reasoning agent that combines local Deepseek models with RAG capabilities. Built using Deepseek (via Ollama), Snowflake for embeddings, Qdrant for vector storage, and Agno for agent orchestration, this application offers both simple local chat and advanced RAG-enhanced interactions with comprehensive document processing and web search capabilities. + +## Features + +- **Dual Operation Modes** + - Local Chat Mode: Direct interaction with Deepseek locally + - RAG Mode: Enhanced reasoning with document context and web search integration - llama3.2 + +- **Document Processing** (RAG Mode) + - PDF document upload and processing + - Web page content extraction + - Automatic text chunking and embedding + - Vector storage in Qdrant cloud + +- **Intelligent Querying** (RAG Mode) + - RAG-based document retrieval + - Similarity search with threshold filtering + - Automatic fallback to web search + - Source attribution for answers + +- **Advanced Capabilities** + - Exa AI web search integration + - Custom domain filtering for web search + - Context-aware response generation + - Chat history management + - Thinking process visualization + +- **Model Specific Features** + - Flexible model selection: + - Deepseek r1 1.5b (lighter, suitable for most laptops) + - Deepseek r1 7b (more capable, requires better hardware) + - Snowflake Arctic Embedding model (SOTA) for vector embeddings + - Agno Agent framework for orchestration + - Streamlit-based interactive interface + +## Prerequisites + +### 1. Ollama Setup +1. Install [Ollama](https://ollama.ai) +2. Pull the Deepseek r1 model(s): +```bash +# For the lighter model +ollama pull deepseek-r1:1.5b + +# For the more capable model (if your hardware supports it) +ollama pull deepseek-r1:7b + +ollama pull snowflake-arctic-embed +ollama pull llama3.2 +``` + +### 2. Qdrant Cloud Setup (for RAG Mode) +1. Visit [Qdrant Cloud](https://cloud.qdrant.io/) +2. Create an account or sign in +3. Create a new cluster +4. Get your credentials: + - Qdrant API Key: Found in API Keys section + - Qdrant URL: Your cluster URL (format: `https://xxx-xxx.cloud.qdrant.io`) + +### 3. Exa AI API Key (Optional) +1. Visit [Exa AI](https://exa.ai) +2. Sign up for an account +3. Generate an API key for web search capabilities + +## How to Run + +1. Clone the repository: +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd rag_tutorials/deepseek_local_rag_agent +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +3. Run the application: +```bash +streamlit run deepseek_rag_agent.py +``` + diff --git a/rag_tutorials/deepseek_local_rag_agent/deepseek_rag_agent.py b/rag_tutorials/deepseek_local_rag_agent/deepseek_rag_agent.py new file mode 100644 index 0000000..aa54edb --- /dev/null +++ b/rag_tutorials/deepseek_local_rag_agent/deepseek_rag_agent.py @@ -0,0 +1,526 @@ +import os +import tempfile +from datetime import datetime +from typing import List +import streamlit as st +import bs4 +from agno.agent import Agent +from agno.models.ollama import Ollama +from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_qdrant import QdrantVectorStore +from qdrant_client import QdrantClient +from qdrant_client.models import Distance, VectorParams +from langchain_core.embeddings import Embeddings +from agno.tools.exa import ExaTools +from agno.embedder.ollama import OllamaEmbedder + + +class OllamaEmbedderr(Embeddings): + def __init__(self, model_name="snowflake-arctic-embed"): + """ + Initialize the OllamaEmbedderr with a specific model. + + Args: + model_name (str): The name of the model to use for embedding. + """ + self.embedder = OllamaEmbedder(id=model_name, dimensions=1024) + + def embed_documents(self, texts: List[str]) -> List[List[float]]: + return [self.embed_query(text) for text in texts] + + def embed_query(self, text: str) -> List[float]: + return self.embedder.get_embedding(text) + + +# Constants +COLLECTION_NAME = "test-deepseek-r1" + + +# Streamlit App Initialization +st.title("๐Ÿ‹ Deepseek Local RAG Reasoning Agent") + +# Session State Initialization +if 'google_api_key' not in st.session_state: + st.session_state.google_api_key = "" +if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = "" +if 'qdrant_url' not in st.session_state: + st.session_state.qdrant_url = "" +if 'model_version' not in st.session_state: + st.session_state.model_version = "deepseek-r1:1.5b" # Default to lighter model +if 'vector_store' not in st.session_state: + st.session_state.vector_store = None +if 'processed_documents' not in st.session_state: + st.session_state.processed_documents = [] +if 'history' not in st.session_state: + st.session_state.history = [] +if 'exa_api_key' not in st.session_state: + st.session_state.exa_api_key = "" +if 'use_web_search' not in st.session_state: + st.session_state.use_web_search = False +if 'force_web_search' not in st.session_state: + st.session_state.force_web_search = False +if 'similarity_threshold' not in st.session_state: + st.session_state.similarity_threshold = 0.7 +if 'rag_enabled' not in st.session_state: + st.session_state.rag_enabled = True # RAG is enabled by default + + +# Sidebar Configuration +st.sidebar.header("๐Ÿค– Agent Configuration") + +# Model Selection +st.sidebar.header("๐Ÿ“ฆ Model Selection") +model_help = """ +- 1.5b: Lighter model, suitable for most laptops +- 7b: More capable but requires better GPU/RAM + +Choose based on your hardware capabilities. +""" +st.session_state.model_version = st.sidebar.radio( + "Select Model Version", + options=["deepseek-r1:1.5b", "deepseek-r1:7b"], + help=model_help +) +st.sidebar.info("Run ollama pull deepseek-r1:7b or deepseek-r1:1.5b respectively") + +# RAG Mode Toggle +st.sidebar.header("๐Ÿ” RAG Configuration") +st.session_state.rag_enabled = st.sidebar.toggle("Enable RAG Mode", value=st.session_state.rag_enabled) + +# Clear Chat Button +if st.sidebar.button("๐Ÿ—‘๏ธ Clear Chat History"): + st.session_state.history = [] + st.rerun() + +# Show API Configuration only if RAG is enabled +if st.session_state.rag_enabled: + st.sidebar.header("๐Ÿ”‘ API Configuration") + qdrant_api_key = st.sidebar.text_input("Qdrant API Key", type="password", value=st.session_state.qdrant_api_key) + qdrant_url = st.sidebar.text_input("Qdrant URL", + placeholder="https://your-cluster.cloud.qdrant.io:6333", + value=st.session_state.qdrant_url) + + # Update session state + st.session_state.qdrant_api_key = qdrant_api_key + st.session_state.qdrant_url = qdrant_url + + # Search Configuration (only shown in RAG mode) + st.sidebar.header("๐ŸŽฏ Search Configuration") + st.session_state.similarity_threshold = st.sidebar.slider( + "Document Similarity Threshold", + min_value=0.0, + max_value=1.0, + value=0.7, + help="Lower values will return more documents but might be less relevant. Higher values are more strict." + ) + +# Add in the sidebar configuration section, after the existing API inputs + +st.sidebar.header("๐ŸŒ Web Search Configuration") +st.session_state.use_web_search = st.sidebar.checkbox("Enable Web Search Fallback", value=st.session_state.use_web_search) + +if st.session_state.use_web_search: + exa_api_key = st.sidebar.text_input( + "Exa AI API Key", + type="password", + value=st.session_state.exa_api_key, + help="Required for web search fallback when no relevant documents are found" + ) + st.session_state.exa_api_key = exa_api_key + + # Optional domain filtering + default_domains = ["arxiv.org", "wikipedia.org", "github.com", "medium.com"] + custom_domains = st.sidebar.text_input( + "Custom domains (comma-separated)", + value=",".join(default_domains), + help="Enter domains to search from, e.g.: arxiv.org,wikipedia.org" + ) + search_domains = [d.strip() for d in custom_domains.split(",") if d.strip()] + +# Search Configuration moved inside RAG mode check + + +# Utility Functions +def init_qdrant() -> QdrantClient | None: + """Initialize Qdrant client with configured settings. + + Returns: + QdrantClient: The initialized Qdrant client if successful. + None: If the initialization fails. + """ + if not all([st.session_state.qdrant_api_key, st.session_state.qdrant_url]): + return None + try: + return QdrantClient( + url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key, + timeout=60 + ) + except Exception as e: + st.error(f"๐Ÿ”ด Qdrant connection failed: {str(e)}") + return None + + +# Document Processing Functions +def process_pdf(file) -> List: + """Process PDF file and add source metadata.""" + try: + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(file.getvalue()) + loader = PyPDFLoader(tmp_file.name) + documents = loader.load() + + # Add source metadata + for doc in documents: + doc.metadata.update({ + "source_type": "pdf", + "file_name": file.name, + "timestamp": datetime.now().isoformat() + }) + + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1000, + chunk_overlap=200 + ) + return text_splitter.split_documents(documents) + except Exception as e: + st.error(f"๐Ÿ“„ PDF processing error: {str(e)}") + return [] + + +def process_web(url: str) -> List: + """Process web URL and add source metadata.""" + try: + loader = WebBaseLoader( + web_paths=(url,), + bs_kwargs=dict( + parse_only=bs4.SoupStrainer( + class_=("post-content", "post-title", "post-header", "content", "main") + ) + ) + ) + documents = loader.load() + + # Add source metadata + for doc in documents: + doc.metadata.update({ + "source_type": "url", + "url": url, + "timestamp": datetime.now().isoformat() + }) + + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1000, + chunk_overlap=200 + ) + return text_splitter.split_documents(documents) + except Exception as e: + st.error(f"๐ŸŒ Web processing error: {str(e)}") + return [] + + +# Vector Store Management +def create_vector_store(client, texts): + """Create and initialize vector store with documents.""" + try: + # Create collection if needed + try: + client.create_collection( + collection_name=COLLECTION_NAME, + vectors_config=VectorParams( + size=1024, + distance=Distance.COSINE + ) + ) + st.success(f"๐Ÿ“š Created new collection: {COLLECTION_NAME}") + except Exception as e: + if "already exists" not in str(e).lower(): + raise e + + # Initialize vector store + vector_store = QdrantVectorStore( + client=client, + collection_name=COLLECTION_NAME, + embedding=OllamaEmbedderr() + ) + + # Add documents + with st.spinner('๐Ÿ“ค Uploading documents to Qdrant...'): + vector_store.add_documents(texts) + st.success("โœ… Documents stored successfully!") + return vector_store + + except Exception as e: + st.error(f"๐Ÿ”ด Vector store error: {str(e)}") + return None + +def get_web_search_agent() -> Agent: + """Initialize a web search agent.""" + return Agent( + name="Web Search Agent", + model=Ollama(id="llama3.2"), + tools=[ExaTools( + api_key=st.session_state.exa_api_key, + include_domains=search_domains, + num_results=5 + )], + instructions="""You are a web search expert. Your task is to: + 1. Search the web for relevant information about the query + 2. Compile and summarize the most relevant information + 3. Include sources in your response + """, + show_tool_calls=True, + markdown=True, + ) + + +def get_rag_agent() -> Agent: + """Initialize the main RAG agent.""" + return Agent( + name="DeepSeek RAG Agent", + model=Ollama(id=st.session_state.model_version), + instructions="""You are an Intelligent Agent specializing in providing accurate answers. + + When asked a question: + - Analyze the question and answer the question with what you know. + + When given context from documents: + - Focus on information from the provided documents + - Be precise and cite specific details + + When given web search results: + - Clearly indicate that the information comes from web search + - Synthesize the information clearly + + Always maintain high accuracy and clarity in your responses. + """, + show_tool_calls=True, + markdown=True, + ) + + + + +def check_document_relevance(query: str, vector_store, threshold: float = 0.7) -> tuple[bool, List]: + + if not vector_store: + return False, [] + + retriever = vector_store.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={"k": 5, "score_threshold": threshold} + ) + docs = retriever.invoke(query) + return bool(docs), docs + + +chat_col, toggle_col = st.columns([0.9, 0.1]) + +with chat_col: + prompt = st.chat_input("Ask about your documents..." if st.session_state.rag_enabled else "Ask me anything...") + +with toggle_col: + st.session_state.force_web_search = st.toggle('๐ŸŒ', help="Force web search") + +# Check if RAG is enabled +if st.session_state.rag_enabled: + qdrant_client = init_qdrant() + + # File/URL Upload Section + st.sidebar.header("๐Ÿ“ Data Upload") + uploaded_file = st.sidebar.file_uploader("Upload PDF", type=["pdf"]) + web_url = st.sidebar.text_input("Or enter URL") + + # Process documents + if uploaded_file: + file_name = uploaded_file.name + if file_name not in st.session_state.processed_documents: + with st.spinner('Processing PDF...'): + texts = process_pdf(uploaded_file) + if texts and qdrant_client: + if st.session_state.vector_store: + st.session_state.vector_store.add_documents(texts) + else: + st.session_state.vector_store = create_vector_store(qdrant_client, texts) + st.session_state.processed_documents.append(file_name) + st.success(f"โœ… Added PDF: {file_name}") + + if web_url: + if web_url not in st.session_state.processed_documents: + with st.spinner('Processing URL...'): + texts = process_web(web_url) + if texts and qdrant_client: + if st.session_state.vector_store: + st.session_state.vector_store.add_documents(texts) + else: + st.session_state.vector_store = create_vector_store(qdrant_client, texts) + st.session_state.processed_documents.append(web_url) + st.success(f"โœ… Added URL: {web_url}") + + # Display sources in sidebar + if st.session_state.processed_documents: + st.sidebar.header("๐Ÿ“š Processed Sources") + for source in st.session_state.processed_documents: + if source.endswith('.pdf'): + st.sidebar.text(f"๐Ÿ“„ {source}") + else: + st.sidebar.text(f"๐ŸŒ {source}") + +if prompt: + # Add user message to history + st.session_state.history.append({"role": "user", "content": prompt}) + with st.chat_message("user"): + st.write(prompt) + + if st.session_state.rag_enabled: + + # Existing RAG flow remains unchanged + with st.spinner("๐Ÿค”Evaluating the Query..."): + try: + rewritten_query = prompt + + with st.expander("Evaluating the query"): + st.write(f"User's Prompt: {prompt}") + except Exception as e: + st.error(f"โŒ Error rewriting query: {str(e)}") + rewritten_query = prompt + + # Step 2: Choose search strategy based on force_web_search toggle + context = "" + docs = [] + if not st.session_state.force_web_search and st.session_state.vector_store: + # Try document search first + retriever = st.session_state.vector_store.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={ + "k": 5, + "score_threshold": st.session_state.similarity_threshold + } + ) + docs = retriever.invoke(rewritten_query) + if docs: + context = "\n\n".join([d.page_content for d in docs]) + st.info(f"๐Ÿ“Š Found {len(docs)} relevant documents (similarity > {st.session_state.similarity_threshold})") + elif st.session_state.use_web_search: + st.info("๐Ÿ”„ No relevant documents found in database, falling back to web search...") + + # Step 3: Use web search if: + # 1. Web search is forced ON via toggle, or + # 2. No relevant documents found AND web search is enabled in settings + if (st.session_state.force_web_search or not context) and st.session_state.use_web_search and st.session_state.exa_api_key: + with st.spinner("๐Ÿ” Searching the web..."): + try: + web_search_agent = get_web_search_agent() + web_results = web_search_agent.run(rewritten_query).content + if web_results: + context = f"Web Search Results:\n{web_results}" + if st.session_state.force_web_search: + st.info("โ„น๏ธ Using web search as requested via toggle.") + else: + st.info("โ„น๏ธ Using web search as fallback since no relevant documents were found.") + except Exception as e: + st.error(f"โŒ Web search error: {str(e)}") + + # Step 4: Generate response using the RAG agent + with st.spinner("๐Ÿค– Thinking..."): + try: + rag_agent = get_rag_agent() + + if context: + full_prompt = f"""Context: {context} + +Original Question: {prompt} +Please provide a comprehensive answer based on the available information.""" + else: + full_prompt = f"Original Question: {prompt}\n" + st.info("โ„น๏ธ No relevant information found in documents or web search.") + + response = rag_agent.run(full_prompt) + + # Add assistant response to history + st.session_state.history.append({ + "role": "assistant", + "content": response.content + }) + + # Display assistant response + with st.chat_message("assistant"): + st.write(response.content) + + # Show sources if available + if not st.session_state.force_web_search and 'docs' in locals() and docs: + with st.expander("๐Ÿ” See document sources"): + for i, doc in enumerate(docs, 1): + source_type = doc.metadata.get("source_type", "unknown") + source_icon = "๐Ÿ“„" if source_type == "pdf" else "๐ŸŒ" + source_name = doc.metadata.get("file_name" if source_type == "pdf" else "url", "unknown") + st.write(f"{source_icon} Source {i} from {source_name}:") + st.write(f"{doc.page_content[:200]}...") + + except Exception as e: + st.error(f"โŒ Error generating response: {str(e)}") + + else: + # Simple mode without RAG + with st.spinner("๐Ÿค– Thinking..."): + try: + rag_agent = get_rag_agent() + web_search_agent = get_web_search_agent() if st.session_state.use_web_search else None + + # Handle web search if forced or enabled + context = "" + if st.session_state.force_web_search and web_search_agent: + with st.spinner("๐Ÿ” Searching the web..."): + try: + web_results = web_search_agent.run(prompt).content + if web_results: + context = f"Web Search Results:\n{web_results}" + st.info("โ„น๏ธ Using web search as requested.") + except Exception as e: + st.error(f"โŒ Web search error: {str(e)}") + + # Generate response + if context: + full_prompt = f"""Context: {context} + +Question: {prompt} + +Please provide a comprehensive answer based on the available information.""" + else: + full_prompt = prompt + + response = rag_agent.run(full_prompt) + response_content = response.content + + # Extract thinking process and final response + import re + think_pattern = r'(.*?)' + think_match = re.search(think_pattern, response_content, re.DOTALL) + + if think_match: + thinking_process = think_match.group(1).strip() + final_response = re.sub(think_pattern, '', response_content, flags=re.DOTALL).strip() + else: + thinking_process = None + final_response = response_content + + # Add assistant response to history (only the final response) + st.session_state.history.append({ + "role": "assistant", + "content": final_response + }) + + # Display assistant response + with st.chat_message("assistant"): + if thinking_process: + with st.expander("๐Ÿค” See thinking process"): + st.markdown(thinking_process) + st.markdown(final_response) + + except Exception as e: + st.error(f"โŒ Error generating response: {str(e)}") + +else: + st.warning("You can directly talk to r1 locally! Toggle the RAG mode to upload documents!") \ No newline at end of file diff --git a/rag_tutorials/deepseek_local_rag_agent/requirements.txt b/rag_tutorials/deepseek_local_rag_agent/requirements.txt new file mode 100644 index 0000000..c79bb6f --- /dev/null +++ b/rag_tutorials/deepseek_local_rag_agent/requirements.txt @@ -0,0 +1,7 @@ +agno +exa==0.5.26 +qdrant-client==1.12.1 +langchain-qdrant==0.2.0 +langchain-community==0.3.13 +streamlit==1.41.1 +ollama diff --git a/rag_tutorials/gemini_agentic_rag/README.md b/rag_tutorials/gemini_agentic_rag/README.md new file mode 100644 index 0000000..b129c5f --- /dev/null +++ b/rag_tutorials/gemini_agentic_rag/README.md @@ -0,0 +1,90 @@ +# ๐Ÿค” Agentic RAG with Gemini Flash Thinking + +A RAG Agentic system built with the new Gemini 2.0 Flash Thinking model and gemini-exp-1206, Qdrant for vector storage, and Agno (phidata prev) for agent orchestration. This application features intelligent query rewriting, document processing, and web search fallback capabilities to provide comprehensive AI-powered responses. + +## Features + +- **Document Processing** + - PDF document upload and processing + - Web page content extraction + - Automatic text chunking and embedding + - Vector storage in Qdrant cloud + +- **Intelligent Querying** + - Query rewriting for better retrieval + - RAG-based document retrieval + - Similarity search with threshold filtering + - Automatic fallback to web search + - Source attribution for answers + +- **Advanced Capabilities** + - Exa AI web search integration + - Custom domain filtering for web search + - Context-aware response generation + - Chat history management + - Query reformulation agent + +- **Model Specific Features** + - Gemini Thinking 2.0 Flash for chat and reasoning + - Gemini Embedding model for vector embeddings + - Agno Agent framework for orchestration + - Streamlit-based interactive interface + +## Prerequisites + +### 1. Google API Key +1. Go to [Google AI Studio](https://aistudio.google.com/apikey) +2. Sign up or log in to your account +3. Create a new API key + +### 2. Qdrant Cloud Setup +1. Visit [Qdrant Cloud](https://cloud.qdrant.io/) +2. Create an account or sign in +3. Create a new cluster +4. Get your credentials: + - Qdrant API Key: Found in API Keys section + - Qdrant URL: Your cluster URL (format: `https://xxx-xxx.cloud.qdrant.io`) + +### 3. Exa AI API Key (Optional) +1. Visit [Exa AI](https://exa.ai) +2. Sign up for an account +3. Generate an API key for web search capabilities + +## How to Run + +1. Clone the repository: +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd rag_tutorials/gemini_agentic_rag +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +3. Run the application: +```bash +streamlit run agentic_rag_gemini.py +``` + +## Usage + +1. Configure API keys in the sidebar: + - Enter your Google API key + - Add Qdrant credentials + - (Optional) Add Exa AI key for web search + +2. Upload documents: + - Use the file uploader for PDFs + - Enter URLs for web content + +3. Ask questions: + - Type your query in the chat interface + - View rewritten queries and sources + - See web search results when relevant + +4. Manage your session: + - Clear chat history as needed + - Configure web search domains + - Monitor processed documents diff --git a/rag_tutorials/gemini_agentic_rag/agentic_rag_gemini.py b/rag_tutorials/gemini_agentic_rag/agentic_rag_gemini.py new file mode 100644 index 0000000..b27ac88 --- /dev/null +++ b/rag_tutorials/gemini_agentic_rag/agentic_rag_gemini.py @@ -0,0 +1,473 @@ +import os +import tempfile +from datetime import datetime +from typing import List + +import streamlit as st +import google.generativeai as genai +import bs4 +from agno.agent import Agent +from agno.models.google import Gemini +from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_qdrant import QdrantVectorStore +from qdrant_client import QdrantClient +from qdrant_client.models import Distance, VectorParams +from langchain_core.embeddings import Embeddings +from agno.tools.exa import ExaTools + + +class GeminiEmbedder(Embeddings): + def __init__(self, model_name="models/text-embedding-004"): + genai.configure(api_key=st.session_state.google_api_key) + self.model = model_name + + def embed_documents(self, texts: List[str]) -> List[List[float]]: + return [self.embed_query(text) for text in texts] + + def embed_query(self, text: str) -> List[float]: + response = genai.embed_content( + model=self.model, + content=text, + task_type="retrieval_document" + ) + return response['embedding'] + + +# Constants +COLLECTION_NAME = "gemini-thinking-agent-agno" + + +# Streamlit App Initialization +st.title("๐Ÿค” Agentic RAG with Gemini Thinking and Agno") + +# Session State Initialization +if 'google_api_key' not in st.session_state: + st.session_state.google_api_key = "" +if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = "" +if 'qdrant_url' not in st.session_state: + st.session_state.qdrant_url = "" +if 'vector_store' not in st.session_state: + st.session_state.vector_store = None +if 'processed_documents' not in st.session_state: + st.session_state.processed_documents = [] +if 'history' not in st.session_state: + st.session_state.history = [] +if 'exa_api_key' not in st.session_state: + st.session_state.exa_api_key = "" +if 'use_web_search' not in st.session_state: + st.session_state.use_web_search = False +if 'force_web_search' not in st.session_state: + st.session_state.force_web_search = False +if 'similarity_threshold' not in st.session_state: + st.session_state.similarity_threshold = 0.7 + + +# Sidebar Configuration +st.sidebar.header("๐Ÿ”‘ API Configuration") +google_api_key = st.sidebar.text_input("Google API Key", type="password", value=st.session_state.google_api_key) +qdrant_api_key = st.sidebar.text_input("Qdrant API Key", type="password", value=st.session_state.qdrant_api_key) +qdrant_url = st.sidebar.text_input("Qdrant URL", + placeholder="https://your-cluster.cloud.qdrant.io:6333", + value=st.session_state.qdrant_url) + +# Clear Chat Button +if st.sidebar.button("๐Ÿ—‘๏ธ Clear Chat History"): + st.session_state.history = [] + st.rerun() + +# Update session state +st.session_state.google_api_key = google_api_key +st.session_state.qdrant_api_key = qdrant_api_key +st.session_state.qdrant_url = qdrant_url + +# Add in the sidebar configuration section, after the existing API inputs +st.sidebar.header("๐ŸŒ Web Search Configuration") +st.session_state.use_web_search = st.sidebar.checkbox("Enable Web Search Fallback", value=st.session_state.use_web_search) + +if st.session_state.use_web_search: + exa_api_key = st.sidebar.text_input( + "Exa AI API Key", + type="password", + value=st.session_state.exa_api_key, + help="Required for web search fallback when no relevant documents are found" + ) + st.session_state.exa_api_key = exa_api_key + + # Optional domain filtering + default_domains = ["arxiv.org", "wikipedia.org", "github.com", "medium.com"] + custom_domains = st.sidebar.text_input( + "Custom domains (comma-separated)", + value=",".join(default_domains), + help="Enter domains to search from, e.g.: arxiv.org,wikipedia.org" + ) + search_domains = [d.strip() for d in custom_domains.split(",") if d.strip()] + +# Add this to the sidebar configuration section +st.sidebar.header("๐ŸŽฏ Search Configuration") +st.session_state.similarity_threshold = st.sidebar.slider( + "Document Similarity Threshold", + min_value=0.0, + max_value=1.0, + value=0.7, + help="Lower values will return more documents but might be less relevant. Higher values are more strict." +) + + +# Utility Functions +def init_qdrant(): + """Initialize Qdrant client with configured settings.""" + if not all([st.session_state.qdrant_api_key, st.session_state.qdrant_url]): + return None + try: + return QdrantClient( + url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key, + timeout=60 + ) + except Exception as e: + st.error(f"๐Ÿ”ด Qdrant connection failed: {str(e)}") + return None + + +# Document Processing Functions +def process_pdf(file) -> List: + """Process PDF file and add source metadata.""" + try: + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(file.getvalue()) + loader = PyPDFLoader(tmp_file.name) + documents = loader.load() + + # Add source metadata + for doc in documents: + doc.metadata.update({ + "source_type": "pdf", + "file_name": file.name, + "timestamp": datetime.now().isoformat() + }) + + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1000, + chunk_overlap=200 + ) + return text_splitter.split_documents(documents) + except Exception as e: + st.error(f"๐Ÿ“„ PDF processing error: {str(e)}") + return [] + + +def process_web(url: str) -> List: + """Process web URL and add source metadata.""" + try: + loader = WebBaseLoader( + web_paths=(url,), + bs_kwargs=dict( + parse_only=bs4.SoupStrainer( + class_=("post-content", "post-title", "post-header", "content", "main") + ) + ) + ) + documents = loader.load() + + # Add source metadata + for doc in documents: + doc.metadata.update({ + "source_type": "url", + "url": url, + "timestamp": datetime.now().isoformat() + }) + + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1000, + chunk_overlap=200 + ) + return text_splitter.split_documents(documents) + except Exception as e: + st.error(f"๐ŸŒ Web processing error: {str(e)}") + return [] + + +# Vector Store Management +def create_vector_store(client, texts): + """Create and initialize vector store with documents.""" + try: + # Create collection if needed + try: + client.create_collection( + collection_name=COLLECTION_NAME, + vectors_config=VectorParams( + size=768, # Gemini embedding-004 dimension + distance=Distance.COSINE + ) + ) + st.success(f"๐Ÿ“š Created new collection: {COLLECTION_NAME}") + except Exception as e: + if "already exists" not in str(e).lower(): + raise e + + # Initialize vector store + vector_store = QdrantVectorStore( + client=client, + collection_name=COLLECTION_NAME, + embedding=GeminiEmbedder() + ) + + # Add documents + with st.spinner('๐Ÿ“ค Uploading documents to Qdrant...'): + vector_store.add_documents(texts) + st.success("โœ… Documents stored successfully!") + return vector_store + + except Exception as e: + st.error(f"๐Ÿ”ด Vector store error: {str(e)}") + return None + + +# Add this after the GeminiEmbedder class +def get_query_rewriter_agent() -> Agent: + """Initialize a query rewriting agent.""" + return Agent( + name="Query Rewriter", + model=Gemini(id="gemini-exp-1206"), + instructions="""You are an expert at reformulating questions to be more precise and detailed. + Your task is to: + 1. Analyze the user's question + 2. Rewrite it to be more specific and search-friendly + 3. Expand any acronyms or technical terms + 4. Return ONLY the rewritten query without any additional text or explanations + + Example 1: + User: "What does it say about ML?" + Output: "What are the key concepts, techniques, and applications of Machine Learning (ML) discussed in the context?" + + Example 2: + User: "Tell me about transformers" + Output: "Explain the architecture, mechanisms, and applications of Transformer neural networks in natural language processing and deep learning" + """, + show_tool_calls=False, + markdown=True, + ) + + +def get_web_search_agent() -> Agent: + """Initialize a web search agent.""" + return Agent( + name="Web Search Agent", + model=Gemini(id="gemini-exp-1206"), + tools=[ExaTools( + api_key=st.session_state.exa_api_key, + include_domains=search_domains, + num_results=5 + )], + instructions="""You are a web search expert. Your task is to: + 1. Search the web for relevant information about the query + 2. Compile and summarize the most relevant information + 3. Include sources in your response + """, + show_tool_calls=True, + markdown=True, + ) + + +def get_rag_agent() -> Agent: + """Initialize the main RAG agent.""" + return Agent( + name="Gemini RAG Agent", + model=Gemini(id="gemini-2.0-flash-thinking-exp-01-21"), + instructions="""You are an Intelligent Agent specializing in providing accurate answers. + + When given context from documents: + - Focus on information from the provided documents + - Be precise and cite specific details + + When given web search results: + - Clearly indicate that the information comes from web search + - Synthesize the information clearly + + Always maintain high accuracy and clarity in your responses. + """, + show_tool_calls=True, + markdown=True, + ) + + +def check_document_relevance(query: str, vector_store, threshold: float = 0.7) -> tuple[bool, List]: + """ + Check if documents in vector store are relevant to the query. + + Args: + query: The search query + vector_store: The vector store to search in + threshold: Similarity threshold + + Returns: + tuple[bool, List]: (has_relevant_docs, relevant_docs) + """ + if not vector_store: + return False, [] + + retriever = vector_store.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={"k": 5, "score_threshold": threshold} + ) + docs = retriever.invoke(query) + return bool(docs), docs + + +# Main Application Flow +if st.session_state.google_api_key: + os.environ["GOOGLE_API_KEY"] = st.session_state.google_api_key + genai.configure(api_key=st.session_state.google_api_key) + + qdrant_client = init_qdrant() + + # File/URL Upload Section + st.sidebar.header("๐Ÿ“ Data Upload") + uploaded_file = st.sidebar.file_uploader("Upload PDF", type=["pdf"]) + web_url = st.sidebar.text_input("Or enter URL") + + # Process documents + if uploaded_file: + file_name = uploaded_file.name + if file_name not in st.session_state.processed_documents: + with st.spinner('Processing PDF...'): + texts = process_pdf(uploaded_file) + if texts and qdrant_client: + if st.session_state.vector_store: + st.session_state.vector_store.add_documents(texts) + else: + st.session_state.vector_store = create_vector_store(qdrant_client, texts) + st.session_state.processed_documents.append(file_name) + st.success(f"โœ… Added PDF: {file_name}") + + if web_url: + if web_url not in st.session_state.processed_documents: + with st.spinner('Processing URL...'): + texts = process_web(web_url) + if texts and qdrant_client: + if st.session_state.vector_store: + st.session_state.vector_store.add_documents(texts) + else: + st.session_state.vector_store = create_vector_store(qdrant_client, texts) + st.session_state.processed_documents.append(web_url) + st.success(f"โœ… Added URL: {web_url}") + + # Display sources in sidebar + if st.session_state.processed_documents: + st.sidebar.header("๐Ÿ“š Processed Sources") + for source in st.session_state.processed_documents: + if source.endswith('.pdf'): + st.sidebar.text(f"๐Ÿ“„ {source}") + else: + st.sidebar.text(f"๐ŸŒ {source}") + + # Chat Interface + # Create two columns for chat input and search toggle + chat_col, toggle_col = st.columns([0.9, 0.1]) + + with chat_col: + prompt = st.chat_input("Ask about your documents...") + + with toggle_col: + st.session_state.force_web_search = st.toggle('๐ŸŒ', help="Force web search") + + if prompt: + # Add user message to history + st.session_state.history.append({"role": "user", "content": prompt}) + with st.chat_message("user"): + st.write(prompt) + + # Step 1: Rewrite the query for better retrieval + with st.spinner("๐Ÿค” Reformulating query..."): + try: + query_rewriter = get_query_rewriter_agent() + rewritten_query = query_rewriter.run(prompt).content + + with st.expander("๐Ÿ”„ See rewritten query"): + st.write(f"Original: {prompt}") + st.write(f"Rewritten: {rewritten_query}") + except Exception as e: + st.error(f"โŒ Error rewriting query: {str(e)}") + rewritten_query = prompt + + # Step 2: Choose search strategy based on force_web_search toggle + context = "" + docs = [] + if not st.session_state.force_web_search and st.session_state.vector_store: + # Try document search first + retriever = st.session_state.vector_store.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={ + "k": 5, + "score_threshold": st.session_state.similarity_threshold + } + ) + docs = retriever.invoke(rewritten_query) + if docs: + context = "\n\n".join([d.page_content for d in docs]) + st.info(f"๐Ÿ“Š Found {len(docs)} relevant documents (similarity > {st.session_state.similarity_threshold})") + elif st.session_state.use_web_search: + st.info("๐Ÿ”„ No relevant documents found in database, falling back to web search...") + + # Step 3: Use web search if: + # 1. Web search is forced ON via toggle, or + # 2. No relevant documents found AND web search is enabled in settings + if (st.session_state.force_web_search or not context) and st.session_state.use_web_search and st.session_state.exa_api_key: + with st.spinner("๐Ÿ” Searching the web..."): + try: + web_search_agent = get_web_search_agent() + web_results = web_search_agent.run(rewritten_query).content + if web_results: + context = f"Web Search Results:\n{web_results}" + if st.session_state.force_web_search: + st.info("โ„น๏ธ Using web search as requested via toggle.") + else: + st.info("โ„น๏ธ Using web search as fallback since no relevant documents were found.") + except Exception as e: + st.error(f"โŒ Web search error: {str(e)}") + + # Step 4: Generate response using the RAG agent + with st.spinner("๐Ÿค– Thinking..."): + try: + rag_agent = get_rag_agent() + + if context: + full_prompt = f"""Context: {context} + +Original Question: {prompt} +Rewritten Question: {rewritten_query} + +Please provide a comprehensive answer based on the available information.""" + else: + full_prompt = f"Original Question: {prompt}\nRewritten Question: {rewritten_query}" + st.info("โ„น๏ธ No relevant information found in documents or web search.") + + response = rag_agent.run(full_prompt) + + # Add assistant response to history + st.session_state.history.append({ + "role": "assistant", + "content": response.content + }) + + # Display assistant response + with st.chat_message("assistant"): + st.write(response.content) + + # Show sources if available + if not st.session_state.force_web_search and 'docs' in locals() and docs: + with st.expander("๐Ÿ” See document sources"): + for i, doc in enumerate(docs, 1): + source_type = doc.metadata.get("source_type", "unknown") + source_icon = "๐Ÿ“„" if source_type == "pdf" else "๐ŸŒ" + source_name = doc.metadata.get("file_name" if source_type == "pdf" else "url", "unknown") + st.write(f"{source_icon} Source {i} from {source_name}:") + st.write(f"{doc.page_content[:200]}...") + + except Exception as e: + st.error(f"โŒ Error generating response: {str(e)}") + +else: + st.warning("โš ๏ธ Please enter your Google API Key to continue") \ No newline at end of file diff --git a/rag_tutorials/gemini_agentic_rag/requirements.txt b/rag_tutorials/gemini_agentic_rag/requirements.txt new file mode 100644 index 0000000..1ac2d62 --- /dev/null +++ b/rag_tutorials/gemini_agentic_rag/requirements.txt @@ -0,0 +1,6 @@ +agno +exa==0.5.26 +qdrant-client==1.12.1 +langchain-qdrant==0.2.0 +langchain-community==0.3.13 +streamlit==1.41.1 \ No newline at end of file diff --git a/rag_tutorials/hybrid_search_rag/README.md b/rag_tutorials/hybrid_search_rag/README.md new file mode 100644 index 0000000..15412e0 --- /dev/null +++ b/rag_tutorials/hybrid_search_rag/README.md @@ -0,0 +1,93 @@ +# ๐Ÿ‘€ RAG App with Hybrid Search + +A powerful document Q&A application that leverages Hybrid Search (RAG) and Claude's advanced language capabilities to provide comprehensive answers. Built with RAGLite for robust document processing and retrieval, and Streamlit for an intuitive chat interface, this system seamlessly combines document-specific knowledge with Claude's general intelligence to deliver accurate and contextual responses. + +## Features + +- **Hybrid Search Question Answering** + - RAG-based answers for document-specific queries + - Fallback to Claude for general knowledge questions + +- **Document Processing**: + - PDF document upload and processing + - Automatic text chunking and embedding + - Hybrid search combining semantic and keyword matching + - Reranking for better context selection + +- **Multi-Model Integration**: + - Claude for text generation - tested with Claude 3 Opus + - OpenAI for embeddings - tested with text-embedding-3-large + - Cohere for reranking - tested with Cohere 3.5 reranker + +## Prerequisites + +You'll need the following API keys and database setup: + +1. **Database**: Create a free PostgreSQL database at [Neon](https://neon.tech): + - Sign up/Login at Neon + - Create a new project + - Copy the connection string (looks like: `postgresql://user:pass@ep-xyz.region.aws.neon.tech/dbname`) + +2. **API Keys**: + - [OpenAI API key](https://platform.openai.com/api-keys) for embeddings + - [Anthropic API key](https://console.anthropic.com/settings/keys) for Claude + - [Cohere API key](https://dashboard.cohere.com/api-keys) for reranking + +## How to get Started? + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/rag_tutorials/hybrid_search_rag + ``` + +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Install spaCy Model**: + ```bash + pip install https://github.com/explosion/spacy-models/releases/download/xx_sent_ud_sm-3.7.0/xx_sent_ud_sm-3.7.0-py3-none-any.whl + ``` + +4. **Run the Application**: + ```bash + streamlit run main.py + ``` + +## Usage + +1. Start the application +2. Enter your API keys in the sidebar: + - OpenAI API key + - Anthropic API key + - Cohere API key + - Database URL (optional, defaults to SQLite) +3. Click "Save Configuration" +4. Upload PDF documents +5. Start asking questions! + - Document-specific questions will use RAG + - General questions will use Claude directly + +## Database Options + +The application supports multiple database backends: + +- **PostgreSQL** (Recommended): + - Create a free serverless PostgreSQL database at [Neon](https://neon.tech) + - Get instant provisioning and scale-to-zero capability + - Connection string format: `postgresql://user:pass@ep-xyz.region.aws.neon.tech/dbname` + +- **MySQL**: + ``` + mysql://user:pass@host:port/db + ``` +- **SQLite** (Local development): + ``` + sqlite:///path/to/db.sqlite + ``` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/rag_tutorials/hybrid_search_rag/main.py b/rag_tutorials/hybrid_search_rag/main.py new file mode 100644 index 0000000..07af8a0 --- /dev/null +++ b/rag_tutorials/hybrid_search_rag/main.py @@ -0,0 +1,215 @@ +import os +import logging +import streamlit as st +from raglite import RAGLiteConfig, insert_document, hybrid_search, retrieve_chunks, rerank_chunks, rag +from rerankers import Reranker +from typing import List +from pathlib import Path +import anthropic +import time +import warnings + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +warnings.filterwarnings("ignore", message=".*torch.classes.*") + +RAG_SYSTEM_PROMPT = """ +You are a friendly and knowledgeable assistant that provides complete and insightful answers. +Answer the user's question using only the context below. +When responding, you MUST NOT reference the existence of the context, directly or indirectly. +Instead, you MUST treat the context as if its contents are entirely part of your working memory. +""".strip() + +def initialize_config(openai_key: str, anthropic_key: str, cohere_key: str, db_url: str) -> RAGLiteConfig: + """Initializes and returns a RAGLiteConfig object with the specified API keys and database URL. + + This function sets the provided API keys in the environment variables and returns a + RAGLiteConfig object configured with the given database URL and pre-defined settings for + language model, embedder, and reranker. + + Args: + openai_key (str): The API key for OpenAI services. + anthropic_key (str): The API key for Anthropic services. + cohere_key (str): The API key for Cohere services. + db_url (str): The database URL for connecting to the desired data source. + + Returns: + RAGLiteConfig: A configuration object initialized with the specified parameters. + + Raises: + ValueError: If there is an issue setting up the configuration, an error is raised with details.""" + try: + os.environ["OPENAI_API_KEY"] = openai_key + os.environ["ANTHROPIC_API_KEY"] = anthropic_key + os.environ["COHERE_API_KEY"] = cohere_key + + return RAGLiteConfig( + db_url=db_url, + llm="claude-3-opus-20240229", + embedder="text-embedding-3-large", + embedder_normalize=True, + chunk_max_size=2000, + embedder_sentence_window_size=2, + reranker=Reranker("cohere", api_key=cohere_key, lang="en") + ) + except Exception as e: + raise ValueError(f"Configuration error: {e}") + +def process_document(file_path: str) -> bool: + """Processes a document by inserting it into a system with a given configuration. + + This function checks if a configuration is initialized in the session state. + If the configuration is present, it attempts to insert the document located + at the given file path using this configuration. + + Args: + file_path (str): The path to the document to be processed. + + Returns: + bool: True if the document was successfully processed; False otherwise.""" + try: + if not st.session_state.get('my_config'): + raise ValueError("Configuration not initialized") + insert_document(Path(file_path), config=st.session_state.my_config) + return True + except Exception as e: + logger.error(f"Error processing document: {str(e)}") + return False + +def perform_search(query: str) -> List[dict]: + """Conducts a hybrid search and returns a list of ranked chunks based on the query. + + This function performs a search using a hybrid search method, retrieves the relevant + chunks, and reranks them according to the query. It handles any exceptions that occur + during the process and logs the errors. + + Args: + query (str): The search query string. + + Returns: + List[dict]: A list of dictionaries representing the ranked chunks. Returns an + empty list if no results are found or if an error occurs.""" + try: + chunk_ids, scores = hybrid_search(query, num_results=10, config=st.session_state.my_config) + if not chunk_ids: + return [] + chunks = retrieve_chunks(chunk_ids, config=st.session_state.my_config) + return rerank_chunks(query, chunks, config=st.session_state.my_config) + except Exception as e: + logger.error(f"Search error: {str(e)}") + return [] + +def handle_fallback(query: str) -> str: + try: + client = anthropic.Anthropic(api_key=st.session_state.user_env["ANTHROPIC_API_KEY"]) + system_prompt = """You are a helpful AI assistant. When you don't know something, + be honest about it. Provide clear, concise, and accurate responses. If the question + is not related to any specific document, use your general knowledge to answer.""" + + message = client.messages.create( + model="claude-3-sonnet-20240229", + max_tokens=1024, + system=system_prompt, + messages=[{"role": "user", "content": query}], + temperature=0.7 + ) + return message.content[0].text + except Exception as e: + logger.error(f"Fallback error: {str(e)}") + st.error(f"Fallback error: {str(e)}") # Show error in UI + return "I apologize, but I encountered an error while processing your request. Please try again." + +def main(): + st.set_page_config(page_title="LLM-Powered Hybrid Search-RAG Assistant", layout="wide") + + for state_var in ['chat_history', 'documents_loaded', 'my_config', 'user_env']: + if state_var not in st.session_state: + st.session_state[state_var] = [] if state_var == 'chat_history' else False if state_var == 'documents_loaded' else None if state_var == 'my_config' else {} + + with st.sidebar: + st.title("Configuration") + openai_key = st.text_input("OpenAI API Key", value=st.session_state.get('openai_key', ''), type="password", placeholder="sk-...") + anthropic_key = st.text_input("Anthropic API Key", value=st.session_state.get('anthropic_key', ''), type="password", placeholder="sk-ant-...") + cohere_key = st.text_input("Cohere API Key", value=st.session_state.get('cohere_key', ''), type="password", placeholder="Enter Cohere key") + db_url = st.text_input("Database URL", value=st.session_state.get('db_url', 'sqlite:///raglite.sqlite'), placeholder="sqlite:///raglite.sqlite") + + if st.button("Save Configuration"): + try: + if not all([openai_key, anthropic_key, cohere_key, db_url]): + st.error("All fields are required!") + return + + for key, value in {'openai_key': openai_key, 'anthropic_key': anthropic_key, 'cohere_key': cohere_key, 'db_url': db_url}.items(): + st.session_state[key] = value + + st.session_state.my_config = initialize_config(openai_key=openai_key, anthropic_key=anthropic_key, cohere_key=cohere_key, db_url=db_url) + st.session_state.user_env = {"ANTHROPIC_API_KEY": anthropic_key} + st.success("Configuration saved successfully!") + except Exception as e: + st.error(f"Configuration error: {str(e)}") + + st.title("๐Ÿ‘€ RAG App with Hybrid Search") + + if st.session_state.my_config: + uploaded_files = st.file_uploader("Upload PDF documents", type=["pdf"], accept_multiple_files=True, key="pdf_uploader") + + if uploaded_files: + success = False + for uploaded_file in uploaded_files: + with st.spinner(f"Processing {uploaded_file.name}..."): + temp_path = f"temp_{uploaded_file.name}" + with open(temp_path, "wb") as f: + f.write(uploaded_file.getvalue()) + + if process_document(temp_path): + st.success(f"Successfully processed: {uploaded_file.name}") + success = True + else: + st.error(f"Failed to process: {uploaded_file.name}") + os.remove(temp_path) + + if success: + st.session_state.documents_loaded = True + st.success("Documents are ready! You can now ask questions about them.") + + if st.session_state.documents_loaded: + for msg in st.session_state.chat_history: + with st.chat_message("user"): st.write(msg[0]) + with st.chat_message("assistant"): st.write(msg[1]) + + user_input = st.chat_input("Ask a question about the documents...") + if user_input: + with st.chat_message("user"): st.write(user_input) + with st.chat_message("assistant"): + message_placeholder = st.empty() + try: + reranked_chunks = perform_search(query=user_input) + if not reranked_chunks or len(reranked_chunks) == 0: + logger.info("No relevant documents found. Falling back to Claude.") + st.info("No relevant documents found. Using general knowledge to answer.") + full_response = handle_fallback(user_input) + else: + formatted_messages = [{"role": "user" if i % 2 == 0 else "assistant", "content": msg} + for i, msg in enumerate([m for pair in st.session_state.chat_history for m in pair]) if msg] + + response_stream = rag(prompt=user_input, + system_prompt=RAG_SYSTEM_PROMPT, + search=hybrid_search, + messages=formatted_messages, + max_contexts=5, + config=st.session_state.my_config) + + full_response = "" + for chunk in response_stream: + full_response += chunk + message_placeholder.markdown(full_response + "โ–Œ") + + message_placeholder.markdown(full_response) + st.session_state.chat_history.append((user_input, full_response)) + except Exception as e: + st.error(f"Error: {str(e)}") + else: + st.info("Please configure your API keys and upload documents to get started." if not st.session_state.my_config else "Please upload some documents to get started.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rag_tutorials/hybrid_search_rag/requirements.txt b/rag_tutorials/hybrid_search_rag/requirements.txt new file mode 100644 index 0000000..2c1f138 --- /dev/null +++ b/rag_tutorials/hybrid_search_rag/requirements.txt @@ -0,0 +1,12 @@ +raglite==0.2.1 +pydantic==2.10.1 +sqlalchemy>=2.0.0 +psycopg2-binary>=2.9.9 +openai>=1.0.0 +cohere>=4.37 +pypdf>=3.0.0 +python-dotenv>=1.0.0 +rerankers==0.6.0 +spacy>=3.7.0 +streamlit +anthropic diff --git a/rag_tutorials/llama3.1_local_rag/README.md b/rag_tutorials/llama3.1_local_rag/README.md index 9d6dd45..c4baca2 100644 --- a/rag_tutorials/llama3.1_local_rag/README.md +++ b/rag_tutorials/llama3.1_local_rag/README.md @@ -13,6 +13,7 @@ Streamlit app that allows you to chat with any webpage using local Llama-3.1 and ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd awesome-llm-apps/rag_tutorials/llama3.1_local_rag ``` 2. Install the required dependencies: diff --git a/rag_tutorials/llama3.1_local_rag/llama3.1_local_rag.py b/rag_tutorials/llama3.1_local_rag/llama3.1_local_rag.py index b7bfdad..f17a570 100644 --- a/rag_tutorials/llama3.1_local_rag/llama3.1_local_rag.py +++ b/rag_tutorials/llama3.1_local_rag/llama3.1_local_rag.py @@ -1,15 +1,19 @@ import streamlit as st -import ollama from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import WebBaseLoader from langchain_community.vectorstores import Chroma -from langchain_community.embeddings import OllamaEmbeddings +from langchain_ollama import OllamaEmbeddings +from langchain_ollama import ChatOllama st.title("Chat with Webpage ๐ŸŒ") st.caption("This app allows you to chat with a webpage using local llama3 and RAG") # Get the webpage URL from the user webpage_url = st.text_input("Enter Webpage URL", type="default") +# Connect to Ollama +ollama_endpoint = "http://127.0.0.1:11434" +ollama_model = "llama3.1" +ollama = ChatOllama(model=ollama_model, base_url=ollama_endpoint) if webpage_url: # 1. Load the data @@ -19,22 +23,48 @@ if webpage_url: splits = text_splitter.split_documents(docs) # 2. Create Ollama embeddings and vector store - embeddings = OllamaEmbeddings(model="llama3.1") + embeddings = OllamaEmbeddings(model=ollama_model, base_url=ollama_endpoint) vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings) # 3. Call Ollama Llama3 model def ollama_llm(question, context): + """Generates a response to a question using the Ollama Llama3 model. + + This function takes a question and its context, formats them into a prompt, + and invokes the Ollama Llama3 model to generate a response. + + Args: + question (str): The question to be answered by the model. + context (str): The context or additional information related to the question. + + Returns: + str: The response generated by the Ollama Llama3 model, stripped of leading and trailing whitespace.""" formatted_prompt = f"Question: {question}\n\nContext: {context}" - response = ollama.chat(model='llama3.1', messages=[{'role': 'user', 'content': formatted_prompt}]) - return response['message']['content'] + response = ollama.invoke([('human', formatted_prompt)]) + return response.content.strip() # 4. RAG Setup retriever = vectorstore.as_retriever() def combine_docs(docs): + """Combines the content of multiple document objects into a single string. + + Args: + docs (list): A list of document objects, each having a 'page_content' attribute. + + Returns: + str: A string consisting of the combined 'page_content' of all document objects, + separated by two newline characters.""" return "\n\n".join(doc.page_content for doc in docs) def rag_chain(question): + """Processes a question to retrieve and format relevant documents, and generates a response using a language model. + + Args: + question (str): The question or query that needs to be answered. + + Returns: + str: The response generated by the language model based on the retrieved and formatted documents.""" retrieved_docs = retriever.invoke(question) formatted_context = combine_docs(retrieved_docs) return ollama_llm(question, formatted_context) @@ -47,4 +77,4 @@ if webpage_url: # Chat with the webpage if prompt: result = rag_chain(prompt) - st.write(result) \ No newline at end of file + st.write(result) diff --git a/rag_tutorials/llama3.1_local_rag/requirements.txt b/rag_tutorials/llama3.1_local_rag/requirements.txt index 2824430..b2b7614 100644 --- a/rag_tutorials/llama3.1_local_rag/requirements.txt +++ b/rag_tutorials/llama3.1_local_rag/requirements.txt @@ -1,4 +1,5 @@ streamlit ollama langchain -langchain_community \ No newline at end of file +langchain_community +langchain_ollama diff --git a/rag_tutorials/local_hybrid_search_rag/README.md b/rag_tutorials/local_hybrid_search_rag/README.md new file mode 100644 index 0000000..f7a0f86 --- /dev/null +++ b/rag_tutorials/local_hybrid_search_rag/README.md @@ -0,0 +1,134 @@ +# ๐Ÿ–ฅ๏ธ Local RAG App with Hybrid Search + +A powerful document Q&A application that leverages Hybrid Search (RAG) and local LLMs for comprehensive answers. Built with RAGLite for robust document processing and retrieval, and Streamlit for an intuitive chat interface, this system combines document-specific knowledge with local LLM capabilities to deliver accurate and contextual responses. + +## Demo: + + +https://github.com/user-attachments/assets/375da089-1ab9-4bf4-b6f3-733f44e47403 + + +## Quick Start + +For immediate testing, use these tested model configurations: +```bash +# LLM Model +bartowski/Llama-3.2-3B-Instruct-GGUF/Llama-3.2-3B-Instruct-Q4_K_M.gguf@4096 + +# Embedder Model +lm-kit/bge-m3-gguf/bge-m3-Q4_K_M.gguf@1024 +``` +These models offer a good balance of performance and resource usage, and have been verified to work well together even on a MacBook Air M2 with 8GB RAM. + +## Features + +- **Local LLM Integration**: + - Uses llama-cpp-python models for local inference + - Supports various quantization formats (Q4_K_M recommended) + - Configurable context window sizes + +- **Document Processing**: + - PDF document upload and processing + - Automatic text chunking and embedding + - Hybrid search combining semantic and keyword matching + - Reranking for better context selection + +- **Multi-Model Integration**: + - Local LLM for text generation (e.g., Llama-3.2-3B-Instruct) + - Local embeddings using BGE models + - FlashRank for local reranking + +## Prerequisites + +1. **Install spaCy Model**: + ```bash + pip install https://github.com/explosion/spacy-models/releases/download/xx_sent_ud_sm-3.7.0/xx_sent_ud_sm-3.7.0-py3-none-any.whl + ``` + +2. **Install Accelerated llama-cpp-python** (Optional but recommended): + ```bash + # Configure installation variables + LLAMA_CPP_PYTHON_VERSION=0.3.2 + PYTHON_VERSION=310 # 3.10, 3.11, 3.12 + ACCELERATOR=metal # For Mac + # ACCELERATOR=cu121 # For NVIDIA GPU + PLATFORM=macosx_11_0_arm64 # For Mac + # PLATFORM=linux_x86_64 # For Linux + # PLATFORM=win_amd64 # For Windows + + # Install accelerated version + pip install "https://github.com/abetlen/llama-cpp-python/releases/download/v$LLAMA_CPP_PYTHON_VERSION-$ACCELERATOR/llama_cpp_python-$LLAMA_CPP_PYTHON_VERSION-cp$PYTHON_VERSION-cp$PYTHON_VERSION-$PLATFORM.whl" + ``` + +3. **Install Dependencies**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd awesome-llm-apps/rag_tutorials/local_hybrid_search_rag + pip install -r requirements.txt + ``` + +## Model Setup + +RAGLite extends LiteLLM with support for llama.cpp models using llama-cpp-python. To select a llama.cpp model (e.g., from bartowski's collection), use a model identifier of the form "llama-cpp-python//@", where n_ctx is an optional parameter that specifies the context size of the model. + +1. **LLM Model Path Format**: + ``` + llama-cpp-python///@ + ``` + Example: + ``` + bartowski/Llama-3.2-3B-Instruct-GGUF/Llama-3.2-3B-Instruct-Q4_K_M.gguf@4096 + ``` + +2. **Embedder Model Path Format**: + ``` + llama-cpp-python///@ + ``` + Example: + ``` + lm-kit/bge-m3-gguf/bge-m3-Q4_K_M.gguf@1024 + ``` + +## Database Setup + +The application supports multiple database backends: + +- **PostgreSQL** (Recommended): + - Create a free serverless PostgreSQL database at [Neon](https://neon.tech) in a few clicks + - Get instant provisioning and scale-to-zero capability + - Connection string format: `postgresql://user:pass@ep-xyz.region.aws.neon.tech/dbname` + + +## How to Run + +1. **Start the Application**: + ```bash + streamlit run local_main.py + ``` + +2. **Configure the Application**: + - Enter LLM model path + - Enter embedder model path + - Set database URL + - Click "Save Configuration" + +3. **Upload Documents**: + - Upload PDF files through the interface + - Wait for processing completion + +4. **Start Chatting**: + - Ask questions about your documents + - Get responses using local LLM + - Fallback to general knowledge when needed + +## Notes + +- Context window size of 4096 is recommended for most use cases +- Q4_K_M quantization offers good balance of speed and quality +- BGE-M3 embedder with 1024 dimensions is optimal +- Local models require sufficient RAM and CPU/GPU resources +- Metal acceleration available for Mac, CUDA for NVIDIA GPUs + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/rag_tutorials/local_hybrid_search_rag/local_main.py b/rag_tutorials/local_hybrid_search_rag/local_main.py new file mode 100644 index 0000000..649da96 --- /dev/null +++ b/rag_tutorials/local_hybrid_search_rag/local_main.py @@ -0,0 +1,257 @@ +import os +import logging +import streamlit as st +from raglite import RAGLiteConfig, insert_document, hybrid_search, retrieve_chunks, rerank_chunks, rag +from rerankers import Reranker +from typing import List, Dict, Any +from pathlib import Path +import time +import warnings + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +warnings.filterwarnings("ignore", message=".*torch.classes.*") + +RAG_SYSTEM_PROMPT = """ +You are a friendly and knowledgeable assistant that provides complete and insightful answers. +Answer the user's question using only the context below. +When responding, you MUST NOT reference the existence of the context, directly or indirectly. +Instead, you MUST treat the context as if its contents are entirely part of your working memory. +""".strip() + +def initialize_config(settings: Dict[str, Any]) -> RAGLiteConfig: + """Initializes and returns a RAGLiteConfig object based on provided settings. + + This function constructs a RAGLiteConfig object using the database URL, + language model path, and embedder path specified in the `settings` dictionary. + The configuration includes default options for embedder normalization and + chunk size. A reranker is also initialized with a predefined model. + + Args: + settings (Dict[str, Any]): A dictionary containing configuration + parameters. Expected keys are 'DBUrl', 'LLMPath', and 'EmbedderPath'. + + Returns: + RAGLiteConfig: An initialized configuration object for RAGLite. + + Raises: + ValueError: If there is an error in the configuration process, such as + missing keys or invalid values in the settings dictionary.""" + try: + return RAGLiteConfig( + db_url=settings["DBUrl"], + llm=f"llama-cpp-python/{settings['LLMPath']}", + embedder=f"llama-cpp-python/{settings['EmbedderPath']}", + embedder_normalize=True, + chunk_max_size=512, + reranker=Reranker("ms-marco-MiniLM-L-12-v2", model_type="flashrank") + ) + except Exception as e: + raise ValueError(f"Configuration error: {e}") + +def process_document(file_path: str) -> bool: + """Processes a document by inserting it into a system with a given configuration. + + This function attempts to insert a document specified by the file path into + a system using a predefined configuration stored in the session state. It + logs an error if the operation fails. + + Args: + file_path (str): The path to the document file that needs to be processed. + + Returns: + bool: True if the document is successfully processed; False if an error occurs.""" + try: + if not st.session_state.get('my_config'): + raise ValueError("Configuration not initialized") + insert_document(Path(file_path), config=st.session_state.my_config) + return True + except Exception as e: + logger.error(f"Error processing document: {str(e)}") + return False + +def perform_search(query: str) -> List[dict]: + """Conducts a hybrid search and returns reranked results. + + This function performs a hybrid search using the provided query and + attempts to retrieve and rerank relevant chunks. It returns a list of + reranked search results. + + Args: + query (str): The search query string. + + Returns: + List[dict]: A list of dictionaries containing reranked search results. + Returns an empty list if no results are found or if an error occurs.""" + try: + chunk_ids, scores = hybrid_search(query, num_results=10, config=st.session_state.my_config) + if not chunk_ids: + return [] + chunks = retrieve_chunks(chunk_ids, config=st.session_state.my_config) + return rerank_chunks(query, chunks, config=st.session_state.my_config) + except Exception as e: + logger.error(f"Search error: {str(e)}") + return [] + +def handle_fallback(query: str) -> str: + try: + system_prompt = """You are a helpful AI assistant. When you don't know something, + be honest about it. Provide clear, concise, and accurate responses.""" + + response_stream = rag( + prompt=query, + system_prompt=system_prompt, + search=None, + messages=[], + max_tokens=1024, + temperature=0.7, + config=st.session_state.my_config + ) + + full_response = "" + for chunk in response_stream: + full_response += chunk + + if not full_response.strip(): + return "I apologize, but I couldn't generate a response. Please try rephrasing your question." + + return full_response + + except Exception as e: + logger.error(f"Fallback error: {str(e)}") + return "I apologize, but I encountered an error while processing your request. Please try again." + +def main(): + st.set_page_config(page_title="Local LLM-Powered Hybrid Search-RAG Assistant", layout="wide") + + for state_var in ['chat_history', 'documents_loaded', 'my_config']: + if state_var not in st.session_state: + st.session_state[state_var] = [] if state_var == 'chat_history' else False if state_var == 'documents_loaded' else None + + with st.sidebar: + st.title("Configuration") + + llm_path = st.text_input( + "LLM Model Path", + value=st.session_state.get('llm_path', ''), + placeholder="TheBloke/Llama-2-7B-Chat-GGUF/llama-2-7b-chat.Q4_K_M.gguf@4096", + help="Path to your local LLM model in GGUF format" + ) + + embedder_path = st.text_input( + "Embedder Model Path", + value=st.session_state.get('embedder_path', ''), + placeholder="lm-kit/bge-m3-gguf/bge-m3-Q4_K_M.gguf@1024", + help="Path to your local embedding model in GGUF format" + ) + + db_url = st.text_input( + "Database URL", + value=st.session_state.get('db_url', ''), + placeholder="postgresql://user:pass@host:port/db", + help="Database connection URL" + ) + + if st.button("Save Configuration"): + try: + if not all([llm_path, embedder_path, db_url]): + st.error("All fields are required!") + return + + settings = { + "LLMPath": llm_path, + "EmbedderPath": embedder_path, + "DBUrl": db_url + } + + st.session_state.my_config = initialize_config(settings) + st.success("Configuration saved successfully!") + + except Exception as e: + st.error(f"Configuration error: {str(e)}") + + st.title("๐Ÿ–ฅ๏ธ Local RAG App with Hybrid Search") + + if st.session_state.my_config: + uploaded_files = st.file_uploader( + "Upload PDF documents", + type=["pdf"], + accept_multiple_files=True, + key="pdf_uploader" + ) + + if uploaded_files: + success = False + for uploaded_file in uploaded_files: + with st.spinner(f"Processing {uploaded_file.name}..."): + temp_path = f"temp_{uploaded_file.name}" + with open(temp_path, "wb") as f: + f.write(uploaded_file.getvalue()) + + if process_document(temp_path): + st.success(f"Successfully processed: {uploaded_file.name}") + success = True + else: + st.error(f"Failed to process: {uploaded_file.name}") + os.remove(temp_path) + + if success: + st.session_state.documents_loaded = True + st.success("Documents are ready! You can now ask questions about them.") + + if st.session_state.documents_loaded: + for msg in st.session_state.chat_history: + with st.chat_message("user"): st.write(msg[0]) + with st.chat_message("assistant"): st.write(msg[1]) + + user_input = st.chat_input("Ask a question about the documents...") + if user_input: + with st.chat_message("user"): st.write(user_input) + with st.chat_message("assistant"): + message_placeholder = st.empty() + try: + reranked_chunks = perform_search(query=user_input) + if not reranked_chunks or len(reranked_chunks) == 0: + logger.info("No relevant documents found. Falling back to local LLM.") + with st.spinner("Using general knowledge to answer..."): + full_response = handle_fallback(user_input) + if full_response.startswith("I apologize"): + st.warning("No relevant documents found and fallback failed.") + else: + st.info("Answering from general knowledge.") + else: + formatted_messages = [ + {"role": "user" if i % 2 == 0 else "assistant", "content": msg} + for i, msg in enumerate([m for pair in st.session_state.chat_history for m in pair]) + if msg + ] + + response_stream = rag( + prompt=user_input, + system_prompt=RAG_SYSTEM_PROMPT, + search=hybrid_search, + messages=formatted_messages, + max_contexts=5, + config=st.session_state.my_config + ) + + full_response = "" + for chunk in response_stream: + full_response += chunk + message_placeholder.markdown(full_response + "โ–Œ") + + message_placeholder.markdown(full_response) + st.session_state.chat_history.append((user_input, full_response)) + + except Exception as e: + logger.error(f"Error: {str(e)}") + st.error(f"Error: {str(e)}") + else: + st.info( + "Please configure your model paths and upload documents to get started." + if not st.session_state.my_config + else "Please upload some documents to get started." + ) + +if __name__ == "__main__": + main() diff --git a/rag_tutorials/local_hybrid_search_rag/requirements.txt b/rag_tutorials/local_hybrid_search_rag/requirements.txt new file mode 100644 index 0000000..8af25da --- /dev/null +++ b/rag_tutorials/local_hybrid_search_rag/requirements.txt @@ -0,0 +1,15 @@ +raglite==0.2.1 +llama-cpp-python>=0.2.56 +sentence-transformers>=2.5.1 +pydantic==2.10.1 +sqlalchemy>=2.0.0 +psycopg2-binary>=2.9.9 +pypdf>=3.0.0 +python-dotenv>=1.0.0 +rerankers==0.6.0 +spacy>=3.7.0 +streamlit>=1.31.0 +flashrank==0.2.9 +numpy>=1.24.0 +pandas>=2.0.0 +tqdm>=4.66.0 diff --git a/rag_tutorials/local_rag_agent/README.md b/rag_tutorials/local_rag_agent/README.md new file mode 100644 index 0000000..bbada4a --- /dev/null +++ b/rag_tutorials/local_rag_agent/README.md @@ -0,0 +1,46 @@ +## ๐Ÿฆ™ Local RAG Agent with Llama 3.2 +This application implements a Retrieval-Augmented Generation (RAG) system using Llama 3.2 via Ollama, with Qdrant as the vector database. + + +### Features +- Fully local RAG implementation +- Powered by Llama 3.2 through Ollama +- Vector search using Qdrant +- Interactive playground interface +- No external API dependencies + +### How to get Started? + +1. Clone the GitHub repository +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +``` + +2. Install the required dependencies: + +```bash +cd awesome-llm-apps/rag_tutorials/local_rag_agent +pip install -r requirements.txt +``` + +3. Install and start [Qdrant](https://qdrant.tech/) vector database locally + +```bash +docker pull qdrant/qdrant +docker run -p 6333:6333 qdrant/qdrant +``` + +4. Install [Ollama](https://ollama.com/download) and pull Llama 3.2 for LLM and OpenHermes as the embedder for OllamaEmbedder +```bash +ollama pull llama3.2 +ollama pull openhermes +``` + +4. Run the AI RAG Agent +```bash +python local_rag_agent.py +``` + +5. Open your web browser and navigate to the URL provided in the console output to interact with the RAG agent through the playground interface. + + diff --git a/rag_tutorials/local_rag_agent/local_rag_agent.py b/rag_tutorials/local_rag_agent/local_rag_agent.py new file mode 100644 index 0000000..8e3c272 --- /dev/null +++ b/rag_tutorials/local_rag_agent/local_rag_agent.py @@ -0,0 +1,40 @@ +# Import necessary libraries +from agno.agent import Agent +from agno.models.ollama import Ollama +from agno.knowledge.pdf_url import PDFUrlKnowledgeBase +from agno.vectordb.qdrant import Qdrant +from agno.embedder.ollama import OllamaEmbedder +from agno.playground import Playground, serve_playground_app + +# Define the collection name for the vector database +collection_name = "thai-recipe-index" + +# Set up Qdrant as the vector database with the embedder +vector_db = Qdrant( + collection=collection_name, + url="http://localhost:6333/", + embedder=OllamaEmbedder() +) + +# Define the knowledge base with the specified PDF URL +knowledge_base = PDFUrlKnowledgeBase( + urls=["https://phi-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf"], + vector_db=vector_db, +) + +# Load the knowledge base, comment out after the first run to avoid reloading +knowledge_base.load(recreate=True, upsert=True) + +# Create the Agent using Ollama's llama3.2 model and the knowledge base +agent = Agent( + name="Local RAG Agent", + model=Ollama(id="llama3.2"), + knowledge=knowledge_base, +) + +# UI for RAG agent +app = Playground(agents=[agent]).get_app() + +# Run the Playground app +if __name__ == "__main__": + serve_playground_app("local_rag_agent:app", reload=True) diff --git a/rag_tutorials/local_rag_agent/requirements.txt b/rag_tutorials/local_rag_agent/requirements.txt new file mode 100644 index 0000000..38393d7 --- /dev/null +++ b/rag_tutorials/local_rag_agent/requirements.txt @@ -0,0 +1,7 @@ +agno +qdrant-client +ollama +pypdf +openai +fastapi +uvicorn \ No newline at end of file diff --git a/rag_tutorials/rag-as-a-service/README.md b/rag_tutorials/rag-as-a-service/README.md index b36930a..ded7254 100644 --- a/rag_tutorials/rag-as-a-service/README.md +++ b/rag_tutorials/rag-as-a-service/README.md @@ -13,7 +13,7 @@ Build and deploy a production-ready Retrieval-Augmented Generation (RAG) service 1. Clone the GitHub repository ```bash git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git -cd rag-as-a-service +cd awesome-llm-apps/rag_tutorials/rag-as-a-service ``` 2. Install the required dependencies: diff --git a/rag_tutorials/rag_agent_cohere/README.md b/rag_tutorials/rag_agent_cohere/README.md new file mode 100644 index 0000000..e781e23 --- /dev/null +++ b/rag_tutorials/rag_agent_cohere/README.md @@ -0,0 +1,64 @@ +# RAG Agent with Cohere โŒ˜R + +A RAG Agentic system built with Cohere's new model Command-r7b-12-2024, Qdrant for vector storage, Langchain for RAG and LangGraph for orchestration. This application allows users to upload documents, ask questions about them, and get AI-powered responses with fallback to web search when needed. + +## Features + +- **Document Processing** + - PDF document upload and processing + - Automatic text chunking and embedding + - Vector storage in Qdrant cloud + +- **Intelligent Querying** + - RAG-based document retrieval + - Similarity search with threshold filtering + - Automatic fallback to web search when no relevant documents found + - Source attribution for answers + +- **Advanced Capabilities** + - DuckDuckGo web search integration + - LangGraph agent for web research + - Context-aware response generation + - Long answer summarization + +- **Model Specific Features** + - Command-r7b-12-2024 model for Chat and RAG + - cohere embed-english-v3.0 model for embeddings + - create_react_agent function from langgraph + - DuckDuckGoSearchRun tool for web search + +## Prerequisites + +### 1. Cohere API Key +1. Go to [Cohere Platform](https://dashboard.cohere.ai/api-keys) +2. Sign up or log in to your account +3. Navigate to API Keys section +4. Create a new API key + +### 2. Qdrant Cloud Setup +1. Visit [Qdrant Cloud](https://cloud.qdrant.io/) +2. Create an account or sign in +3. Create a new cluster +4. Get your credentials: + - Qdrant API Key: Found in API Keys section + - Qdrant URL: Your cluster URL (format: `https://xxx-xxx.aws.cloud.qdrant.io`) + + +## How to Run + +1. Clone the repository: +```bash +git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git +cd rag_tutorials/rag_agent_cohere +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +```bash +streamlit run rag_agent_cohere.py +``` + + diff --git a/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py new file mode 100644 index 0000000..8e26d81 --- /dev/null +++ b/rag_tutorials/rag_agent_cohere/rag_agent_cohere.py @@ -0,0 +1,319 @@ +import os +import streamlit as st +from langchain_community.document_loaders import PyPDFLoader +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_cohere import CohereEmbeddings, ChatCohere +from langchain_qdrant import QdrantVectorStore +from qdrant_client import QdrantClient +from qdrant_client.models import Distance, VectorParams +from langchain.chains.combine_documents import create_stuff_documents_chain +from langchain.chains import create_retrieval_chain +from langchain import hub +import tempfile +from langgraph.prebuilt import create_react_agent +from langchain_community.tools import DuckDuckGoSearchRun +from typing import TypedDict, List +from langchain_core.language_models import BaseLanguageModel +from langchain_core.messages import AIMessage, HumanMessage, SystemMessage +from time import sleep +from tenacity import retry, wait_exponential, stop_after_attempt + + +def init_session_state(): + if 'api_keys_submitted' not in st.session_state: + st.session_state.api_keys_submitted = False + if 'chat_history' not in st.session_state: + st.session_state.chat_history = [] + if 'vectorstore' not in st.session_state: + st.session_state.vectorstore = None + if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = "" + if 'qdrant_url' not in st.session_state: + st.session_state.qdrant_url = "" + +def sidebar_api_form(): + with st.sidebar: + st.header("API Credentials") + + if st.session_state.api_keys_submitted: + st.success("API credentials verified") + if st.button("Reset Credentials"): + st.session_state.clear() + st.rerun() + return True + + with st.form("api_credentials"): + cohere_key = st.text_input("Cohere API Key", type="password") + qdrant_key = st.text_input("Qdrant API Key", type="password", help="Enter your Qdrant API key") + qdrant_url = st.text_input("Qdrant URL", + placeholder="https://xyz-example.eu-central.aws.cloud.qdrant.io:6333", + help="Enter your Qdrant instance URL") + + if st.form_submit_button("Submit Credentials"): + try: + client = QdrantClient(url=qdrant_url, api_key=qdrant_key, timeout=60) + client.get_collections() + + st.session_state.cohere_api_key = cohere_key + st.session_state.qdrant_api_key = qdrant_key + st.session_state.qdrant_url = qdrant_url + st.session_state.api_keys_submitted = True + + st.success("Credentials verified!") + st.rerun() + except Exception as e: + st.error(f"Qdrant connection failed: {str(e)}") + return False + +def init_qdrant() -> QdrantClient: + if not st.session_state.get("qdrant_api_key"): + raise ValueError("Qdrant API key not provided") + if not st.session_state.get("qdrant_url"): + raise ValueError("Qdrant URL not provided") + + return QdrantClient(url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key, + timeout=60) + +init_session_state() + +if not sidebar_api_form(): + st.info("Please enter your API credentials in the sidebar to continue.") + st.stop() + +embedding = CohereEmbeddings(model="embed-english-v3.0", + cohere_api_key=st.session_state.cohere_api_key) + +chat_model = ChatCohere(model="command-r7b-12-2024", + temperature=0.1, + max_tokens=512, + verbose=True, + cohere_api_key=st.session_state.cohere_api_key) + +client = init_qdrant() + +def process_document(file): + try: + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(file.getvalue()) + tmp_path = tmp_file.name + + loader = PyPDFLoader(tmp_path) + documents = loader.load() + text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) + texts = text_splitter.split_documents(documents) + + os.unlink(tmp_path) + + return texts + except Exception as e: + st.error(f"Error processing document: {e}") + return [] + +COLLECTION_NAME = "cohere_rag" + +def create_vector_stores(texts): + """Create and populate vector store with documents.""" + try: + try: + client.create_collection(collection_name=COLLECTION_NAME, + vectors_config=VectorParams(size=1024, + distance=Distance.COSINE)) + st.success(f"Created new collection: {COLLECTION_NAME}") + except Exception as e: + if "already exists" not in str(e).lower(): + raise e + + vector_store = QdrantVectorStore(client=client, + collection_name=COLLECTION_NAME, + embedding=embedding) + + with st.spinner('Storing documents in Qdrant...'): + vector_store.add_documents(texts) + st.success("Documents successfully stored in Qdrant!") + + return vector_store + + except Exception as e: + st.error(f"Error in vector store creation: {str(e)}") + return None + +# Define the state schema using TypedDict +class AgentState(TypedDict): + """State schema for the agent.""" + messages: List[HumanMessage | AIMessage | SystemMessage] + is_last_step: bool + +class RateLimitedDuckDuckGo(DuckDuckGoSearchRun): + @retry(wait=wait_exponential(multiplier=1, min=4, max=10), + stop=stop_after_attempt(3)) + def run(self, query: str) -> str: + """Run search with rate limiting.""" + try: + sleep(2) # Add delay between requests + return super().run(query) + except Exception as e: + if "Ratelimit" in str(e): + sleep(5) # Longer delay on rate limit + return super().run(query) + raise e + +def create_fallback_agent(chat_model: BaseLanguageModel): + """Create a LangGraph agent for web research.""" + + def web_research(query: str) -> str: + """Web search with result formatting.""" + try: + search = DuckDuckGoSearchRun(num_results=5) + results = search.run(query) + return results + except Exception as e: + return f"Search failed: {str(e)}. Providing answer based on general knowledge." + + tools = [web_research] + + agent = create_react_agent(model=chat_model, + tools=tools, + debug=False) + + return agent + +def process_query(vectorstore, query) -> tuple[str, list]: + """Process a query using RAG with fallback to web search.""" + try: + retriever = vectorstore.as_retriever( + search_type="similarity_score_threshold", + search_kwargs={ + "k": 10, + "score_threshold": 0.7 + } + ) + + relevant_docs = retriever.get_relevant_documents(query) + + if relevant_docs: + retrieval_qa_prompt = hub.pull("langchain-ai/retrieval-qa-chat") + combine_docs_chain = create_stuff_documents_chain(chat_model, retrieval_qa_prompt) + retrieval_chain = create_retrieval_chain(retriever, combine_docs_chain) + response = retrieval_chain.invoke({"input": query}) + return response['answer'], relevant_docs + + else: + st.info("No relevant documents found. Searching web...") + fallback_agent = create_fallback_agent(chat_model) + + with st.spinner('Researching...'): + agent_input = { + "messages": [ + HumanMessage(content=f"""Please thoroughly research the question: '{query}' and provide a detailed and comprehensive response. Make sure to gather the latest information from credible sources. Minimum 400 words.""") + ], + "is_last_step": False + } + + config = {"recursion_limit": 100} + + try: + response = fallback_agent.invoke(agent_input, config=config) + + if isinstance(response, dict) and "messages" in response: + last_message = response["messages"][-1] + answer = last_message.content if hasattr(last_message, 'content') else str(last_message) + + return f"""Web Search Result: +{answer} +""", [] + + except Exception as agent_error: + fallback_response = chat_model.invoke(f"Please provide a general answer to: {query}").content + return f"Web search unavailable. General response: {fallback_response}", [] + + except Exception as e: + st.error(f"Error: {str(e)}") + return "I encountered an error. Please try rephrasing your question.", [] + +def post_process(answer, sources): + """Post-process the answer and format sources.""" + answer = answer.strip() + + # Summarize long answers + if len(answer) > 500: + summary_prompt = f"Summarize the following answer in 2-3 sentences: {answer}" + summary = chat_model.invoke(summary_prompt).content + answer = f"{summary}\n\nFull Answer: {answer}" + + formatted_sources = [] + for i, source in enumerate(sources, 1): + formatted_source = f"{i}. {source.page_content[:200]}..." + formatted_sources.append(formatted_source) + return answer, formatted_sources + +st.title("RAG Agent with Cohere โŒ˜R") + +uploaded_file = st.file_uploader("Choose a PDF or Image File", type=["pdf", "jpg", "jpeg"]) + +if uploaded_file is not None and 'processed_file' not in st.session_state: + with st.spinner('Processing file... This may take a while for images.'): + texts = process_document(uploaded_file) + vectorstore = create_vector_stores(texts) + if vectorstore: + st.session_state.vectorstore = vectorstore + st.session_state.processed_file = True + st.success('File uploaded and processed successfully!') + else: + st.error('Failed to process file. Please try again.') + +for message in st.session_state.chat_history: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + +if query := st.chat_input("Ask a question about the document:"): + st.session_state.chat_history.append({"role": "user", "content": query}) + with st.chat_message("user"): + st.markdown(query) + + if st.session_state.vectorstore: + with st.chat_message("assistant"): + try: + answer, sources = process_query(st.session_state.vectorstore, query) + st.markdown(answer) + + if sources: + with st.expander("Sources"): + for source in sources: + st.markdown(f"- {source.page_content[:200]}...") + + st.session_state.chat_history.append({ + "role": "assistant", + "content": answer + }) + + except Exception as e: + st.error(f"Error: {str(e)}") + st.info("Please try asking your question again.") + else: + st.error("Please upload a document first.") + +with st.sidebar: + st.divider() + col1, col2 = st.columns(2) + with col1: + if st.button('Clear Chat History'): + st.session_state.chat_history = [] + st.rerun() + with col2: + if st.button('Clear All Data'): + try: + collections = client.get_collections().collections + collection_names = [col.name for col in collections] + + if COLLECTION_NAME in collection_names: + client.delete_collection(COLLECTION_NAME) + if f"{COLLECTION_NAME}_compressed" in collection_names: + client.delete_collection(f"{COLLECTION_NAME}_compressed") + + st.session_state.vectorstore = None + st.session_state.chat_history = [] + st.success("All data cleared successfully!") + st.rerun() + except Exception as e: + st.error(f"Error clearing data: {str(e)}") diff --git a/rag_tutorials/rag_agent_cohere/requirements.txt b/rag_tutorials/rag_agent_cohere/requirements.txt new file mode 100644 index 0000000..0d69870 --- /dev/null +++ b/rag_tutorials/rag_agent_cohere/requirements.txt @@ -0,0 +1,14 @@ +langchain==0.3.12 +langchain-community==0.3.12 +langchain-core==0.3.25 +langchain-cohere==0.3.2 +langchain-qdrant==0.2.0 +cohere==5.11.4 +qdrant-client==1.12.1 +duckduckgo-search==6.4.1 +streamlit==1.40.2 +tenacity==9.0.0 +typing-extensions==4.12.2 +pydantic==2.9.2 +pydantic-core==2.23.4 +langgraph==0.2.53 \ No newline at end of file diff --git a/rag_tutorials/rag_chain/README.md b/rag_tutorials/rag_chain/README.md new file mode 100644 index 0000000..c253d73 --- /dev/null +++ b/rag_tutorials/rag_chain/README.md @@ -0,0 +1,48 @@ +# PharmaQuery + +## Overview +PharmaQuery is an advanced Pharmaceutical Insight Retrieval System designed to help users gain meaningful insights from research papers and documents in the pharmaceutical domain. + +## Demo +https://github.com/user-attachments/assets/c12ee305-86fe-4f71-9219-57c7f438f291 + +## Features +- **Natural Language Querying**: Ask complex questions about the pharmaceutical industry and get concise, accurate answers. +- **Custom Database**: Upload your own research documents to enhance the retrieval system's knowledge base. +- **Similarity Search**: Retrieves the most relevant documents for your query using AI embeddings. +- **Streamlit Interface**: User-friendly interface for queries and document uploads. + +## Technologies Used +- **Programming Language**: [Python 3.10+](https://www.python.org/downloads/release/python-31011/) +- **Framework**: [LangChain](https://www.langchain.com/) +- **Database**: [ChromaDB](https://www.trychroma.com/) +- **Models**: + - Embeddings: [Google Gemini API (embedding-001)](https://ai.google.dev/gemini-api/docs/embeddings) + - Chat: [Google Gemini API (gemini-1.5-pro)](https://ai.google.dev/gemini-api/docs/models/gemini#gemini-1.5-pro) +- **PDF Processing**: [PyPDFLoader](https://python.langchain.com/docs/integrations/document_loaders/pypdfloader/) +- **Document Splitter**: [SentenceTransformersTokenTextSplitter](https://python.langchain.com/api_reference/text_splitters/sentence_transformers/langchain_text_splitters.sentence_transformers.SentenceTransformersTokenTextSplitter.html) + +## Requirements +1. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +2. **Run the Application**: + ```bash + streamlit run app.py + ``` + +3. **Use the Application**: + - Paste your Google API Key in the sidebar. + - Enter your query in the main interface. + - Optionally, upload research papers in the sidebar to enhance the database. + +## :mailbox: Connect With Me +handshake gif + +

+ codewithcharan + __mr.__.unique + codewithcharan +

\ No newline at end of file diff --git a/rag_tutorials/rag_chain/app.py b/rag_tutorials/rag_chain/app.py new file mode 100644 index 0000000..714999d --- /dev/null +++ b/rag_tutorials/rag_chain/app.py @@ -0,0 +1,200 @@ +import os +import streamlit as st + +from langchain_google_genai import GoogleGenerativeAIEmbeddings +from langchain_chroma import Chroma +from langchain_community.document_loaders import PyPDFLoader +from langchain_text_splitters.sentence_transformers import SentenceTransformersTokenTextSplitter +from langchain_core.prompts import ChatPromptTemplate +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain_core.output_parsers import StrOutputParser +from langchain_core.runnables import RunnablePassthrough + + +# Initialize embedding model +embedding_model = GoogleGenerativeAIEmbeddings(model="models/embedding-001") + +# Initialize pharma database +db = Chroma(collection_name="pharma_database", + embedding_function=embedding_model, + persist_directory='./pharma_db') + +def format_docs(docs): + """Formats a list of document objects into a single string. + + Args: + docs (list): A list of document objects, each having a 'page_content' attribute. + + Returns: + str: A single string containing the page content from each document, + separated by double newlines.""" + return "\n\n".join(doc.page_content for doc in docs) + +def add_to_db(uploaded_files): + """Processes and adds uploaded PDF files to the database. + + This function checks if any files have been uploaded. If files are uploaded, + it saves each file to a temporary location, processes the content using a PDF loader, + and splits the content into smaller chunks. Each chunk, along with its metadata, + is then added to the database. Temporary files are removed after processing. + + Args: + uploaded_files (list): A list of uploaded file objects to be processed. + + Returns: + None""" + # Check if files are uploaded + if not uploaded_files: + st.error("No files uploaded!") + return + + for uploaded_file in uploaded_files: + # Save the uploaded file to a temporary path + temp_file_path = os.path.join("./temp", uploaded_file.name) + os.makedirs(os.path.dirname(temp_file_path), exist_ok=True) + + with open(temp_file_path, "wb") as temp_file: + temp_file.write(uploaded_file.getbuffer()) + + # Load the file using PyPDFLoader + loader = PyPDFLoader(temp_file_path) + data = loader.load() + + # Store metadata and content + doc_metadata = [data[i].metadata for i in range(len(data))] + doc_content = [data[i].page_content for i in range(len(data))] + + # Split documents into smaller chunks + st_text_splitter = SentenceTransformersTokenTextSplitter( + model_name="sentence-transformers/all-mpnet-base-v2", + chunk_size=100, + chunk_overlap=50 + ) + st_chunks = st_text_splitter.create_documents(doc_content, doc_metadata) + + # Add chunks to database + db.add_documents(st_chunks) + + # Remove the temporary file after processing + os.remove(temp_file_path) + +def run_rag_chain(query): + """Processes a query using a Retrieval-Augmented Generation (RAG) chain. + + This function utilizes a RAG chain to answer a given query. It retrieves + relevant context using similarity search and then generates a response + based on this context using a chat model. The chat model is pre-configured + with a prompt template specialized in pharmaceutical sciences. + + Args: + query (str): The user's question that needs to be answered. + + Returns: + str: A response generated by the chat model, based on the retrieved context.""" + # Create a Retriever Object and apply Similarity Search + retriever = db.as_retriever(search_type="similarity", search_kwargs={'k': 5}) + + # Initialize a Chat Prompt Template + PROMPT_TEMPLATE = """ + You are a highly knowledgeable assistant specializing in pharmaceutical sciences. + Answer the question based only on the following context: + {context} + + Answer the question based on the above context: + {question} + + Use the provided context to answer the user's question accurately and concisely. + Don't justify your answers. + Don't give information not mentioned in the CONTEXT INFORMATION. + Do not say "according to the context" or "mentioned in the context" or similar. + """ + + prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE) + + # Initialize a Generator (i.e. Chat Model) + chat_model = ChatGoogleGenerativeAI( + model="gemini-1.5-pro", + api_key=st.session_state.get("gemini_api_key"), + temperature=1 + ) + + # Initialize a Output Parser + output_parser = StrOutputParser() + + # RAG Chain + rag_chain = {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt_template | chat_model | output_parser + + # Invoke the Chain + response = rag_chain.invoke(query) + + return response + +def main(): + """Initialize and manage the PharmaQuery application interface. + + This function sets up the Streamlit application interface for PharmaQuery, + a Pharmaceutical Insight Retrieval System. Users can enter queries related + to the pharmaceutical industry, upload research documents, and manage API + keys for enhanced functionality. + + The main features include: + - Query input area for users to ask questions about the pharmaceutical industry. + - Submission button to process the query and display the retrieved insights. + - Sidebar for API key input and management. + - File uploader for adding research documents to the database, enhancing query responses. + + Args: + None + + Returns: + None""" + st.set_page_config(page_title="PharmaQuery", page_icon=":microscope:") + st.header("Pharmaceutical Insight Retrieval System") + + query = st.text_area( + ":bulb: Enter your query about the Pharmaceutical Industry:", + placeholder="e.g., What are the AI applications in drug discovery?" + ) + + if st.button("Submit"): + if not query: + st.warning("Please ask a question") + + else: + with st.spinner("Thinking..."): + result = run_rag_chain(query=query) + st.write(result) + + with st.sidebar: + st.title("API Keys") + gemini_api_key = st.text_input("Enter your Gemini API key:", type="password") + + if st.button("Enter"): + if gemini_api_key: + st.session_state.gemini_api_key = gemini_api_key + st.success("API key saved!") + + else: + st.warning("Please enter your Gemini API key to proceed.") + + with st.sidebar: + st.markdown("---") + pdf_docs = st.file_uploader("Upload your research documents related to Pharmaceutical Sciences (Optional) :memo:", + type=["pdf"], + accept_multiple_files=True + ) + + if st.button("Submit & Process"): + if not pdf_docs: + st.warning("Please upload the file") + + else: + with st.spinner("Processing your documents..."): + add_to_db(pdf_docs) + st.success(":file_folder: Documents successfully added to the database!") + + # Sidebar Footer + st.sidebar.write("Built with โค๏ธ by [Charan](https://www.linkedin.com/in/codewithcharan/)") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rag_tutorials/rag_chain/requirements.txt b/rag_tutorials/rag_chain/requirements.txt new file mode 100644 index 0000000..7b5fa46 --- /dev/null +++ b/rag_tutorials/rag_chain/requirements.txt @@ -0,0 +1,9 @@ +streamlit +langchain-google-genai +langchain-chroma +langchain-community +langchain-core +chromadb +sentence-transformers +PyPDF2 +python-dotenv diff --git a/rag_tutorials/rag_database_routing/README.md b/rag_tutorials/rag_database_routing/README.md new file mode 100644 index 0000000..5a325a5 --- /dev/null +++ b/rag_tutorials/rag_database_routing/README.md @@ -0,0 +1,73 @@ +# ๐Ÿ“  RAG Agent with Database Routing + +A Streamlit application that demonstrates an advanced implementation of RAG Agent with intelligent query routing. The system combines multiple specialized databases with smart fallback mechanisms to ensure reliable and accurate responses to user queries. + +## Features + +- **Document Upload**: Users can upload multiple PDF documents related to a particular company. These documents are processed and stored in one of the three databases: Product Information, Customer Support & FAQ, or Financial Information. + +- **Natural Language Querying**: Users can ask questions in natural language. The system automatically routes the query to the most relevant database using a phidata agent as the router. + +- **RAG Orchestration**: Utilizes Langchain for orchestrating the retrieval augmented generation process, ensuring that the most relevant information is retrieved and presented to the user. + +- **Fallback Mechanism**: If no relevant documents are found in the databases, a LangGraph agent with a DuckDuckGo search tool is used to perform web research and provide an answer. + +## How to Run? + +1. **Clone the Repository**: + ```bash + git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git + cd rag_tutorials/rag_database_routing + ``` + +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +3. **Run the Application**: + ```bash + streamlit run rag_database_routing.py + ``` + +4. **Get OpenAI API Key**: Obtain an OpenAI API key and set it in the application. This is required for initializing the language models used in the application. + +5. **Setup Qdrant Cloud** +- Visit [Qdrant Cloud](https://cloud.qdrant.io/) +- Create an account or sign in +- Create a new cluster +- Get your credentials: + - Qdrant API Key: Found in API Keys section + - Qdrant URL: Your cluster URL (format: https://xxx-xxx.aws.cloud.qdrant.io) + +5. **Upload Documents**: Use the document upload section to add PDF documents to the desired database. + +6. **Ask Questions**: Enter your questions in the query section. The application will route your question to the appropriate database and provide an answer. + +## Technologies Used + +- **Langchain**: For RAG orchestration, ensuring efficient retrieval and generation of information. +- **Phidata Agent**: Used as the router agent to determine the most relevant database for a given query. +- **LangGraph Agent**: Acts as a fallback mechanism, utilizing DuckDuckGo for web research when necessary. +- **Streamlit**: Provides a user-friendly interface for document upload and querying. +- **Qdrant**: Used for managing the databases, storing and retrieving document embeddings efficiently. + +## How It Works? + +**1. Query Routing** +The system uses a three-stage routing approach: +- Vector similarity search across all databases +- LLM-based routing for ambiguous queries +- Web search fallback for unknown topics + +**2. Document Processing** +- Automatic text extraction from PDFs +- Smart text chunking with overlap +- Vector embedding generation +- Efficient database storage + +**3. Answer Generation** +- Context-aware retrieval +- Smart document combination +- Confidence-based responses +- Web research integration \ No newline at end of file diff --git a/rag_tutorials/rag_database_routing/rag_database_routing.py b/rag_tutorials/rag_database_routing/rag_database_routing.py new file mode 100644 index 0000000..7e83a68 --- /dev/null +++ b/rag_tutorials/rag_database_routing/rag_database_routing.py @@ -0,0 +1,387 @@ +import os +from typing import List, Dict, Any, Literal, Optional +from dataclasses import dataclass +import streamlit as st +from langchain_core.documents import Document +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_community.document_loaders import PyPDFLoader +from langchain_community.vectorstores import Qdrant +from langchain_openai import OpenAIEmbeddings +from langchain_openai import ChatOpenAI +import tempfile +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from langchain.schema import HumanMessage +from langchain.chains.combine_documents import create_stuff_documents_chain +from langchain.chains import create_retrieval_chain +from langchain import hub +from langgraph.prebuilt import create_react_agent +from langchain_community.tools import DuckDuckGoSearchRun +from langchain_core.language_models import BaseLanguageModel +from langchain.prompts import ChatPromptTemplate +from qdrant_client import QdrantClient +from qdrant_client.models import Distance, VectorParams + +def init_session_state(): + """Initialize session state variables""" + if 'openai_api_key' not in st.session_state: + st.session_state.openai_api_key = "" + if 'qdrant_url' not in st.session_state: + st.session_state.qdrant_url = "" + if 'qdrant_api_key' not in st.session_state: + st.session_state.qdrant_api_key = "" + if 'embeddings' not in st.session_state: + st.session_state.embeddings = None + if 'llm' not in st.session_state: + st.session_state.llm = None + if 'databases' not in st.session_state: + st.session_state.databases = {} + +init_session_state() + +DatabaseType = Literal["products", "support", "finance"] +PERSIST_DIRECTORY = "db_storage" + +@dataclass +class CollectionConfig: + name: str + description: str + collection_name: str # This will be used as Qdrant collection name + +# Collection configurations +COLLECTIONS: Dict[DatabaseType, CollectionConfig] = { + "products": CollectionConfig( + name="Product Information", + description="Product details, specifications, and features", + collection_name="products_collection" + ), + "support": CollectionConfig( + name="Customer Support & FAQ", + description="Customer support information, frequently asked questions, and guides", + collection_name="support_collection" + ), + "finance": CollectionConfig( + name="Financial Information", + description="Financial data, revenue, costs, and liabilities", + collection_name="finance_collection" + ) +} + +def initialize_models(): + """Initialize OpenAI models and Qdrant client""" + if (st.session_state.openai_api_key and + st.session_state.qdrant_url and + st.session_state.qdrant_api_key): + + os.environ["OPENAI_API_KEY"] = st.session_state.openai_api_key + st.session_state.embeddings = OpenAIEmbeddings(model="text-embedding-3-small") + st.session_state.llm = ChatOpenAI(temperature=0) + + try: + client = QdrantClient( + url=st.session_state.qdrant_url, + api_key=st.session_state.qdrant_api_key + ) + + # Test connection + client.get_collections() + vector_size = 1536 + st.session_state.databases = {} + for db_type, config in COLLECTIONS.items(): + try: + client.get_collection(config.collection_name) + except Exception: + # Create collection if it doesn't exist + client.create_collection( + collection_name=config.collection_name, + vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE) + ) + + st.session_state.databases[db_type] = Qdrant( + client=client, + collection_name=config.collection_name, + embeddings=st.session_state.embeddings + ) + + return True + except Exception as e: + st.error(f"Failed to connect to Qdrant: {str(e)}") + return False + return False + +def process_document(file) -> List[Document]: + """Process uploaded PDF document""" + try: + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + tmp_file.write(file.getvalue()) + tmp_path = tmp_file.name + + loader = PyPDFLoader(tmp_path) + documents = loader.load() + + # Clean up temporary file + os.unlink(tmp_path) + + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1000, + chunk_overlap=200 + ) + texts = text_splitter.split_documents(documents) + + return texts + except Exception as e: + st.error(f"Error processing document: {e}") + return [] + +def create_routing_agent() -> Agent: + """Creates a routing agent using phidata framework""" + return Agent( + model=OpenAIChat( + id="gpt-4o", + api_key=st.session_state.openai_api_key + ), + tools=[], + description="""You are a query routing expert. Your only job is to analyze questions and determine which database they should be routed to. + You must respond with exactly one of these three options: 'products', 'support', or 'finance'. The user's question is: {question}""", + instructions=[ + "Follow these rules strictly:", + "1. For questions about products, features, specifications, or item details, or product manuals โ†’ return 'products'", + "2. For questions about help, guidance, troubleshooting, or customer service, FAQ, or guides โ†’ return 'support'", + "3. For questions about costs, revenue, pricing, or financial data, or financial reports and investments โ†’ return 'finance'", + "4. Return ONLY the database name, no other text or explanation", + "5. If you're not confident about the routing, return an empty response" + ], + markdown=False, + show_tool_calls=False + ) + +def route_query(question: str) -> Optional[DatabaseType]: + """Route query by searching all databases and comparing relevance scores. + Returns None if no suitable database is found.""" + try: + best_score = -1 + best_db_type = None + all_scores = {} # Store all scores for debugging + + # Search each database and compare relevance scores + for db_type, db in st.session_state.databases.items(): + results = db.similarity_search_with_score( + question, + k=3 + ) + + if results: + avg_score = sum(score for _, score in results) / len(results) + all_scores[db_type] = avg_score + + if avg_score > best_score: + best_score = avg_score + best_db_type = db_type + + confidence_threshold = 0.5 + if best_score >= confidence_threshold and best_db_type: + st.success(f"Using vector similarity routing: {best_db_type} (confidence: {best_score:.3f})") + return best_db_type + + st.warning(f"Low confidence scores (below {confidence_threshold}), falling back to LLM routing") + + # Fallback to LLM routing + routing_agent = create_routing_agent() + response = routing_agent.run(question) + + db_type = (response.content + .strip() + .lower() + .translate(str.maketrans('', '', '`\'"'))) + + if db_type in COLLECTIONS: + st.success(f"Using LLM routing decision: {db_type}") + return db_type + + st.warning("No suitable database found, will use web search fallback") + return None + + except Exception as e: + st.error(f"Routing error: {str(e)}") + return None + +def create_fallback_agent(chat_model: BaseLanguageModel): + """Create a LangGraph agent for web research.""" + + def web_research(query: str) -> str: + """Web search with result formatting.""" + try: + search = DuckDuckGoSearchRun(num_results=5) + results = search.run(query) + return results + except Exception as e: + return f"Search failed: {str(e)}. Providing answer based on general knowledge." + + tools = [web_research] + + agent = create_react_agent(model=chat_model, + tools=tools, + debug=False) + + return agent + +def query_database(db: Qdrant, question: str) -> tuple[str, list]: + """Query the database and return answer and relevant documents""" + try: + retriever = db.as_retriever( + search_type="similarity", + search_kwargs={"k": 4} + ) + + relevant_docs = retriever.get_relevant_documents(question) + + if relevant_docs: + # Use simpler chain creation with hub prompt + retrieval_qa_prompt = ChatPromptTemplate.from_messages([ + ("system", """You are a helpful AI assistant that answers questions based on provided context. + Always be direct and concise in your responses. + If the context doesn't contain enough information to fully answer the question, acknowledge this limitation. + Base your answers strictly on the provided context and avoid making assumptions."""), + ("human", "Here is the context:\n{context}"), + ("human", "Question: {input}"), + ("assistant", "I'll help answer your question based on the context provided."), + ("human", "Please provide your answer:"), + ]) + combine_docs_chain = create_stuff_documents_chain(st.session_state.llm, retrieval_qa_prompt) + retrieval_chain = create_retrieval_chain(retriever, combine_docs_chain) + + response = retrieval_chain.invoke({"input": question}) + return response['answer'], relevant_docs + + raise ValueError("No relevant documents found in database") + + except Exception as e: + st.error(f"Error: {str(e)}") + return "I encountered an error. Please try rephrasing your question.", [] + +def _handle_web_fallback(question: str) -> tuple[str, list]: + st.info("No relevant documents found. Searching web...") + fallback_agent = create_fallback_agent(st.session_state.llm) + + with st.spinner('Researching...'): + agent_input = { + "messages": [ + HumanMessage(content=f"Research and provide a detailed answer for: '{question}'") + ], + "is_last_step": False + } + + try: + response = fallback_agent.invoke(agent_input, config={"recursion_limit": 100}) + if isinstance(response, dict) and "messages" in response: + answer = response["messages"][-1].content + return f"Web Search Result:\n{answer}", [] + + except Exception: + # Fallback to general LLM response + fallback_response = st.session_state.llm.invoke(question).content + return f"Web search unavailable. General response: {fallback_response}", [] + +def main(): + """Main application function.""" + st.set_page_config(page_title="RAG Agent with Database Routing", page_icon="๐Ÿ“š") + st.title("๐Ÿ“  RAG Agent with Database Routing") + + # Sidebar for API keys and configuration + with st.sidebar: + st.header("Configuration") + + # OpenAI API Key + api_key = st.text_input( + "Enter OpenAI API Key:", + type="password", + value=st.session_state.openai_api_key, + key="api_key_input" + ) + + # Qdrant Configuration + qdrant_url = st.text_input( + "Enter Qdrant URL:", + value=st.session_state.qdrant_url, + help="Example: https://your-cluster.qdrant.tech" + ) + + qdrant_api_key = st.text_input( + "Enter Qdrant API Key:", + type="password", + value=st.session_state.qdrant_api_key + ) + + # Update session state + if api_key: + st.session_state.openai_api_key = api_key + if qdrant_url: + st.session_state.qdrant_url = qdrant_url + if qdrant_api_key: + st.session_state.qdrant_api_key = qdrant_api_key + + # Initialize models if all credentials are provided + if (st.session_state.openai_api_key and + st.session_state.qdrant_url and + st.session_state.qdrant_api_key): + if initialize_models(): + st.success("Connected to OpenAI and Qdrant successfully!") + else: + st.error("Failed to initialize. Please check your credentials.") + else: + st.warning("Please enter all required credentials to continue") + st.stop() + + st.markdown("---") + + st.header("Document Upload") + st.info("Upload documents to populate the databases. Each tab corresponds to a different database.") + tabs = st.tabs([collection_config.name for collection_config in COLLECTIONS.values()]) + + for (collection_type, collection_config), tab in zip(COLLECTIONS.items(), tabs): + with tab: + st.write(collection_config.description) + uploaded_files = st.file_uploader( + f"Upload PDF documents to {collection_config.name}", + type="pdf", + key=f"upload_{collection_type}", + accept_multiple_files=True + ) + + if uploaded_files: + with st.spinner('Processing documents...'): + all_texts = [] + for uploaded_file in uploaded_files: + texts = process_document(uploaded_file) + all_texts.extend(texts) + + if all_texts: + db = st.session_state.databases[collection_type] + db.add_documents(all_texts) + st.success("Documents processed and added to the database!") + + # Query section + st.header("Ask Questions") + st.info("Enter your question below to find answers from the relevant database.") + question = st.text_input("Enter your question:") + + if question: + with st.spinner('Finding answer...'): + # Route the question + collection_type = route_query(question) + + if collection_type is None: + # Use web search fallback directly + answer, relevant_docs = _handle_web_fallback(question) + st.write("### Answer (from web search)") + st.write(answer) + else: + # Display routing information and query the database + st.info(f"Routing question to: {COLLECTIONS[collection_type].name}") + db = st.session_state.databases[collection_type] + answer, relevant_docs = query_database(db, question) + st.write("### Answer") + st.write(answer) + +if __name__ == "__main__": + main() diff --git a/rag_tutorials/rag_database_routing/requirements.txt b/rag_tutorials/rag_database_routing/requirements.txt new file mode 100644 index 0000000..0c69e77 --- /dev/null +++ b/rag_tutorials/rag_database_routing/requirements.txt @@ -0,0 +1,11 @@ +langchain==0.3.12 +langchain-community==0.3.12 +langchain-core==0.3.28 +qdrant-client==1.12.1 +streamlit>=1.29.0 +pypdf>=4.0.0 +sentence-transformers>=2.2.2 +phidata==2.7.3 +langchain-openai==0.2.14 +langgraph==0.2.53 +duckduckgo-search==6.4.1 \ No newline at end of file