From b7d59835cec30a5a59241b9d1eaebe60fe970c00 Mon Sep 17 00:00:00 2001 From: Armor00 <2654988228@qq.com> Date: Wed, 12 Mar 2025 16:13:31 +0800 Subject: [PATCH] update --- src/multi_agent/config.py | 10 +++ src/multi_agent/hierarchical_demo.py | 106 +++++++++++++++++-------- src/multi_agent/util/tools.py | 17 ++++ src/multi_agent/util/weather_search.py | 28 +++++++ src/multi_agent/util/web_search.py | 43 +++++----- src/multi_agent/weather_agent.py | 57 +++++++++++++ 6 files changed, 209 insertions(+), 52 deletions(-) create mode 100644 src/multi_agent/config.py create mode 100644 src/multi_agent/util/tools.py create mode 100644 src/multi_agent/util/weather_search.py create mode 100644 src/multi_agent/weather_agent.py diff --git a/src/multi_agent/config.py b/src/multi_agent/config.py new file mode 100644 index 0000000..78ca965 --- /dev/null +++ b/src/multi_agent/config.py @@ -0,0 +1,10 @@ + + +from langchain_ollama import ChatOllama + +LLM = ChatOllama(model='qwen2.5:14b', api_key="1145141919810", base_url='http://192.168.195.158:11434') +# LLM = ChatOpenAI(model="gpt-4o", openai_api_key='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') + +tavily_key = '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' + +gaode_key = '00fd082df2414f75c6efb64896819451' diff --git a/src/multi_agent/hierarchical_demo.py b/src/multi_agent/hierarchical_demo.py index 9a404ad..1bd4dc2 100644 --- a/src/multi_agent/hierarchical_demo.py +++ b/src/multi_agent/hierarchical_demo.py @@ -1,3 +1,4 @@ +import json import os import time import operator @@ -11,7 +12,12 @@ from langchain_core.messages import HumanMessage from pydantic import BaseModel, Field -llm = ChatOpenAI(model="gpt-4o", temperature=0.1, openai_api_key='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') +from src.multi_agent.util.tools import formatted_json +from src.multi_agent.util.web_search import do_search +from src.multi_agent.config import LLM +from src.multi_agent.weather_agent import weather_chain + +llm = LLM MEMBERS = {'天气查询模块': '输入一个城市和日期,查询当日的天气状况', '网络查询模块': '在网上搜索指定的内容', @@ -41,31 +47,53 @@ def planner(state: AgentState): ]) planner_chain = (planner_prompt | llm) - result_dict = {'plan': '为了完成这个任务,我们需要按照以下步骤进行:\n\n1. **网络查询模块**:\n - 任务:查询2024年中国GDP最高的城市。\n - 行动:在网上搜索2024年中国GDP最高的城市的信息。\n\n2. **天气查询模块**:\n - 任务:查询该城市今天的天气状况。\n - 行动:使用天气查询模块,输入城市名称和当前日期,获取该城市的天气信息。\n\n3. **诗歌创作模块**:\n - 任务:根据查询到的城市名称和天气状况,创作一首诗。\n - 行动:利用诗歌创作模块,结合城市的特点和天气状况,创作一首诗。\n\n请各模块按照上述计划执行任务,并在完成后汇总信息。'} - - return result_dict - # llm_resp = planner_chain.invoke({'query': [('user', user_query)]}) - # return {'plan': llm_resp.content} + llm_resp = planner_chain.invoke({'query': [('user', user_query)]}) + return {'plan': llm_resp.content} def decision_node(state: AgentState): - class NextStep(BaseModel): - next_agent: str = Field(description='下一步应该交给哪个模块来完成') - action: str = Field(description='下一步应该执行的行动计划是什么') - members = state.get('members', {}) member_keys = list(members.keys()) - decision_prompt = ChatPromptTemplate.from_messages([ - ('system', f'你负责针对一个给定的任务计划,以及当前的任务执行状态,制定下一步应该由谁来执行什么内容。可以在这些模块中进行选择:{", ".join(member_keys)}。'), - ('user', f'当前的计划是:'), - MessagesPlaceholder(variable_name="plan"), - ('user', '以下是各个模块之间的消息记录'), - MessagesPlaceholder(variable_name="messages") - ]) - decision_chain = (decision_prompt | llm.with_structured_output(NextStep)) - llm_resp = decision_chain.invoke({'plan': [('user', state.get('plan', ''))], 'messages': state.get('messages', [])}) - next_agent = llm_resp.next_agent - action = llm_resp.action + if isinstance(llm, ChatOpenAI): + class NextStep(BaseModel): + next_agent: str = Field(description='下一步应该交给哪个模块来完成') + action: str = Field(description='下一步应该执行的行动计划是什么。如果已经执行所有的任务,则填入执行结果') + + decision_prompt = ChatPromptTemplate.from_messages([ + ('system', f'你负责针对一个给定的任务计划,以及当前的任务执行状态,制定下一步应该由谁来执行什么内容。' + f'可以在这些模块中进行选择:{", ".join(member_keys)}。'), + ('user', f'当前的计划是:'), + MessagesPlaceholder(variable_name="plan"), + ('user', '以下是各个模块之间的消息记录'), + MessagesPlaceholder(variable_name="messages") + ]) + decision_chain = (decision_prompt | llm.with_structured_output(NextStep)) + llm_resp = decision_chain.invoke({'plan': [('user', state.get('plan', ''))], + 'messages': state.get('messages', [])}) + next_agent = llm_resp.next_agent + action = llm_resp.action + else: + decision_prompt = ChatPromptTemplate.from_messages([ + ('system', + f'你负责针对一个给定的任务计划,以及当前的任务执行状态,制定下一步应该由谁来执行什么内容。' + f'可以在这些模块中进行选择:{", ".join(member_keys)}。你的输出必须只能是一个json字符串,' + f'必须包含next_agent和action两个字段。其中next_agent是下一步应该交给哪个模块来完成,' + f'action是下一步应该执行的行动计划是什么,如果已经执行完成所有的任务,则在action中填入执行结果' + f'你只需要输出这个json即可,不需要给出任何解释'), + ('user', f'当前的计划是:'), + MessagesPlaceholder(variable_name="plan"), + ('user', '以下是各个模块之间的消息记录'), + MessagesPlaceholder(variable_name="messages") + ]) + decision_chain = (decision_prompt | llm) + llm_resp = decision_chain.invoke( + {'plan': [('user', state.get('plan', ''))], 'messages': state.get('messages', [])}) + llm_resp_str = formatted_json(llm_resp.content) + resp_dict = json.loads(llm_resp_str) + next_agent = resp_dict.get('next_agent') + action = resp_dict.get('action') + if not (next_agent and action): + raise ValueError('模型返回异常') return {'messages': [HumanMessage(content=f'接下来由{next_agent}来执行{action}')], 'next_agent': next_agent, 'next_plan': action} @@ -79,13 +107,25 @@ def router_node(state: AgentState): def weather_node(state: AgentState): - result = '上海的天气是25℃,下雨' + # result = '上海的天气是25℃,下雨' + agent_resp = weather_chain.invoke({'next_plan': state.get('next_plan')}) + result = agent_resp.get('result') return {'messages': [HumanMessage(content=result)]} def websearch_node(state: AgentState): - result = '2024年中国GDP最高的城市是上海' - return {'messages': [HumanMessage(content=result)]} + + next_plan = state.get('next_plan', '') + websearch_prompt = ChatPromptTemplate.from_messages([ + ('system', f'你是一个网络搜索Agent,你需要根据用户的问题,从中提取出用于在网络搜索引擎上搜索的关键字。只需要回答关键字即可,不需要进行解释'), + ('user', f'当前的用户问题是:'), + MessagesPlaceholder(variable_name="next_plan"), + ]) + websearch_chain = (websearch_prompt | llm) + llm_resp = websearch_chain.invoke({'next_plan': [('user', next_plan)]}) + search_result = do_search(llm_resp.content) + result_resp = llm.invoke(f'请根据搜索结果进行回答我的问题。\n我的问题是:{next_plan}\n\n搜索结果为:{search_result}') + return {'messages': [HumanMessage(content=result_resp.content)]} def composer_node(state: AgentState): @@ -100,7 +140,7 @@ def composer_node(state: AgentState): def end_node(state: AgentState): - print('placeholder') + print('任务完成') workflow = StateGraph(AgentState) @@ -134,14 +174,16 @@ workflow.add_edge('结束节点', END) workflow.set_entry_point('planner') graph = workflow.compile() -print(graph.get_graph().draw_ascii()) -enter = {'user_query': "查询2024年中国GDP最高的城市,以这个城市今天的天气,创作一首诗", - "members": MEMBERS} -for s in graph.stream(enter): - if "__end__" not in s: - print(s) - print('-'*80) +if '__main__' == __name__: + print(graph.get_graph().draw_ascii()) + + user_query = '查询2024年中国GDP最高的城市,以这个城市今天的天气,创作一首诗' + enter = {'user_query': user_query, "members": MEMBERS} + for s in graph.stream(enter): + if "__end__" not in s and 'router' not in s: + print(s) + print('-'*80) diff --git a/src/multi_agent/util/tools.py b/src/multi_agent/util/tools.py new file mode 100644 index 0000000..106debc --- /dev/null +++ b/src/multi_agent/util/tools.py @@ -0,0 +1,17 @@ +import json + + +def formatted_json(input_str: str): + if input_str.startswith("```"): + input_str = input_str[3::] + if input_str.endswith("```"): + input_str = input_str[:-3:] + if input_str.startswith("'''"): + input_str = input_str[3::] + if input_str.endswith("'''"): + input_str = input_str[:-3:] + while input_str.startswith('\n'): + input_str = input_str[1::] + if input_str.startswith('json'): + input_str = input_str[4::] + return input_str \ No newline at end of file diff --git a/src/multi_agent/util/weather_search.py b/src/multi_agent/util/weather_search.py new file mode 100644 index 0000000..0c22dc3 --- /dev/null +++ b/src/multi_agent/util/weather_search.py @@ -0,0 +1,28 @@ +import requests + +from src.multi_agent.config import gaode_key + + +def search_weather(area_str): + district_code_url = 'https://restapi.amap.com/v3/config/district?parameters' + weather_url = 'https://restapi.amap.com/v3/weather/weatherInfo?parameters' + + # area_str = '上海市' + + response = requests.get(district_code_url, params={'key': gaode_key, 'keywords': area_str}) + district_code_list = response.json().get('districts') + + if not district_code_list: + raise Exception('查询失败') + + district_code = district_code_list[0].get('adcode') + + response = requests.get(weather_url, params={'key': gaode_key, 'city': district_code}) + weather_list = response.json().get('lives') + if not weather_list: + raise Exception('查询失败') + + weather = weather_list[0] + + result = f'{area_str}现在{weather["weather"]},{weather["temperature"]}摄氏度,湿度{weather["humidity"]}%,{weather["winddirection"]}风{weather["windpower"]}级' + return result diff --git a/src/multi_agent/util/web_search.py b/src/multi_agent/util/web_search.py index 1d4367b..07d88b4 100644 --- a/src/multi_agent/util/web_search.py +++ b/src/multi_agent/util/web_search.py @@ -1,27 +1,30 @@ -from tavily import TavilyClient import requests +from src.multi_agent.config import tavily_key -query = '乔布斯是谁' -base_url = 'https://api.tavily.com/search' -json = { - 'api_key': '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', - 'query': query, - 'search_depth': 'basic', - 'include_answer': False, - 'include_images': False, - 'include_raw_content': False, - 'max_results': 5, - 'include_domains': [], - 'exclude_domains': [] -} +def do_search(query): + # query = '乔布斯是谁' -response = requests.post(base_url, json=json, verify=False) + base_url = 'https://api.tavily.com/search' + json = { + 'api_key': tavily_key, + 'query': query, + 'search_depth': 'basic', + 'include_answer': False, + 'include_images': False, + 'include_raw_content': False, + 'max_results': 5, + 'include_domains': [], + 'exclude_domains': [] + } + + response = requests.post(base_url, json=json, verify=False) + + if response.status_code == 200: + search_result = response.json() + else: + raise Exception(f'Error: {response.status_code}: {response.reason}') + return search_result -if response.status_code == 200: - search_result = response.json() -else: - raise Exception(f'Error: {response.status_code}: {response.reason}') -print('done') diff --git a/src/multi_agent/weather_agent.py b/src/multi_agent/weather_agent.py new file mode 100644 index 0000000..e0eaa6a --- /dev/null +++ b/src/multi_agent/weather_agent.py @@ -0,0 +1,57 @@ +import operator + +from typing import TypedDict, List, Annotated +from langgraph.graph import END, StateGraph +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder + +from src.multi_agent.config import LLM +from src.multi_agent.util.weather_search import search_weather + +llm = LLM + + +class WeatherState(TypedDict): + user_request: str + city: str + result: str + + +def requirement_analysis(state: WeatherState): + user_request = state.get('user_request') + analysis_prompt = ChatPromptTemplate([ + ('system', '你是一个天气查询agent,你负责从用户的需求中提取出需要查询天气的城市或地区,你只需要输出这个地名,不需要输出其他任何内容'), + ('user', f'当前的用户需求是:'), + MessagesPlaceholder(variable_name="user_request"), + ]) + analysis_chain = analysis_prompt | llm + llm_resp = analysis_chain.invoke({'user_request': [("user", user_request)]}) + return {'city': llm_resp.content} + + +def get_weather_data(state: WeatherState): + city = state.get('city') + weather_data = search_weather(city) + return {'result': weather_data} + + +def enter_chain(input_data: dict): + return {'user_request': input_data['next_plan']} + + +workflow = StateGraph(WeatherState) +# 其实完全没必要使用子agent,但演示一下子agent如何并网 + +workflow.add_node('需求分析', requirement_analysis) +workflow.add_node('获取天气', get_weather_data) + +workflow.set_entry_point('需求分析') + +workflow.add_edge('需求分析', '获取天气') +workflow.add_edge('获取天气', END) + +weather_graph = workflow.compile() +weather_chain = enter_chain | weather_graph + + +if __name__ == '__main__': + requirement_analysis({}) \ No newline at end of file