├── README.md ├── requirements.txt ├── loop.py ├── scraper.py ├── Stock.py ├── adaptive_rag.py └── CRAG.py /README.md: -------------------------------------------------------------------------------- 1 | If you like this topic and you want to support me: 2 | 3 | - Medium: https://medium.com/@GaoDalie_AI 4 | - Youtube: https://www.youtube.com/@GaoDalie_AI 5 | - Twitter : https://twitter.com/GaoDalie_AI 6 | - Linkedin: https://shorturl.at/gIPZ5 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | langchain_community 3 | langchain-openai 4 | langchain_core 5 | langchain 6 | langgraph 7 | streamlit 8 | langchain-nomic 9 | tiktoken 10 | langchainhub 11 | chromadb 12 | tavily-python 13 | BeautifulSoup4 14 | gpt4all 15 | pypdf 16 | PyPDF2 17 | -------------------------------------------------------------------------------- /loop.py: -------------------------------------------------------------------------------- 1 | from langchain_core.tools import tool 2 | from typing import Annotated 3 | from langchain_experimental.utilities import PythonREPL 4 | from langchain_community.tools.tavily_search import TavilySearchResults 5 | from langchain_openai import OpenAI 6 | from typing import TypedDict, Annotated, List, Union 7 | from langchain_core.agents import AgentAction, AgentFinish 8 | from langchain_core.messages import BaseMessage 9 | import operator 10 | from langchain_core.agents import AgentFinish 11 | from langgraph.prebuilt.tool_executor import ToolExecutor 12 | from langchain.agents import create_json_chat_agent 13 | from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, PromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate 14 | from langchain.agents import create_json_chat_agent 15 | import json 16 | import os 17 | from typing import List, Union 18 | from langgraph.graph import END, StateGraph 19 | from langchain_core.messages import ( 20 | AIMessage, 21 | HumanMessage, 22 | ChatMessage, 23 | SystemMessage, 24 | FunctionMessage, 25 | ToolMessage, 26 | ) 27 | 28 | class AgentState(TypedDict): 29 | input: str 30 | chat_history: list[BaseMessage] 31 | agent_outcome: Union[AgentAction, AgentFinish, None] 32 | intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add] 33 | 34 | os.environ["TAVILY_API_KEY"] = "Your_Api_key" 35 | 36 | search = TavilySearchResults(max_results=1) 37 | 38 | repl = PythonREPL() 39 | 40 | chat_model = OpenAI(openai_api_key="Your_API_key") 41 | 42 | 43 | def extract_json(text): 44 | try: 45 | # Find the index where the JSON part starts (after "AI:") 46 | start_index = text.find("AI:") 47 | if start_index != -1: 48 | # Extract the JSON part of the text 49 | json_str = text[start_index + len("AI:"):].strip() 50 | print(f"JSON string: {json_str}") # Add this line to print the JSON string 51 | # Parse the JSON object 52 | json_obj = json.loads(json_str) 53 | return json_obj 54 | else: 55 | # If "AI:" prefix not found, return None 56 | return None 57 | except json.JSONDecodeError as e: 58 | # Handle JSON decoding errors 59 | print(f"Error decoding JSON: {e}") 60 | return None 61 | except Exception as e: 62 | # Handle other unexpected errors 63 | print(f"Unexpected error: {e}") 64 | return None 65 | 66 | 67 | @tool 68 | def python_repl( 69 | code: Annotated[str, "The python code to execute to generate your chart."] 70 | ): 71 | """Use this to execute python code. If you want to see the output of a value, 72 | you should print it out with `print(...)`. This is visible to the user.""" 73 | try: 74 | result = repl.run(code) 75 | except BaseException as e: 76 | return f"Failed to execute. Error: {repr(e)}" 77 | return f"Succesfully executed:\n```python\n{code}\n```\nStdout: {result}" 78 | 79 | tools = [search, python_repl] 80 | 81 | 82 | prompt2 = ChatPromptTemplate( 83 | input_variables=["agent_scratchpad", "input", "tool_names", "tools"], 84 | input_types={ 85 | "chat_history": List[ 86 | Union[ 87 | AIMessage, 88 | HumanMessage, 89 | ChatMessage, 90 | SystemMessage, 91 | FunctionMessage, 92 | ToolMessage, 93 | ] 94 | ], 95 | "agent_scratchpad": List[ 96 | Union[ 97 | AIMessage, 98 | HumanMessage, 99 | ChatMessage, 100 | SystemMessage, 101 | FunctionMessage, 102 | ToolMessage, 103 | ] 104 | ], 105 | }, 106 | messages=[ 107 | SystemMessagePromptTemplate( 108 | prompt=PromptTemplate( 109 | input_variables=[], 110 | template="""Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range 111 | of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. 112 | As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage 113 | in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand. 114 | \n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process 115 | and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide 116 | range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it 117 | to engage in discussions and provide explanations and descriptions on a wide range of topics. 118 | \n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and 119 | information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation 120 | about a particular topic, Assistant is here to assist.""", 121 | ) 122 | ), 123 | MessagesPlaceholder(variable_name="chat_history", optional=True), 124 | HumanMessagePromptTemplate( 125 | prompt=PromptTemplate( 126 | input_variables=["input", "tool_names", "tools"], 127 | template='TOOLS\n------\nAssistant can ask the user to use tools to look up information that may be helpful in answering the users original question. The tools the human can use are:\n\n{tools}\n\nRESPONSE FORMAT INSTRUCTIONS\n----------------------------\n\nWhen responding to me, please output a response in one of two formats:\n\n**Option 1:**\nUse this if you want the human to use a tool.\nMarkdown code snippet formatted in the following schema:\n\n```json\n{{\n "action": string, \\ The action to take. Must be one of {tool_names}\n "action_input": string \\ The input to the action\n}}\n```\n\n**Option #2:**\nUse this if you can respond directly to the human after tool execution. Markdown code snippet formatted in the following schema:\n\n```json\n{{\n "action": "Final Answer",\n "action_input": string \\ You should put what you want to return to use here\n}}\n```\n\nUSER\'S INPUT\n--------------------\nHere is the user\'s input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else):\n\n{input}', 128 | 129 | ) 130 | ), 131 | MessagesPlaceholder(variable_name="agent_scratchpad"), 132 | ], 133 | ) 134 | 135 | agent_runnable = create_json_chat_agent(chat_model, tools, prompt2) 136 | 137 | tool_executor = ToolExecutor(tools) 138 | 139 | def run_agent(data): 140 | agent_outcome = agent_runnable.invoke(data) 141 | return {"agent_outcome": agent_outcome} 142 | 143 | def execute_tools(data): 144 | agent_action = data['agent_outcome'] 145 | tool = agent_action.tool 146 | tool_input = agent_action.tool_input 147 | 148 | 149 | response = input(f"[y/n] continue with: {tool} with {tool_input}?") 150 | if response == "n": 151 | raise ValueError 152 | 153 | output = tool_executor.invoke(agent_action) 154 | data["intermediate_steps"].append((agent_action, str(output))) 155 | return data 156 | 157 | 158 | def should_continue(data): 159 | if isinstance(data['agent_outcome'], AgentFinish): 160 | return "end" 161 | else: 162 | return "continue" 163 | 164 | 165 | 166 | workflow = StateGraph(AgentState) 167 | 168 | 169 | workflow.add_node("agent", run_agent) 170 | workflow.add_node("action", execute_tools) 171 | 172 | workflow.set_entry_point("agent") 173 | 174 | workflow.add_conditional_edges( 175 | "agent", 176 | should_continue, 177 | { 178 | "continue": "action", 179 | "end": END, 180 | }, 181 | ) 182 | 183 | workflow.add_edge("action", "agent") 184 | 185 | app = workflow.compile() 186 | 187 | inputs = {"input": "what is the weather in taiwan", "chat_history": []} 188 | result = app.invoke(inputs) 189 | 190 | print(result["agent_outcome"].return_values["output"]) -------------------------------------------------------------------------------- /scraper.py: -------------------------------------------------------------------------------- 1 | import os 2 | from langchain.agents import AgentExecutor, create_openai_tools_agent 3 | from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser 4 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 5 | from langchain_openai import ChatOpenAI 6 | from langchain.tools import tool 7 | from langchain_core.messages import HumanMessage, SystemMessage 8 | import streamlit as st 9 | from langchain_core.runnables import Runnable 10 | import operator 11 | from typing import Annotated, Sequence, TypedDict , List 12 | from langchain_core.messages import BaseMessage 13 | from langchain_community.document_loaders import WebBaseLoader 14 | from langgraph.graph import StateGraph, END 15 | 16 | 17 | 18 | 19 | def main(): 20 | 21 | st.title("LangGraph + Function Call + YahooFinance 👾") 22 | # Add a sidebar for model selection 23 | OPENAI_MODEL = st.sidebar.selectbox( 24 | "Select Model", 25 | ["gpt-4-turbo-preview", "gpt-3.5-turbo", "gpt-3.5-turbo-instruct"] # Add your model options here 26 | ) 27 | 28 | api_key = st.sidebar.text_input("Enter your OpenAI API Key", type="password") 29 | 30 | if api_key: 31 | os.environ["OPENAI_API_KEY"] = api_key 32 | 33 | user_input = st.text_input("Enter your input here:") 34 | 35 | # Run the workflow 36 | if st.button("Run Workflow"): 37 | with st.spinner("Running Workflow..."): 38 | 39 | def create_agent(llm: ChatOpenAI, tools: list, system_prompt: str): 40 | prompt = ChatPromptTemplate.from_messages( 41 | [ 42 | ("system", system_prompt), 43 | MessagesPlaceholder(variable_name="messages"), 44 | MessagesPlaceholder(variable_name="agent_scratchpad"), 45 | ] 46 | ) 47 | agent = create_openai_tools_agent(llm, tools, prompt) 48 | return AgentExecutor(agent=agent, tools=tools) 49 | 50 | def create_supervisor(llm: ChatOpenAI, agents: list[str]): 51 | system_prompt = ( 52 | "You are the supervisor over the following agents: {agents}." 53 | " You are responsible for assigning tasks to each agent as requested by the user." 54 | " Each agent executes tasks according to their roles and responds with their results and status." 55 | " Please review the information and answer with the name of the agent to which the task should be assigned next." 56 | " Answer 'FINISH' if you are satisfied that you have fulfilled the user's request." 57 | ) 58 | 59 | options = ["FINISH"] + agents 60 | function_def = { 61 | "name": "supervisor", 62 | "description": "Select the next agent.", 63 | "parameters": { 64 | "type": "object", 65 | "properties": { 66 | "next": { 67 | "anyOf": [ 68 | {"enum": options}, 69 | ], 70 | } 71 | }, 72 | "required": ["next"], 73 | }, 74 | } 75 | 76 | prompt = ChatPromptTemplate.from_messages( 77 | [ 78 | ("system", system_prompt), 79 | MessagesPlaceholder(variable_name="messages"), 80 | ( 81 | "system", 82 | "In light of the above conversation, please select one of the following options for which agent should be act or end next: {options}." 83 | ), 84 | ] 85 | ).partial(options=str(options), agents=", ".join(agents)) 86 | 87 | return ( 88 | prompt 89 | | llm.bind_functions(functions=[function_def], function_call="supervisor") 90 | | JsonOutputFunctionsParser() 91 | ) 92 | 93 | @tool 94 | def researcher(urls: List[str]) -> str: 95 | """Use requests and bs4 to scrape the provided web pages for detailed information.""" 96 | loader = WebBaseLoader(urls) 97 | docs = loader.load() 98 | return "\n\n".join( 99 | [ 100 | f'\n{doc.page_content}\n' 101 | for doc in docs 102 | ] 103 | ) 104 | 105 | 106 | @tool("Market Analyser") 107 | def analyze(content: str) -> str: 108 | """Market Analyser""" 109 | chat = ChatOpenAI() 110 | messages = [ 111 | SystemMessage( 112 | content="You are a market analyst specializing in e-commerce trends, tasked with identifying a winning product to sell on Amazon. " 113 | "Your goal is to leverage market analysis datas and your expertise to pinpoint a product that meets specific criteria for " 114 | "success in the highly competitive online marketplace " 115 | ), 116 | HumanMessage( 117 | content=content 118 | ), 119 | ] 120 | response = chat(messages) 121 | return response.content 122 | 123 | @tool("DropShipping_expert") 124 | def expert(content: str) -> str: 125 | """Execute a trade""" 126 | chat = ChatOpenAI() 127 | messages = [ 128 | SystemMessage( 129 | content="Act as an experienced DropShopping assistant.Your task is to identify Wining Product" 130 | "thorough examination of the product range and pricing" 131 | "Provide insights to help decide whether to start selling this product or not" 132 | ), 133 | HumanMessage( 134 | content=content 135 | ), 136 | ] 137 | response = chat(messages) 138 | return response.content 139 | 140 | 141 | 142 | 143 | llm = ChatOpenAI(model=OPENAI_MODEL) 144 | 145 | def scraper_agent() -> Runnable: 146 | prompt = ( 147 | "You are an amazon scraper." 148 | ) 149 | return create_agent(llm, [researcher], prompt) 150 | 151 | def analyzer_agent() -> Runnable: 152 | prompt = ( 153 | "you are analyser data that scrape from amazaon scraper i want you to help to find wining product" 154 | 155 | ) 156 | return create_agent(llm, [analyze], prompt) 157 | 158 | def Expert_agent() -> Runnable: 159 | prompt = ( 160 | "You are a Buyer, your job is to help me decide wthere i start selling product or not" 161 | ) 162 | return create_agent(llm, [expert], prompt) 163 | 164 | 165 | 166 | RESEARCHER = "RESEARCHER" 167 | ANALYZER = "Analyzer" 168 | EXPERT = "Expet" 169 | SUPERVISOR = "SUPERVISOR" 170 | 171 | agents = [RESEARCHER , ANALYZER, EXPERT] 172 | 173 | class AgentState(TypedDict): 174 | messages: Annotated[Sequence[BaseMessage], operator.add] 175 | next: str 176 | 177 | def scraper_node(state: AgentState) -> dict: 178 | result = scraper_agent().invoke(state) 179 | return {"messages": [HumanMessage(content=result["output"], name=RESEARCHER)]} 180 | 181 | def Analyzer_node(state: AgentState) -> dict: 182 | result = analyzer_agent().invoke(state) 183 | return {"messages": [HumanMessage(content=result["output"], name=ANALYZER)]} 184 | 185 | def Expert_node(state: AgentState) -> dict: 186 | result = Expert_agent().invoke(state) 187 | return {"messages": [HumanMessage(content=result["output"], name=EXPERT)]} 188 | 189 | def supervisor_node(state: AgentState) -> Runnable: 190 | return create_supervisor(llm, agents) 191 | 192 | 193 | workflow = StateGraph(AgentState) 194 | workflow.add_node(RESEARCHER, scraper_node) 195 | workflow.add_node(ANALYZER,Analyzer_node) 196 | workflow.add_node(EXPERT,Expert_node) 197 | workflow.add_node(SUPERVISOR, supervisor_node) 198 | 199 | workflow.add_edge(RESEARCHER, SUPERVISOR) 200 | workflow.add_edge(ANALYZER,SUPERVISOR) 201 | workflow.add_edge(EXPERT,SUPERVISOR) 202 | workflow.add_conditional_edges( 203 | SUPERVISOR, 204 | lambda x: x["next"], 205 | { 206 | RESEARCHER : RESEARCHER, 207 | ANALYZER: ANALYZER, 208 | EXPERT : EXPERT, 209 | "FINISH" : END 210 | } 211 | ) 212 | 213 | workflow.set_entry_point(SUPERVISOR) 214 | 215 | graph = workflow.compile() 216 | 217 | 218 | for s in graph.stream({"messages": [HumanMessage(content=user_input)]}): 219 | if "__end__" not in s: 220 | st.write(s) 221 | st.write("-----") 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | workflow = StateGraph(AgentState) 248 | 249 | workflow.add_node(RESEARCHER, scraper_node) 250 | workflow.add_node(ANALYZER, Analyzer_node) 251 | workflow.add_node(EXPERT, Expert_node) 252 | workflow.add_node(SUPERVISOR, supervisor_node) 253 | 254 | workflow.add_edge(RESEARCHER, SUPERVISOR) 255 | workflow.add_edge(ANALYZER, SUPERVISOR) 256 | workflow.add_edge(EXPERT, SUPERVISOR) 257 | workflow.add_conditional_edges( 258 | SUPERVISOR, 259 | lambda x: x["next"], 260 | { 261 | RESEARCHER : RESEARCHER , 262 | ANALYZER: ANALYZER, 263 | EXPERT: EXPERT, 264 | "FINISH": END 265 | } 266 | ) 267 | 268 | workflow.set_entry_point(SUPERVISOR) 269 | 270 | graph = workflow.compile() 271 | 272 | #what are some of the most popular stocks for 2024 should i invest in or stock that might have the biggest gains in the future 273 | #What are some of the stocks that had the greatest performance recently And are also the most liquid and highly traded ? 274 | # User_input = ( 275 | # "sCRAPE THIS game laptop and help me to find wining product" 276 | # ) 277 | 278 | for s in graph.stream({"messages": [HumanMessage(content=user_input)]}): 279 | if "__end__" not in s: 280 | st.write(s) 281 | st.write("----") 282 | 283 | 284 | if __name__ == "__main__": 285 | main() 286 | 287 | -------------------------------------------------------------------------------- /Stock.py: -------------------------------------------------------------------------------- 1 | import os 2 | from langchain.agents import AgentExecutor, create_openai_tools_agent 3 | from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser 4 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 5 | from langchain_openai import ChatOpenAI 6 | from langchain.tools import tool 7 | from langchain_community.tools.yahoo_finance_news import YahooFinanceNewsTool 8 | from langchain_core.messages import HumanMessage, SystemMessage 9 | import streamlit as st 10 | import operator 11 | from typing import Annotated, Sequence, TypedDict 12 | from langchain_core.messages import BaseMessage 13 | 14 | 15 | 16 | 17 | def main(): 18 | 19 | 20 | st.title("LangGraph + Function Call + YahooFinance 👾") 21 | # Add a sidebar for model selection 22 | OPENAI_MODEL = st.sidebar.selectbox( 23 | "Select Model", 24 | ["gpt-4-turbo-preview", "gpt-3.5-turbo", "gpt-3.5-turbo-instruct"] # Add your model options here 25 | ) 26 | 27 | api_key = st.sidebar.text_input("Enter your OpenAI API Key", type="password") 28 | 29 | if api_key: 30 | os.environ["OPENAI_API_KEY"] = api_key 31 | 32 | user_input = st.text_input("Enter your input here:") 33 | 34 | # Run the workflow 35 | if st.button("Run Workflow"): 36 | with st.spinner("Running Workflow..."): 37 | 38 | def create_agent(llm: ChatOpenAI, tools: list, system_prompt: str): 39 | prompt = ChatPromptTemplate.from_messages( 40 | [ 41 | ("system", system_prompt), 42 | MessagesPlaceholder(variable_name="messages"), 43 | MessagesPlaceholder(variable_name="agent_scratchpad"), 44 | ] 45 | ) 46 | agent = create_openai_tools_agent(llm, tools, prompt) 47 | return AgentExecutor(agent=agent, tools=tools) 48 | 49 | def create_supervisor(llm: ChatOpenAI, agents: list[str]): 50 | system_prompt = ( 51 | "You are the supervisor over the following agents: {agents}." 52 | " You are responsible for assigning tasks to each agent as requested by the user." 53 | " Each agent executes tasks according to their roles and responds with their results and status." 54 | " Please review the information and answer with the name of the agent to which the task should be assigned next." 55 | " Answer 'FINISH' if you are satisfied that you have fulfilled the user's request." 56 | ) 57 | 58 | options = ["FINISH"] + agents 59 | function_def = { 60 | "name": "supervisor", 61 | "description": "Select the next agent.", 62 | "parameters": { 63 | "type": "object", 64 | "properties": { 65 | "next": { 66 | "anyOf": [ 67 | {"enum": options}, 68 | ], 69 | } 70 | }, 71 | "required": ["next"], 72 | }, 73 | } 74 | 75 | prompt = ChatPromptTemplate.from_messages( 76 | [ 77 | ("system", system_prompt), 78 | MessagesPlaceholder(variable_name="messages"), 79 | ( 80 | "system", 81 | "In light of the above conversation, please select one of the following options for which agent should be act or end next: {options}." 82 | ), 83 | ] 84 | ).partial(options=str(options), agents=", ".join(agents)) 85 | 86 | return ( 87 | prompt 88 | | llm.bind_functions(functions=[function_def], function_call="supervisor") 89 | | JsonOutputFunctionsParser() 90 | ) 91 | 92 | 93 | @tool("Trading_Research") 94 | def researcher(query: str) -> str: 95 | """Research by Yahoo""" 96 | Yfinance = YahooFinanceNewsTool() 97 | return Yfinance.run(query) 98 | 99 | @tool("Market Analysist") 100 | def analyze(content: str) -> str: 101 | """Market Analyser""" 102 | chat = ChatOpenAI() 103 | messages = [ 104 | SystemMessage( 105 | content="Act as a day trading assistant. Your task is to identify trading assets that meet the specified{User_input}" 106 | "Utilize your expertise and available market analysis tools to scan, filter, and evaluate potential assets for trading." 107 | "Once identified, create a comprehensive list with supporting data for each asset, indicating why it meets the criteria. " 108 | "Ensure that all information is up-to-date and relevant to the current market conditions. " 109 | ), 110 | HumanMessage( 111 | content=content 112 | ), 113 | ] 114 | response = chat(messages) 115 | return response.content 116 | 117 | @tool("Trade Execution") 118 | def executer(content: str) -> str: 119 | """Execute a trade""" 120 | chat = ChatOpenAI() 121 | messages = [ 122 | SystemMessage( 123 | content="Act as an experienced trading assistant. Based on your comprehensive analysis of current market conditions," 124 | "historical data, and emerging trends, decide on optimal entry, stop-loss, and target points for a specified " 125 | "trading asset. Begin by thoroughly reviewing recent price action, key technical indicators, and relevant news" 126 | "that might influence the asset's direction." 127 | ), 128 | HumanMessage( 129 | content=content 130 | ), 131 | ] 132 | response = chat(messages) 133 | return response.content 134 | 135 | 136 | from langchain_core.runnables import Runnable 137 | 138 | llm = ChatOpenAI(model=OPENAI_MODEL) 139 | 140 | def researcher_agent() -> Runnable: 141 | prompt = ( 142 | "You are an Trader research assistant, you uses Yahoo Fiance News to find the most up-to-date and correct information." 143 | "Your research should be rigorous, data-driven, and well-documented" 144 | ) 145 | return create_agent(llm, [researcher], prompt) 146 | 147 | def analyzer_agent() -> Runnable: 148 | prompt = ( 149 | "As a Market Stock Analyzer, your main job is to study the stock market and " 150 | "help people make smart decisions about their investments " 151 | ) 152 | return create_agent(llm, [analyze], prompt) 153 | 154 | def executor_agent() -> Runnable: 155 | prompt = ( 156 | "You are a Executor in the stock market, your job is to help people invest their money wisely." 157 | "You study how the stock market works and figure out which companies are good to invest in." 158 | ) 159 | return create_agent(llm, [analyze], prompt) 160 | 161 | 162 | 163 | RESEARCHER = "RESEARCHER" 164 | ANALYZER = "Analyzer" 165 | EXECUTOR = "Executor" 166 | SUPERVISOR = "SUPERVISOR" 167 | 168 | agents = [RESEARCHER, ANALYZER, EXECUTOR] 169 | 170 | class AgentState(TypedDict): 171 | messages: Annotated[Sequence[BaseMessage], operator.add] 172 | next: str 173 | 174 | def researcher_node(state: AgentState) -> dict: 175 | result = researcher_agent().invoke(state) 176 | return {"messages": [HumanMessage(content=result["output"], name=RESEARCHER)]} 177 | 178 | def Analyzer_node(state: AgentState) -> dict: 179 | result = Analyzer_node().invoke(state) 180 | return {"messages": [HumanMessage(content=result['output'], name=ANALYZER)]} 181 | 182 | def Executor_node(state: AgentState) -> dict: 183 | result = executor_agent().invoke(state) 184 | return {"messages":[HumanMessage(content=result["output"], name=EXECUTOR)]} 185 | 186 | def supervisor_node(state: AgentState) -> Runnable: 187 | return create_supervisor(llm,agents) 188 | 189 | 190 | workflow = StateGraph(AgentState) 191 | 192 | workflow.add_node(RESEARCHER,researcher_node) 193 | workflow.add_node(ANALYZER,Analyzer_node) 194 | workflow.add_node(EXECUTOR, Executor_node) 195 | workflow.add_node(SUPERVISOR, supervisor_node) 196 | 197 | workflow.add_edge(RESEARCHER, SUPERVISOR) 198 | workflow.add_edge(ANALYZER, SUPERVISOR) 199 | workflow.add_edge(EXECUTOR,SUPERVISOR) 200 | 201 | workflow.add_conditional_edges( 202 | SUPERVISOR, 203 | lambda x: x["next"], 204 | { 205 | RESEARCHER: RESEARCHER, 206 | ANALYZER: ANALYZER, 207 | EXECUTOR : EXECUTOR, 208 | "FINISH" : END 209 | } 210 | ) 211 | 212 | workflow.set_entry_point(SUPERVISOR) 213 | 214 | graph = workflow.compile() 215 | 216 | for s in graph.stream({"messages" : [HumanMessage(content=user_input)]}): 217 | if "__end__" not in s: 218 | st.write(s) 219 | st.write("----") 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | def Analyzer_node(state: AgentState) -> dict: 248 | result = analyzer_agent().invoke(state) 249 | return {"messages": [HumanMessage(content=result["output"], name=ANALYZER)]} 250 | 251 | def Executor_node(state: AgentState) -> dict: 252 | result = executor_agent().invoke(state) 253 | return {"messages": [HumanMessage(content=result["output"], name=EXECUTOR)]} 254 | 255 | def supervisor_node(state: AgentState) -> Runnable: 256 | return create_supervisor(llm, agents) 257 | 258 | from langgraph.graph import StateGraph, END 259 | 260 | workflow = StateGraph(AgentState) 261 | 262 | workflow.add_node(RESEARCHER, researcher_node) 263 | workflow.add_node(ANALYZER, Analyzer_node) 264 | workflow.add_node(EXECUTOR, Executor_node) 265 | workflow.add_node(SUPERVISOR, supervisor_node) 266 | 267 | workflow.add_edge(RESEARCHER, SUPERVISOR) 268 | workflow.add_edge(ANALYZER, SUPERVISOR) 269 | workflow.add_edge(EXECUTOR, SUPERVISOR) 270 | workflow.add_conditional_edges( 271 | SUPERVISOR, 272 | lambda x: x["next"], 273 | { 274 | RESEARCHER: RESEARCHER, 275 | ANALYZER: ANALYZER, 276 | EXECUTOR: EXECUTOR, 277 | "FINISH": END 278 | } 279 | ) 280 | 281 | workflow.set_entry_point(SUPERVISOR) 282 | 283 | graph = workflow.compile() 284 | 285 | #what are some of the most popular stocks for 2024 should i invest in or stock that might have the biggest gains in the future 286 | #What are some of the stocks that had the greatest performance recently And are also the most liquid and highly traded ? 287 | # user_input = ( 288 | # "what are some of the most popular stocks for 2023 should i invest in or stock that might have the biggest gains in the future" 289 | # ) 290 | 291 | for s in graph.stream({"messages": [HumanMessage(content=user_input)]}): 292 | if "__end__" not in s: 293 | st.write(s) 294 | st.write("-----") 295 | 296 | 297 | if __name__ == "__main__": 298 | main() -------------------------------------------------------------------------------- /adaptive_rag.py: -------------------------------------------------------------------------------- 1 | from langchain.text_splitter import RecursiveCharacterTextSplitter 2 | from langchain_community.vectorstores import Chroma 3 | from langchain_community.embeddings import GPT4AllEmbeddings 4 | from langchain.prompts import PromptTemplate 5 | from langchain_community.chat_models import ChatOllama 6 | from langchain_core.output_parsers import JsonOutputParser 7 | from langchain.prompts import PromptTemplate 8 | from langchain_community.chat_models import ChatOllama 9 | from langchain_core.output_parsers import JsonOutputParser 10 | from langchain import hub 11 | from langchain_community.chat_models import ChatOllama 12 | from langchain_core.output_parsers import StrOutputParser 13 | from typing_extensions import TypedDict 14 | from typing import List 15 | from langchain.schema import Document 16 | from langgraph.graph import END, StateGraph 17 | from langchain_community.tools.tavily_search import TavilySearchResults 18 | from langchain.schema import Document 19 | from langchain_community.document_loaders import PyPDFLoader 20 | import streamlit as st 21 | import os 22 | local_llm = "llama3" 23 | tavily_api_key = os.environ['TAVILY_API_KEY'] = '' 24 | st.title("Multi-PDF ChatBot using LLAMA3 & Adaptive RAG") 25 | user_input = st.text_input("Question:", placeholder="Ask about your PDF", key='input') 26 | 27 | with st.sidebar: 28 | uploaded_files = st.file_uploader("Upload your file", type=['pdf'], accept_multiple_files=True) 29 | process = st.button("Process") 30 | 31 | if process: 32 | if not uploaded_files: 33 | st.warning("Please upload at least one PDF file.") 34 | st.stop() 35 | 36 | # Ensure the temp directory exists 37 | temp_dir = 'C:/temp/' 38 | if not os.path.exists(temp_dir): 39 | os.makedirs(temp_dir) 40 | 41 | # Process each uploaded file 42 | for uploaded_file in uploaded_files: 43 | temp_file_path = os.path.join(temp_dir, uploaded_file.name) 44 | 45 | # Save the file to disk 46 | with open(temp_file_path, "wb") as file: 47 | file.write(uploaded_file.getbuffer()) # Use getbuffer() for Streamlit's UploadedFile 48 | 49 | # Load the PDF using PyPDFLoader 50 | try: 51 | loader = PyPDFLoader(temp_file_path) 52 | data = loader.load() # Assuming loader.load() is the correct method call 53 | st.write(f"Data loaded for {uploaded_file.name}") 54 | except Exception as e: 55 | st.error(f"Failed to load {uploaded_file.name}: {str(e)}") 56 | 57 | 58 | text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( 59 | chunk_size=250, chunk_overlap=0 60 | ) 61 | text_chunks = text_splitter.split_documents(data) 62 | 63 | # Add to vectorDB 64 | vectorstore = Chroma.from_documents( 65 | documents=text_chunks, 66 | collection_name="rag-chroma", 67 | embedding=GPT4AllEmbeddings(), 68 | ) 69 | retriever = vectorstore.as_retriever() 70 | llm = ChatOllama(model=local_llm, format="json", temperature=0) 71 | 72 | prompt = PromptTemplate( 73 | template="""You are an expert at routing a user question to a vectorstore or web search. \n 74 | Use the vectorstore for questions on LLM agents, prompt engineering, and adversarial attacks. \n 75 | You do not need to be stringent with the keywords in the question related to these topics. \n 76 | Otherwise, use web-search. Give a binary choice 'web_search' or 'vectorstore' based on the question. \n 77 | Return the a JSON with a single key 'datasource' and no premable or explaination. \n 78 | Question to route: {question}""", 79 | input_variables=["question"], 80 | ) 81 | 82 | question_router = prompt | llm | JsonOutputParser() 83 | question = "llm agent memory" 84 | docs = retriever.get_relevant_documents(question) 85 | doc_txt = docs[1].page_content 86 | question_router.invoke({"question": question}) 87 | 88 | llm = ChatOllama(model=local_llm, format="json", temperature=0) 89 | 90 | prompt = PromptTemplate( 91 | template="""You are a grader assessing relevance of a retrieved document to a user question. \n 92 | Here is the retrieved document: \n\n {document} \n\n 93 | Here is the user question: {question} \n 94 | If the document contains keywords related to the user question, grade it as relevant. \n 95 | It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \n 96 | Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. \n 97 | Provide the binary score as a JSON with a single key 'score' and no premable or explaination.""", 98 | input_variables=["question", "document"], 99 | ) 100 | 101 | retrieval_grader = prompt | llm | JsonOutputParser() 102 | question = "agent memory" 103 | docs = retriever.get_relevant_documents(question) 104 | doc_txt = docs[1].page_content 105 | st.write(retrieval_grader.invoke({"question": question, "document": doc_txt})) 106 | 107 | prompt = hub.pull("rlm/rag-prompt") 108 | 109 | # LLM 110 | llm = ChatOllama(model=local_llm, temperature=0) 111 | 112 | # Post-processing 113 | def format_docs(docs): 114 | return "\n\n".join(doc.page_content for doc in docs) 115 | 116 | # Chain 117 | rag_chain = prompt | llm | StrOutputParser() 118 | 119 | # Run 120 | question = "agent memory" 121 | generation = rag_chain.invoke({"context": docs, "question": question}) 122 | st.write(generation) 123 | 124 | llm = ChatOllama(model=local_llm, format="json", temperature=0) 125 | 126 | # Prompt 127 | prompt = PromptTemplate( 128 | template="""You are a grader assessing whether an answer is grounded in / supported by a set of facts. \n 129 | Here are the facts: 130 | \n ------- \n 131 | {documents} 132 | \n ------- \n 133 | Here is the answer: {generation} 134 | Give a binary score 'yes' or 'no' score to indicate whether the answer is grounded in / supported by a set of facts. \n 135 | Provide the binary score as a JSON with a single key 'score' and no preamble or explanation.""", 136 | input_variables=["generation", "documents"], 137 | ) 138 | 139 | hallucination_grader = prompt | llm | JsonOutputParser() 140 | hallucination_grader.invoke({"documents": docs, "generation": generation}) 141 | 142 | llm = ChatOllama(model=local_llm, format="json", temperature=0) 143 | 144 | # Prompt 145 | prompt = PromptTemplate( 146 | template="""You are a grader assessing whether an answer is useful to resolve a question. \n 147 | Here is the answer: 148 | \n ------- \n 149 | {generation} 150 | \n ------- \n 151 | Here is the question: {question} 152 | Give a binary score 'yes' or 'no' to indicate whether the answer is useful to resolve a question. \n 153 | Provide the binary score as a JSON with a single key 'score' and no preamble or explanation.""", 154 | input_variables=["generation", "question"], 155 | ) 156 | 157 | answer_grader = prompt | llm | JsonOutputParser() 158 | answer_grader.invoke({"question": question,"generation": generation}) 159 | 160 | llm = ChatOllama(model=local_llm, temperature=0) 161 | 162 | # Prompt 163 | re_write_prompt = PromptTemplate( 164 | template="""You a question re-writer that converts an input question to a better version that is optimized \n 165 | for vectorstore retrieval. Look at the initial and formulate an improved question. \n 166 | Here is the initial question: \n\n {question}. Improved question with no preamble: \n """, 167 | input_variables=["generation", "question"], 168 | ) 169 | 170 | question_rewriter = re_write_prompt | llm | StrOutputParser() 171 | question_rewriter.invoke({"question": question}) 172 | 173 | 174 | web_search_tool = TavilySearchResults(k=3,tavily_api_key=tavily_api_key) 175 | 176 | 177 | class GraphState(TypedDict): 178 | """ 179 | Represents the state of our graph. 180 | 181 | Attributes: 182 | question: question 183 | generation: LLM generation 184 | documents: list of documents 185 | """ 186 | question : str 187 | generation : str 188 | documents : List[str] 189 | 190 | def retrieve(state): 191 | """ 192 | Retrieve documents 193 | 194 | Args: 195 | state (dict): The current graph state 196 | 197 | Returns: 198 | state (dict): New key added to state, documents, that contains retrieved documents 199 | """ 200 | st.write("---RETRIEVE---") 201 | question = state["question"] 202 | 203 | # Retrieval 204 | documents = retriever.get_relevant_documents(question) 205 | return {"documents": documents, "question": question} 206 | 207 | def generate(state): 208 | """ 209 | Generate answer 210 | 211 | Args: 212 | state (dict): The current graph state 213 | 214 | Returns: 215 | state (dict): New key added to state, generation, that contains LLM generation 216 | """ 217 | st.write("---GENERATE---") 218 | question = state["question"] 219 | documents = state["documents"] 220 | 221 | # RAG generation 222 | generation = rag_chain.invoke({"context": documents, "question": question}) 223 | return {"documents": documents, "question": question, "generation": generation} 224 | 225 | def grade_documents(state): 226 | """ 227 | Determines whether the retrieved documents are relevant to the question. 228 | 229 | Args: 230 | state (dict): The current graph state 231 | 232 | Returns: 233 | state (dict): Updates documents key with only filtered relevant documents 234 | """ 235 | 236 | st.write("---CHECK DOCUMENT RELEVANCE TO QUESTION---") 237 | question = state["question"] 238 | documents = state["documents"] 239 | 240 | # Score each doc 241 | filtered_docs = [] 242 | for d in documents: 243 | score = retrieval_grader.invoke({"question": question, "document": d.page_content}) 244 | grade = score['score'] 245 | if grade == "yes": 246 | st.write("---GRADE: DOCUMENT RELEVANT---") 247 | filtered_docs.append(d) 248 | else: 249 | st.write("---GRADE: DOCUMENT NOT RELEVANT---") 250 | continue 251 | return {"documents": filtered_docs, "question": question} 252 | 253 | def transform_query(state): 254 | """ 255 | Transform the query to produce a better question. 256 | 257 | Args: 258 | state (dict): The current graph state 259 | 260 | Returns: 261 | state (dict): Updates question key with a re-phrased question 262 | """ 263 | 264 | st.write("---TRANSFORM QUERY---") 265 | question = state["question"] 266 | documents = state["documents"] 267 | 268 | # Re-write question 269 | better_question = question_rewriter.invoke({"question": question}) 270 | return {"documents": documents, "question": better_question} 271 | 272 | def web_search(state): 273 | """ 274 | Web search based on the re-phrased question. 275 | 276 | Args: 277 | state (dict): The current graph state 278 | 279 | Returns: 280 | state (dict): Updates documents key with appended web results 281 | """ 282 | 283 | st.write("---WEB SEARCH---") 284 | question = state["question"] 285 | 286 | # Web search 287 | docs = web_search_tool.invoke({"query": question}) 288 | web_results = "\n".join([d["content"] for d in docs]) 289 | web_results = Document(page_content=web_results) 290 | 291 | return {"documents": web_results, "question": question} 292 | 293 | ### Edges ### 294 | 295 | def route_question(state): 296 | """ 297 | Route question to web search or RAG. 298 | 299 | Args: 300 | state (dict): The current graph state 301 | 302 | Returns: 303 | str: Next node to call 304 | """ 305 | 306 | st.write("---ROUTE QUESTION---") 307 | question = state["question"] 308 | st.write(question) 309 | source = question_router.invoke({"question": question}) 310 | st.write(source) 311 | st.write(source['datasource']) 312 | if source['datasource'] == 'web_search': 313 | st.write("---ROUTE QUESTION TO WEB SEARCH---") 314 | return "web_search" 315 | elif source['datasource'] == 'vectorstore': 316 | st.write("---ROUTE QUESTION TO RAG---") 317 | return "vectorstore" 318 | 319 | def decide_to_generate(state): 320 | """ 321 | Determines whether to generate an answer, or re-generate a question. 322 | 323 | Args: 324 | state (dict): The current graph state 325 | 326 | Returns: 327 | str: Binary decision for next node to call 328 | """ 329 | 330 | st.write("---ASSESS GRADED DOCUMENTS---") 331 | question = state["question"] 332 | filtered_documents = state["documents"] 333 | 334 | if not filtered_documents: 335 | # All documents have been filtered check_relevance 336 | # We will re-generate a new query 337 | st.write("---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---") 338 | return "transform_query" 339 | else: 340 | # We have relevant documents, so generate answer 341 | st.write("---DECISION: GENERATE---") 342 | return "generate" 343 | 344 | def grade_generation_v_documents_and_question(state): 345 | """ 346 | Determines whether the generation is grounded in the document and answers question. 347 | 348 | Args: 349 | state (dict): The current graph state 350 | 351 | Returns: 352 | str: Decision for next node to call 353 | """ 354 | 355 | st.write("---CHECK HALLUCINATIONS---") 356 | question = state["question"] 357 | documents = state["documents"] 358 | generation = state["generation"] 359 | 360 | score = hallucination_grader.invoke({"documents": documents, "generation": generation}) 361 | grade = score['score'] 362 | 363 | # Check hallucination 364 | if grade == "yes": 365 | st.write("---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---") 366 | # Check question-answering 367 | st.write("---GRADE GENERATION vs QUESTION---") 368 | score = answer_grader.invoke({"question": question,"generation": generation}) 369 | grade = score['score'] 370 | if grade == "yes": 371 | st.write("---DECISION: GENERATION ADDRESSES QUESTION---") 372 | return "useful" 373 | else: 374 | st.write("---DECISION: GENERATION DOES NOT ADDRESS QUESTION---") 375 | return "not useful" 376 | else: 377 | st.write("---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---") 378 | return "not supported" 379 | 380 | workflow = StateGraph(GraphState) 381 | 382 | # Define the nodes 383 | workflow.add_node("web_search", web_search) # web search 384 | workflow.add_node("retrieve", retrieve) # retrieve 385 | workflow.add_node("grade_documents", grade_documents) # grade documents 386 | workflow.add_node("generate", generate) # generatae 387 | workflow.add_node("transform_query", transform_query) # transform_query 388 | 389 | # Build graph 390 | workflow.set_conditional_entry_point( 391 | route_question, 392 | { 393 | "web_search": "web_search", 394 | "vectorstore": "retrieve", 395 | }, 396 | ) 397 | workflow.add_edge("web_search", "generate") 398 | workflow.add_edge("retrieve", "grade_documents") 399 | workflow.add_conditional_edges( 400 | "grade_documents", 401 | decide_to_generate, 402 | { 403 | "transform_query": "transform_query", 404 | "generate": "generate", 405 | }, 406 | ) 407 | workflow.add_edge("transform_query", "retrieve") 408 | workflow.add_conditional_edges( 409 | "generate", 410 | grade_generation_v_documents_and_question, 411 | { 412 | "not supported": "generate", 413 | "useful": END, 414 | "not useful": "transform_query", 415 | }, 416 | ) 417 | 418 | # Compile 419 | app = workflow.compile() 420 | 421 | inputs = {"question": user_input} 422 | for output in app.stream(inputs): 423 | for key, value in output.items(): 424 | # Node 425 | st.write(f"Node '{key}':") 426 | # Optional: print full state at each node 427 | # pprint.pprint(value["keys"], indent=2, width=80, depth=None) 428 | print("\n---\n") 429 | 430 | # Final generation 431 | st.write(value["generation"]) -------------------------------------------------------------------------------- /CRAG.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from langchain import hub 3 | from langchain.output_parsers import PydanticOutputParser 4 | from langchain_core.output_parsers import StrOutputParser 5 | from langchain.schema import Document 6 | from langchain_core.pydantic_v1 import BaseModel, Field 7 | from langchain_community.document_loaders import WebBaseLoader 8 | from langchain_community.tools.tavily_search import TavilySearchResults 9 | from langchain_community.vectorstores import Chroma 10 | from langchain_community.chat_models import ChatOllama 11 | from langchain_community.embeddings import GPT4AllEmbeddings 12 | from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings 13 | from langchain_google_genai import ChatGoogleGenerativeAI 14 | from langchain_openai import ChatOpenAI, OpenAIEmbeddings 15 | from langchain.text_splitter import RecursiveCharacterTextSplitter 16 | from langgraph.graph import END, StateGraph 17 | from typing import Dict, TypedDict 18 | from langchain.prompts import PromptTemplate 19 | import pprint 20 | import os 21 | # Load environment variables 22 | load_dotenv() 23 | 24 | 25 | run_local = 'No' 26 | models = "openai" 27 | openai_api_key = "Your_API_KEY" 28 | google_api_key = "Your_API_KEY" 29 | local_llm = 'Solar' 30 | os.environ["TAVILY_API_KEY"] = "" 31 | 32 | 33 | # Split documents 34 | url = 'https://lilianweng.github.io/posts/2023-06-23-agent/' 35 | loader = WebBaseLoader(url) 36 | docs = loader.load() 37 | 38 | 39 | # Split 40 | text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( 41 | chunk_size=500, chunk_overlap=100 42 | ) 43 | all_splits = text_splitter.split_documents(docs) 44 | 45 | 46 | # Embed and index 47 | if run_local == 'Yes': 48 | embeddings = GPT4AllEmbeddings() 49 | elif models == 'openai': 50 | embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key) 51 | else: 52 | embeddings = GoogleGenerativeAIEmbeddings( 53 | model="models/embedding-001", google_api_key=google_api_key 54 | ) 55 | 56 | # Index 57 | vectorstore = Chroma.from_documents( 58 | documents=all_splits, 59 | collection_name="rag-chroma", 60 | embedding=embeddings, 61 | ) 62 | retriever = vectorstore.as_retriever() 63 | print(retriever) 64 | 65 | ################################################################### 66 | 67 | 68 | class GraphState(TypedDict): 69 | """ 70 | Represents the state of our graph. 71 | 72 | Attributes: 73 | keys: A dictionary where each key is a string. 74 | """ 75 | 76 | keys: Dict[str, any] 77 | ############################################################# 78 | 79 | 80 | ### Nodes ### 81 | 82 | def retrieve(state): 83 | """ 84 | Retrieve documents 85 | 86 | Args: 87 | state (dict): The current graph state 88 | 89 | Returns: 90 | state (dict): New key added to state, documents, that contains retrieved documents 91 | """ 92 | print("---RETRIEVE---") 93 | state_dict = state["keys"] 94 | question = state_dict["question"] 95 | local = state_dict["local"] 96 | documents = retriever.get_relevant_documents(question) 97 | return {"keys": {"documents": documents, "local": local, "question": question}} 98 | 99 | def generate(state): 100 | """ 101 | Generate answer 102 | 103 | Args: 104 | state (dict): The current graph state 105 | 106 | Returns: 107 | state (dict): New key added to state, generation, that contains LLM generation 108 | """ 109 | print("---GENERATE---") 110 | state_dict = state["keys"] 111 | question = state_dict["question"] 112 | documents = state_dict["documents"] 113 | 114 | # Prompt 115 | prompt = hub.pull("rlm/rag-prompt") 116 | 117 | # LLM Setup 118 | if run_local == "Yes": 119 | llm = ChatOllama(model=local_llm, 120 | temperature=0) 121 | elif models == "openai" : 122 | llm = ChatOpenAI( 123 | model="gpt-4-0125-preview", 124 | temperature=0 , 125 | openai_api_key=openai_api_key 126 | ) 127 | else: 128 | llm = ChatGoogleGenerativeAI(model="gemini-pro", 129 | google_api_key=google_api_key, 130 | convert_system_message_to_human = True, 131 | verbose = True, 132 | ) 133 | 134 | # Post-processing 135 | def format_docs(docs): 136 | return "\n\n".join(doc.page_content for doc in docs) 137 | 138 | # Chain 139 | rag_chain = prompt | llm | StrOutputParser() 140 | 141 | # Run 142 | generation = rag_chain.invoke({"context": documents, "question": question}) 143 | return { 144 | "keys": {"documents": documents, "question": question, "generation": generation} 145 | } 146 | 147 | def grade_documents(state): 148 | """ 149 | Determines whether the retrieved documents are relevant to the question. 150 | 151 | Args: 152 | state (dict): The current graph state 153 | 154 | Returns: 155 | state (dict): Updates documents key with relevant documents 156 | """ 157 | 158 | print("---CHECK RELEVANCE---") 159 | state_dict = state["keys"] 160 | question = state_dict["question"] 161 | documents = state_dict["documents"] 162 | local = state_dict["local"] 163 | 164 | # LLM 165 | if run_local == "Yes": 166 | llm = ChatOllama(model=local_llm, 167 | temperature=0) 168 | elif models == "openai" : 169 | llm = ChatOpenAI( 170 | model="gpt-4-0125-preview", 171 | temperature=0 , 172 | openai_api_key=openai_api_key 173 | ) 174 | else: 175 | llm = ChatGoogleGenerativeAI(model="gemini-pro", 176 | google_api_key=google_api_key, 177 | convert_system_message_to_human = True, 178 | verbose = True, 179 | ) 180 | # Data model 181 | class grade(BaseModel): 182 | """Binary score for relevance check.""" 183 | 184 | score: str = Field(description="Relevance score 'yes' or 'no'") 185 | 186 | # Set up a parser + inject instructions into the prompt template. 187 | parser = PydanticOutputParser(pydantic_object=grade) 188 | 189 | from langchain_core.output_parsers import JsonOutputParser 190 | 191 | parser = JsonOutputParser(pydantic_object=grade) 192 | 193 | prompt = PromptTemplate( 194 | template="""You are a grader assessing relevance of a retrieved document to a user question. \n 195 | Here is the retrieved document: \n\n {context} \n\n 196 | Here is the user question: {question} \n 197 | If the document contains keywords related to the user question, grade it as relevant. \n 198 | It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \n 199 | Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. \n 200 | Provide the binary score as a JSON with no premable or explaination and use these instructons to format the output: {format_instructions}""", 201 | input_variables=["query"], 202 | partial_variables={"format_instructions": parser.get_format_instructions()}, 203 | ) 204 | 205 | chain = prompt | llm | parser 206 | 207 | # Score 208 | filtered_docs = [] 209 | search = "No" # Default do not opt for web search to supplement retrieval 210 | for d in documents: 211 | score = chain.invoke( 212 | { 213 | "question": question, 214 | "context": d.page_content, 215 | "format_instructions": parser.get_format_instructions(), 216 | } 217 | ) 218 | grade = score["score"] 219 | if grade == "yes": 220 | print("---GRADE: DOCUMENT RELEVANT---") 221 | filtered_docs.append(d) 222 | else: 223 | print("---GRADE: DOCUMENT NOT RELEVANT---") 224 | search = "Yes" # Perform web search 225 | continue 226 | 227 | return { 228 | "keys": { 229 | "documents": filtered_docs, 230 | "question": question, 231 | "local": local, 232 | "run_web_search": search, 233 | } 234 | } 235 | def transform_query(state): 236 | """ 237 | Transform the query to produce a better question. 238 | 239 | Args: 240 | state (dict): The current graph state 241 | 242 | Returns: 243 | state (dict): Updates question key with a re-phrased question 244 | """ 245 | 246 | print("---TRANSFORM QUERY---") 247 | state_dict = state["keys"] 248 | question = state_dict["question"] 249 | documents = state_dict["documents"] 250 | local = state_dict["local"] 251 | 252 | # Create a prompt template with format instructions and the query 253 | prompt = PromptTemplate( 254 | template="""You are generating questions that is well optimized for retrieval. \n 255 | Look at the input and try to reason about the underlying sematic intent / meaning. \n 256 | Here is the initial question: 257 | \n ------- \n 258 | {question} 259 | \n ------- \n 260 | Provide an improved question without any premable, only respond with the updated question: """, 261 | input_variables=["question"], 262 | ) 263 | 264 | # Grader 265 | # LLM 266 | if run_local == "Yes": 267 | llm = ChatOllama(model=local_llm, 268 | temperature=0) 269 | elif models == "openai" : 270 | llm = ChatOpenAI( 271 | model="gpt-4-0125-preview", 272 | temperature=0 , 273 | openai_api_key=openai_api_key 274 | ) 275 | else: 276 | llm = ChatGoogleGenerativeAI(model="gemini-pro", 277 | google_api_key=google_api_key, 278 | convert_system_message_to_human = True, 279 | verbose = True, 280 | ) 281 | # Prompt 282 | chain = prompt | llm | StrOutputParser() 283 | better_question = chain.invoke({"question": question}) 284 | 285 | return { 286 | "keys": {"documents": documents, "question": better_question, "local": local} 287 | } 288 | 289 | def web_search(state): 290 | """ 291 | Web search based on the re-phrased question using Tavily API. 292 | 293 | Args: 294 | state (dict): The current graph state 295 | 296 | Returns: 297 | state (dict): Web results appended to documents. 298 | """ 299 | 300 | print("---WEB SEARCH---") 301 | state_dict = state["keys"] 302 | question = state_dict["question"] 303 | documents = state_dict["documents"] 304 | local = state_dict["local"] 305 | try: 306 | tool = TavilySearchResults() 307 | docs = tool.invoke({"query": question}) 308 | web_results = "\n".join([d["content"] for d in docs]) 309 | web_results = Document(page_content=web_results) 310 | documents.append(web_results) 311 | except Exception as error: 312 | print(error) 313 | 314 | return {"keys": {"documents": documents, "local": local, "question": question}} 315 | 316 | ### Edges 317 | 318 | 319 | def decide_to_generate(state): 320 | """ 321 | Determines whether to generate an answer or re-generate a question for web search. 322 | 323 | Args: 324 | state (dict): The current state of the agent, including all keys. 325 | 326 | Returns: 327 | str: Next node to call 328 | """ 329 | 330 | print("---DECIDE TO GENERATE---") 331 | state_dict = state["keys"] 332 | question = state_dict["question"] 333 | filtered_documents = state_dict["documents"] 334 | search = state_dict["run_web_search"] 335 | 336 | if search == "Yes": 337 | # All documents have been filtered check_relevance 338 | # We will re-generate a new query 339 | print("---DECISION: TRANSFORM QUERY and RUN WEB SEARCH---") 340 | return "transform_query" 341 | else: 342 | # We have relevant documents, so generate answer 343 | print("---DECISION: GENERATE---") 344 | return "generate" 345 | 346 | workflow = StateGraph(GraphState) 347 | 348 | # Define the nodes 349 | workflow.add_node("retrieve", retrieve) # retrieve 350 | workflow.add_node("grade_documents", grade_documents) # grade documents 351 | workflow.add_node("generate", generate) # generatae 352 | workflow.add_node("transform_query", transform_query) # transform_query 353 | workflow.add_node("web_search", web_search) # web search 354 | 355 | # Build graph 356 | workflow.set_entry_point("retrieve") 357 | workflow.add_edge("retrieve", "grade_documents") 358 | workflow.add_conditional_edges( 359 | "grade_documents", 360 | decide_to_generate, 361 | { 362 | "transform_query": "transform_query", 363 | "generate": "generate", 364 | }, 365 | ) 366 | workflow.add_edge("transform_query", "web_search") 367 | workflow.add_edge("web_search", "generate") 368 | workflow.add_edge("generate", END) 369 | 370 | # Compile 371 | app = workflow.compile() 372 | 373 | # Run 374 | inputs = { 375 | "keys": { 376 | "question": 'Explain how the different types of agent memory work?', 377 | "local": run_local, 378 | } 379 | } 380 | for output in app.stream(inputs): 381 | for key, value in output.items(): 382 | # Node 383 | print(f"Node '{key}':") 384 | # Optional: print full state at each node 385 | # pprint.pprint(value["keys"], indent=2, width=80, depth=None) 386 | pprint.pprint("\n---\n") 387 | 388 | # Final generation 389 | pprint.pprint(value['keys']['generation']) 390 | 391 | import os 392 | import requests 393 | from bs4 import BeautifulSoup 394 | from langchain.agents import AgentExecutor, create_openai_tools_agent 395 | from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser 396 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 397 | from langchain_openai import ChatOpenAI 398 | from langchain.tools import tool 399 | from langchain_core.messages import BaseMessage 400 | from langchain_core.messages import HumanMessage, SystemMessage 401 | from langgraph.graph import StateGraph, END 402 | from typing import Annotated, Sequence, TypedDict 403 | from langchain_core.runnables import Runnable 404 | import operator 405 | import streamlit as st 406 | 407 | 408 | def main(): 409 | 410 | 411 | st.title("LangGraph + Function Call + Amazaon Scraper 👾") 412 | # Add a sidebar for model selection 413 | OPENAI_MODEL = st.sidebar.selectbox( 414 | "Select Model", 415 | ["gpt-4-turbo-preview", "gpt-3.5-turbo", "gpt-3.5-turbo-instruct"] # Add your model options here 416 | ) 417 | 418 | api_key = st.sidebar.text_input("Enter your OpenAI API Key", type="password") 419 | 420 | if api_key: 421 | os.environ["OPENAI_API_KEY"] = api_key 422 | 423 | user_input = st.text_input("Enter your input here:") 424 | 425 | # Run the workflow 426 | if st.button("Run Workflow"): 427 | with st.spinner("Running Workflow..."): 428 | 429 | def create_agent(llm: ChatOpenAI, tools: list, system_prompt: str): 430 | prompt = ChatPromptTemplate.from_messages( 431 | [ 432 | ("system", system_prompt), 433 | MessagesPlaceholder(variable_name="messages"), 434 | MessagesPlaceholder(variable_name="agent_scratchpad"), 435 | ] 436 | ) 437 | agent = create_openai_tools_agent(llm, tools, prompt) 438 | return AgentExecutor(agent=agent, tools=tools) 439 | 440 | def create_supervisor(llm: ChatOpenAI, agents: list[str]): 441 | system_prompt = ( 442 | f"You are the supervisor over the following agents: {', '.join(agents)}. " 443 | "You are responsible for assigning tasks to each agent as requested by the user. " 444 | "Each agent executes tasks according to their roles and responds with their results and status. " 445 | "Please review the information and answer with the name of the agent to which the task should be assigned next. " 446 | "Answer 'FINISH' if you are satisfied that you have fulfilled the user's request." 447 | ) 448 | 449 | options = ["FINISH"] + agents 450 | function_def = { 451 | "name": "supervisor", 452 | "description": "Select the next agent.", 453 | "parameters": { 454 | "type": "object", 455 | "properties": { 456 | "next": { 457 | "anyOf": [ 458 | {"enum": options}, 459 | ], 460 | } 461 | }, 462 | "required": ["next"], 463 | }, 464 | } 465 | 466 | prompt = ChatPromptTemplate.from_messages( 467 | [ 468 | ("system", system_prompt), 469 | MessagesPlaceholder(variable_name="messages"), 470 | ( 471 | "system", 472 | "In light of the above conversation, please select one of the following options for which agent should act or end next: {options}." 473 | ), 474 | ] 475 | ).partial(options=str(options), agents=", ".join(agents)) 476 | 477 | return ( 478 | prompt 479 | | llm.bind_functions(functions=[function_def], function_call="supervisor") 480 | | JsonOutputFunctionsParser() 481 | ) 482 | 483 | def researcher(query): 484 | 485 | """ 486 | Scrape product titles and prices from the given Amazon URL. 487 | """ 488 | url = f"https://www.amazon.com/s?k={query}" 489 | headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'} 490 | page = requests.get(url, headers=headers) 491 | soup = BeautifulSoup(page.content, "html.parser") 492 | 493 | product_details = [] 494 | 495 | # Find all the product containers 496 | product_containers = soup.find_all("div", {"data-component-type": "s-search-result"}) 497 | 498 | for product in product_containers: 499 | product_info = {} 500 | 501 | # Obtain title of the product 502 | title = product.find("span", {"class": "a-size-medium"}) 503 | if title: 504 | product_info['title'] = title.get_text(strip=True) 505 | 506 | # Obtain price of the product 507 | price = product.find("span", {"class": "a-price-whole"}) 508 | if price: 509 | product_info['price'] = price.get_text(strip=True) 510 | 511 | # Add more details as needed 512 | 513 | product_details.append(product_info) 514 | 515 | return product_details 516 | 517 | @tool("Amazon_Research") 518 | def researcher_tool(query: str) -> str: 519 | """Research by Scraper""" 520 | func = lambda word: researcher(word) 521 | 522 | return func 523 | 524 | @tool("Market Analyser") 525 | def analyze_tool(content: str) -> str: 526 | """Market Analyser""" 527 | chat = ChatOpenAI() 528 | messages = [ 529 | SystemMessage( 530 | content="You are a market analyst specializing in e-commerce trends, tasked with identifying a winning product to sell on Amazon. " 531 | "Your goal is to leverage market analysis data and your expertise to pinpoint a product that meets specific criteria for " 532 | "success in the highly competitive online marketplace " 533 | ), 534 | HumanMessage( 535 | content=content 536 | ), 537 | ] 538 | response = chat(messages) 539 | return response.content 540 | 541 | @tool("DropShipping_expert") 542 | def expert_tool(content: str) -> str: 543 | """Execute a trade""" 544 | chat = ChatOpenAI() 545 | messages = [ 546 | SystemMessage( 547 | content="Act as an experienced DropShipping assistant. Your task is to identify Winning Product " 548 | "through examination of the product range and pricing. " 549 | "Provide insights to help decide whether to start selling this product or not." 550 | ), 551 | HumanMessage( 552 | content=content 553 | ), 554 | ] 555 | response = chat(messages) 556 | return response.content 557 | 558 | llm = ChatOpenAI(model=OPENAI_MODEL) 559 | 560 | def scraper_agent() -> Runnable: 561 | prompt = ( 562 | "You are an Amazon scraper." 563 | ) 564 | return create_agent(llm, [researcher_tool], prompt) 565 | 566 | def analyzer_agent() -> Runnable: 567 | prompt = ( 568 | "You are analyzing data scraped from Amazon. I want you to help find a winning product." 569 | ) 570 | return create_agent(llm, [analyze_tool], prompt) 571 | 572 | def expert_agent() -> Runnable: 573 | prompt = ( 574 | "You are a buyer. Your job is to help me decide whether to start selling a product or not." 575 | ) 576 | return create_agent(llm, [expert_tool], prompt) 577 | 578 | RESEARCHER = "RESEARCHER" 579 | ANALYZER = "Analyzer" 580 | EXPERT = "Expert" 581 | SUPERVISOR = "SUPERVISOR" 582 | 583 | agents = [RESEARCHER, ANALYZER, EXPERT] 584 | 585 | class AgentState(TypedDict): 586 | messages: Annotated[Sequence[BaseMessage], operator.add] 587 | next: str 588 | 589 | def scraper_node(state: AgentState) -> dict: 590 | result = scraper_agent().invoke(state) 591 | return {"messages": [HumanMessage(content=result["output"], name=RESEARCHER)]} 592 | 593 | def analyzer_node(state: AgentState) -> dict: 594 | result = analyzer_agent().invoke(state) 595 | return {"messages": [HumanMessage(content=result["output"], name=ANALYZER)]} 596 | 597 | def expert_node(state: AgentState) -> dict: 598 | result = expert_agent().invoke(state) 599 | return {"messages": [HumanMessage(content=result["output"], name=EXPERT)]} 600 | 601 | def supervisor_node(state: AgentState) -> Runnable: 602 | return create_supervisor(llm, agents) 603 | 604 | workflow = StateGraph(AgentState) 605 | 606 | workflow.add_node(RESEARCHER, scraper_node) 607 | workflow.add_node(ANALYZER, analyzer_node) 608 | workflow.add_node(EXPERT, expert_node) 609 | workflow.add_node(SUPERVISOR, supervisor_node) 610 | 611 | workflow.add_edge(RESEARCHER, SUPERVISOR) 612 | workflow.add_edge(ANALYZER, SUPERVISOR) 613 | workflow.add_edge(EXPERT, SUPERVISOR) 614 | workflow.add_conditional_edges( 615 | SUPERVISOR, 616 | lambda x: x["next"], 617 | { 618 | RESEARCHER : RESEARCHER , 619 | ANALYZER: ANALYZER, 620 | EXPERT: EXPERT, 621 | "FINISH": END 622 | } 623 | ) 624 | 625 | workflow.set_entry_point(SUPERVISOR) 626 | 627 | graph = workflow.compile() 628 | 629 | #what are some of the most popular stocks for 2024 should i invest in or stock that might have the biggest gains in the future 630 | #What are some of the stocks that had the greatest performance recently And are also the most liquid and highly traded ? 631 | # User_input = ( 632 | # "sCRAPE THIS game laptop and help me to find wining product" 633 | # ) 634 | 635 | for s in graph.stream({"messages": [HumanMessage(content=user_input)]}): 636 | if "__end__" not in s: 637 | print(s) 638 | print("----") 639 | 640 | 641 | if __name__ == "__main__": 642 | main() 643 | 644 | --------------------------------------------------------------------------------