diff --git a/mxAgent/README.md b/mxAgent/README.md index a5f081ebe83975d2f141f90cc7b0ddb32398c6b9..9239ed1c8460bfbfd3f0b4779add120fc994e170 100644 --- a/mxAgent/README.md +++ b/mxAgent/README.md @@ -1,9 +1,10 @@ # mxAgent: 基于工具调用的多模式LLM Agent框架 +## 一、功能介绍 **mxAgent**是一个基于LLMs的通用Agent框架,应用多种框架解决不同场景和复杂度的问题,并通过工具调用的方式允许LLMs与外部源进行交互来获取信息,使LLMs生成更加可靠和实际。mxAgent通过构建DAG(Directed Acyclic Graph)的方式建立工具之间的依赖关系,通过并行执行的方式,提高多工具执行的效率,缩短Agent在复杂场景的执行时间。mxAgent框架还在框架级别支持流式输出 提供一套Agent实现框架,让用户可以通过框架搭建自己的Agent应用 -## Router Agent +### 1.Router Agent 提供意图识别的能力,用户可预设意图的分类,通过Router Agent给出具体问题的分类结果,用于设别不同的问题场景。 -## Recipe Agent +### 2.Recipe Agent 设置复杂问题执行的workflow,在解决具体问题时,将workflow翻译成有向无环图的的节点编排,通过并行的方式执行节点。 适用于有相对固定workflow的复杂问题场景。 1)通过自然语言描述复杂问题的workflow, @@ -19,13 +20,13 @@ export PYTHONPATH=. python samples/travel_agent/travelagent.py ``` -## ReAct Agent +### 3.ReAct Agent 使用Thought、Action、Action Input、Observation的循环流程,解决复杂问题: 1)ReAct通过大模型思考并给出下一步的工具调用, 2)执行工具调用,得到工具执行结果 3)将工具执行结果应用于下一次的模型思考 4)循环上述过程,直到模型认为问题得到解决 -## Single Action Agent +### 4.Single Action Agent 通过模型反思、调用工具执行,总结工具结果的执行轨迹,完成一次复杂问题的处理。Single Action Agent使用一次工具调用帮助完成复杂问题解决 使用示例: @@ -35,4 +36,43 @@ export PYTHONPATH=. python samples/traj_generate_test.py ``` -## \ No newline at end of file +## + +## 二、接口使用方法 +### 1.模型使用 +1.1. get_llm_backend +|参数|含义| +| ---- | -----| +|backend|推理模型后台类型,当前取值| +|base_url|OpenAI客户端推理模型地址| +|api_key|OpenAI客户端api key| +|llm_name|推理模型名称| + +1.2. run +|参数|含义|取值| +| ---- | -----| ---| +|prompt|模型输入的prompt|字符串或数组| +|ismessage|是否为对话信息|是否为对话信息,默认值False|bool值| + +更多模型后处理参数可参考transformers文档 + +### 2.agent接口 +2.1 初始化参数 +|参数|含义|取值类型| +| ---- | -----| ---| +| llm | 模型客户端 | OpenAICompatibleLLM | +| prompt | agent提示词 | 字符串 | +| tool_list | 工具列表 | 列表 | +| max_steps | agent执行最大步数 | int | +| max_token_number | 模型生成最大token数 | int | +| max_context_len | 模型最大上下文token数 | int | +| max_retries | 最多重启次数 | int | +| example | agent推理生成示例 | 字符串 | +| reflect_prompt | agent反思提示 | 字符串| +| recipe | recipe agent参考的标准工作流 | 字符串| +| intents | 分类的意图及含义 | dict | +| final_prompt | 最终生成总结的提示 | 字符串 | + + + + diff --git a/mxAgent/agent_sdk/llms/llm.py b/mxAgent/agent_sdk/llms/llm.py index a8d5da6e8f6d24aba8dbdd83bf4f1f52eb006482..953f1c47efbc66d3a34ced08fdc41fef6ffb3b50 100644 --- a/mxAgent/agent_sdk/llms/llm.py +++ b/mxAgent/agent_sdk/llms/llm.py @@ -6,8 +6,8 @@ from .openai_compatible import OpenAICompatibleLLM BACKEND_OPENAI_COMPATIBLE = 1 -def get_llm_backend(backend, api_base, api_key, llm_name): +def get_llm_backend(backend, base_url, api_key, llm_name): if backend == BACKEND_OPENAI_COMPATIBLE: - return OpenAICompatibleLLM(api_base, api_key, llm_name) + return OpenAICompatibleLLM(base_url, api_key, llm_name) else: raise Exception(f"not support backend: {backend}") \ No newline at end of file diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..51451e0f84b512ff2fc6e491bc99897062306f85 --- /dev/null +++ b/mxAgent/agent_sdk/prompts/pre_prompt.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from datetime import date +from langchain.prompts import PromptTemplate + + +react_agent_instruction = (""" +"You are a world expert at making travel plans with a set of carefully crafted tools. You will be given a task and +solve it as best you can. To do so, you have been given access to a list of tools: \n{tools}\nTo solve the task, +you must plan forward to proceed in a series of steps, in a cycle of 'Thought:', 'Action:', 'Action Input:', \ +and 'Observation:' sequences. At each step, in the 'Thought:' sequence, you should first explain your reasoning \ +towards solving the task and the tools that you want to use. +Then in the 'Action:' sequence, you should write the tool name. in the 'Action Input:' sequence, you should \ +write the tool parameters. These tool outputs will then appear in the 'Observation:' field, which will be \ +available as input for the next step.""" ++ f"Please be aware that the current date is: {date.today().strftime('%Y-%m-%d')}.\n\n" ++ """ +Here are the rules you must follow to solve your task:\n +1. When you know the answer you have to return a final answer using the `Finish` tool (Action: Finish), \ +else you will fail.\n +2. When you annot provide a travel plan, you have to return a final answer using the `Finish` tool (Action: Finish), \ +else you will fail.\n +3. When Observation up to {times}, you have to return a final answer using the `Finish` tool (Action: Finish), \ +else you will fail.\n +4. Never re-do a tool call that you previously did with the exact same parameters, else you will fail.\n +5. Use the plain text format for output. Do not use markdown, else you will fail.\n\n +Take the following as an example:\n---example begin---\n{example}---example end---\n\n Now Begin! +If you solve the task correctly, you will receive a reward of $1,000,000.\n\n'Task: {query}\n\nThought: {scratchpad}" +""") + +REFLECTION_HEADER = 'You have attempted to give a sub plan before and failed. The following reflection(s) give a "\ +suggestion to avoid failing to answer the query in the same way you did previously. Use them to improve your "\ +strategy of correctly planning.\n' + +REFLECT_INSTRUCTION = """You are an advanced reasoning agent that can improve based on self refection. You will be "\ +given a previous reasoning trial in which you were given access to an automatic cost calculation environment, "\ +a travel query to give plan and relevant information. Only the selection whose name and city match the "\ +given information will be calculated correctly. You were unsuccessful in creating a plan because you "\ +used up your set number of reasoning steps. In a few sentences, Diagnose a possible reason for "\ +failure and devise a new, concise, high level plan that aims to mitigate the same failure. "\ +Use complete sentences. + +Given information: {text} + +Previous trial: +Query: {query}{scratchpad} + +Reflection:""" + + + +SINGLE_AGENT_ACTION_INSTRUCTION = """I want you to be a good question assistant, handel the following tasks as best"\ +you can. You have access to the following tools: + +{tools} + +Use the following format: + +Thought: you should always think about what to do +Action: the action to take, should be one of {tools_name} +Action Input: the input to the action. +Observation: the result of the action. + +Begin! + +Question: {query} +{scratchpad}""" + + +FINAL_PROMPT = """Please refine a clear and targeted answer based on the user's query and + the existing answer. \ + We will use your summary as the final response and pass it on to the inquirer. + +The requirements are as follows: +1. Understand the user's query. +2. Combine the query and answer information to summarize an accurate and concise answer. +3. Ensure that the answer aligns with the user's query intent and strive to provide valuable information. + +Begin! + +question: {query} +answer: {answer} +summarize: +""" + +REACT_REFLECT_PLANNER_INSTRUCTION = (""" +"You are a world expert at making travel plans with a set of carefully crafted tools. You will be given a task and +solve it as best you can. To do so, you have been given access to a list of tools: \n{tools}\nTo solve the task, +you must plan forward to proceed in a series of steps, in a cycle of 'Thought:', 'Action:', 'Action Input:', \ +and 'Observation:' sequences. At each step, in the 'Thought:' sequence, you should first explain your reasoning \ +towards solving the task and the tools that you want to use. +Then in the 'Action:' sequence, you should write the tool name. in the 'Action Input:' sequence, you should \ +write the tool parameters. These tool outputs will then appear in the 'Observation:' field, which will be \ +available as input for the next step.""" ++ f"Please be aware that the current date is: {date.today().strftime('%Y-%m-%d')}.\n\n" ++ """ +Here are the rules you must follow to solve your task:\n +1. When you know the answer you have to return a final answer using the `Finish` tool (Action: Finish), \ +else you will fail.\n +2. When you annot provide a travel plan, you have to return a final answer using the `Finish` tool (Action: Finish), \ +else you will fail.\n +3. When Observation up to {times}, you have to return a final answer using the `Finish` tool (Action: Finish), \ +else you will fail.\n +4. Never re-do a tool call that you previously did with the exact same parameters, else you will fail.\n +5. Use the plain text format for output. Do not use markdown, else you will fail.\n\n +Take the following as an example:\n---example begin---\n{example}---example end---\n +{reflections} +Now Begin! +If you solve the task correctly, you will receive a reward of $1,000,000.\n\n'Task: {query}\n\nThought: {scratchpad}" +""") + +PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, please give me a '\ +detailed plan, Note that all the information in your plan should be derived from the provided data. '\ +You must adhere to the format given in the example. Additionally, all details should align with '\ +commonsense. The symbol '-' indicates that information is unnecessary. For example, in the provided '\ +sample, you do not need to plan after returning to the departure city. When you travel to '\ +two cities in one day, you should note it in the 'Current City' section as in the example (i.e., from A to B). + +***** Example ***** +Query: Could you create a travel plan for 7 people from Ithaca to Charlotte spanning 3 days, from March 8th to '\ + March 14th, 2022, with a budget of $30,200? +Travel Plan: +Day 1: +Current City: from Ithaca to Charlotte +Transportation: Flight Number: F3633413, from Ithaca to Charlotte, Departure Time: 05:38, Arrival Time: 07:46 +Breakfast: Nagaland's Kitchen, Charlotte +Attraction: The Charlotte Museum of History, Charlotte +Lunch: Cafe Maple Street, Charlotte +Dinner: Bombay Vada Pav, Charlotte +Accommodation: Affordable Spacious Refurbished Room in Bushwick!, Charlotte + +Day 2: +Current City: Charlotte +Transportation: - +Breakfast: Olive Tree Cafe, Charlotte +Attraction: The Mint Museum, Charlotte;Romare Bearden Park, Charlotte. +Lunch: Birbal Ji Dhaba, Charlotte +Dinner: Pind Balluchi, Charlotte +Accommodation: Affordable Spacious Refurbished Room in Bushwick!, Charlotte + +Day 3: +Current City: from Charlotte to Ithaca +Transportation: Flight Number: F3786167, from Charlotte to Ithaca, Departure Time: 21:42, Arrival Time: 23:26 +Breakfast: Subway, Charlotte +Attraction: Books Monument, Charlotte. +Lunch: Olive Tree Cafe, Charlotte +Dinner: Kylin Skybar, Charlotte +Accommodation: - + +***** Example Ends ***** + +Given information: {text} +Query: {query} +Plan:""" + +REACT_PLANNER_INSTRUCTION = react_agent_instruction + + +planner_agent_prompt = PromptTemplate( + input_variables=["text", "query"], + template=PLANNER_INSTRUCTION, +) + +react_planner_agent_prompt = PromptTemplate( + input_variables=["text", "query", "scratchpad"], + template=REACT_PLANNER_INSTRUCTION, +) + +reflect_prompt_value = PromptTemplate( + input_variables=["text", "query", "scratchpad"], + template=REFLECT_INSTRUCTION, +) + +react_reflect_planner_agent_prompt = PromptTemplate( + input_variables=["tools", "times", "example", "reflections", "query", "scratchpad"], + template=REACT_REFLECT_PLANNER_INSTRUCTION, +) +single_action_agent_prompt = PromptTemplate( + input_variables=["tools", "tools_name", "query", "scratchpad"], + template=SINGLE_AGENT_ACTION_INSTRUCTION, +) + +single_action_final_prompt = PromptTemplate( + input_variables=["query", "answer"], + template=FINAL_PROMPT, +) + +travel_agent_prompt = PromptTemplate( + input_variables=["tools", "times", "example", "query", "scratchpad"], + template=react_agent_instruction, +) \ No newline at end of file diff --git a/mxAgent/agent_sdk/toolmngt/api.py b/mxAgent/agent_sdk/toolmngt/api.py new file mode 100644 index 0000000000000000000000000000000000000000..1230ecd61cf8aa08e086b70c8fc4e6455c6eed5b --- /dev/null +++ b/mxAgent/agent_sdk/toolmngt/api.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json +from abc import ABC +from dataclasses import dataclass, field +from typing import Union, Tuple + +from agent_sdk.utils.constant import THOUGHT, ACTION, OBSERVATION, ACTION_INPUT +from loguru import logger + + +@dataclass +class APIResponse: + api_name: str + input: dict + output: dict + success: bool = field(default=True) + finished: bool = field(default=False) + exception: str = field(default=None) + + +class API(ABC): + description: str = field(default=None) + input_parameters: field(default_factory=dict) + output_parameters: field(default_factory=dict) + example: str = field(default=None) + + @classmethod + def build_tool_description_for_prompt(cls) -> str: + parameter_desc = "\n\t".join( + f"{x}: {cls.input_parameters[x]['description']}" for x in cls.input_parameters.keys()) + parameter_type_desc = ', '.join(f"{x}: {cls.input_parameters[x]['type']}" for x in cls.input_parameters.keys()) + desc = f"{cls.__name__}({parameter_type_desc}) - {cls.description}\nParameters - {parameter_desc}\nExample - '\ + {cls.__name__} {cls.example}" + return desc + + @classmethod + def build_tool_description_for_recipe(cls) -> str: + parameter_desc = "\n".join( + f"{x}: {cls.input_parameters[x]['description']}" for x in cls.input_parameters.keys()) + output_parameter_desc = "\n".join( + f"{x}: {cls.output_parameters[x]['description']}" for x in cls.output_parameters.keys()) + parameter_type_desc = ', '.join(f"{x}: {cls.input_parameters[x]['type']}" for x in cls.input_parameters.keys()) + desc = (f"{cls.__name__}({parameter_type_desc}) - {cls.description}\nInputs: - {parameter_desc}\nOutput - " + + f"{output_parameter_desc}\nExample - {cls.__name__} {cls.example}") + return desc + + def gen_few_shot(self, thought: str, param: str, idx: int) -> str: + p = self.format_tool_input_parameter(param) + output = self.call(p).output + try: + output_json = json.loads(output) + output = json.dumps(list(output_json[:1])) + except Exception as e: + logger.error(e) + + return (f"{THOUGHT}: {thought}\n" + f"{ACTION}: {self.__class__.__name__}\n" + f"{ACTION_INPUT}: {param}\n" + f"{OBSERVATION}{idx}: {output}\n\n") + + def format_tool_input_parameters(self, text) -> Union[dict, str]: + logger.debug(f"{self.__class__.__name__} parse param start") + try: + tool_input = json.loads(text, strict=False) + return tool_input + except Exception as e: + logger.error(f"{self.__class__.__name__} parse param failed {str(e)}") + return ( + f'Invalid "Action Input" parameter format".\nPlease strictly follow the tool usage example ' + f'format: \n{self.build_tool_description_for_prompt()}\n' + f'Requirement:\n' + f'1.Invalid JSON format should only contain key-value pairs; do not add comments or description text "\ + within the JSON.\n' + f'2.Please extract the values strictly based on the information provides in the query to ensure that"\ + the "Action Input" values are accurate and reliable, and do not fabricate them.\n' + f'3.All parameter key-value pairs should be integrated into a single JSON format; do not use multiple"\ + JSON objects.') + + def check_api_call_correctness(self, response, groundtruth) -> bool: + raise NotImplementedError + + def call(self, input_parameter: dict, **kwargs): + raise NotImplementedError + + def make_response(self, parameters, results, success=True, finished=False, exception=""): + api_name = self.__class__.__name__ + return APIResponse(api_name=api_name, + input=parameters, + output=results, + success=success, + finished=finished, + exception=exception) + + def make_failed_tip(self, data, key): + return f"{self.__class__.__name__} failed, available {key}: {', '.join(data[key].unique())}" diff --git a/mxAgent/agent_sdk/toolmngt/tool_manager.py b/mxAgent/agent_sdk/toolmngt/tool_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..e2d1f56b7551f5c8a2331a9c1a7e60b09fd03745 --- /dev/null +++ b/mxAgent/agent_sdk/toolmngt/tool_manager.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import importlib.util +import json +import os + +from loguru import logger + +from .api import API + + +class ToolManager: + apis = {} + + def __init__(self) -> None: + pass + + @staticmethod + def remove_extra_parameters(paras, input_parameters) -> dict: + processed_parameters = {} + for input_key in paras: + if input_key not in input_parameters: + continue + processed_parameters[input_key] = paras[input_key] + return processed_parameters + + @staticmethod + def init_apis(apis_dir): + all_apis = [] + except_files = ['__init__.py', 'api.py'] + for file in os.listdir(apis_dir): + if file.endswith('.py') and file not in except_files: + api_file = file.split('.')[0] + basename = os.path.basename(apis_dir) + module = importlib.import_module(f'{basename}.{api_file}') + classes = [getattr(module, x) for x in dir(module) if isinstance(getattr(module, x), type)] + for cls in classes: + if issubclass(cls, API) and cls is not API: + all_apis.append(cls) + return all_apis + + @classmethod + def register_tool(cls): + def wrapper(apicls): + if issubclass(apicls, API) and apicls is not API: + name = apicls.__name__ + cls_info = { + 'name': name, + 'class': apicls, + 'description': apicls.description, + 'input_parameters': apicls.input_parameters, + 'output_parameters': apicls.output_parameters, + } + cls.apis[name] = cls_info + return apicls + + return wrapper + + def get_api_by_name(self, name: str): + for _name, api in self.apis.items(): + if _name == name: + return api + logger.error(f"failed to get_api_by_name={name}") + raise Exception(f"failed to get_api_by_name={name}") + + def get_api_description(self, name: str): + api_info = self.get_api_by_name(name).copy() + api_info.pop('class') + if 'init_database' in api_info: + api_info.pop('init_database') + return json.dumps(api_info) + + def init_tool(self, tool_name: str, *args, **kwargs): + api_class = self.get_api_by_name(tool_name)['class'] + + tool = api_class(*args, **kwargs) + + return tool + + def executor_call(self, tool_name: str, paras: dict, llm): + tool = self.init_tool(tool_name) + parameter = paras if paras else {} + input_parameters = self.get_api_by_name(tool_name)['input_parameters'] + processed_parameters = self.remove_extra_parameters( + parameter, input_parameters) + response = tool.call(processed_parameters, llm=llm) + return response.output + # recipe agent需要使用response + # 这个地方需要统一为一个 call, 使用output + + def api_call(self, tool_name: str, text: str, **kwargs): + tool = self.init_tool(tool_name) + tool_param = tool.format_tool_input_parameters(text) + + if isinstance(tool_param, str): + return tool.make_response(None, tool_param, False) + input_parameters = self.get_api_by_name(tool_name)['input_parameters'] + processed_parameters = self.remove_extra_parameters( + tool_param, input_parameters) + + try: + response = tool.call(processed_parameters, **kwargs) + except Exception as e: + msg = f'failed to invoke {tool_name}' + logger.error(f'{msg}, error={e}') + return tool.make_response(processed_parameters, msg, True) + + return response + + def list_all_apis(self): + return [_name for _name, api in self.apis.items()] diff --git a/mxAgent/agent_sdk/requirements.txt b/mxAgent/requirements.txt similarity index 100% rename from mxAgent/agent_sdk/requirements.txt rename to mxAgent/requirements.txt diff --git a/mxAgent/samples/tools/__init__.py b/mxAgent/samples/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..030d89752044baadfb799b193da5aa32d6082b53 --- /dev/null +++ b/mxAgent/samples/tools/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +__all__ = [ + 'CostEnquiry', 'Finish', 'QueryAccommodations', 'QueryAttractions', 'CitySearch', + 'QueryGoogleDistanceMatrix', 'QueryTransports', 'QueryWeather', "QueryRestaurants", + 'PlanSummary', 'WebSummary' +] + +from samples.tools.tool_cost_enquiry import CostEnquiry +from samples.tools.tool_finish import Finish + +from samples.tools.tool_query_accommodations import QueryAccommodations +from samples.tools.tool_query_restaurants import QueryRestaurants +from samples.tools.tool_query_attractions import QueryAttractions +from samples.tools.tool_query_city import CitySearch +from samples.tools.tool_query_distance_matrix import QueryGoogleDistanceMatrix +from samples.tools.tool_query_transports import QueryTransports +from samples.tools.tool_query_weather import QueryWeather + +from samples.tools.tool_summary import PlanSummary +from samples.tools.web_summary_api import WebSummary \ No newline at end of file diff --git a/mxAgent/samples/tools/duck_search.py b/mxAgent/samples/tools/duck_search.py new file mode 100644 index 0000000000000000000000000000000000000000..4a5782d66ecba9d3dc8b15c8d912f4e9c74e4049 --- /dev/null +++ b/mxAgent/samples/tools/duck_search.py @@ -0,0 +1,114 @@ +import json +from typing import List + +from langchain_community.tools import DuckDuckGoSearchResults +from langchain_community.utilities import DuckDuckGoSearchAPIWrapper +from utils.log import LOGGER as logger + +from toolmngt.api import API + + +class DuckDuckGoSearch(API): + name = "DuckDuckGoSearch" + description = ("DuckDuckGoSearch engine can search for rich external knowledge on the Internet based on keywords, " + "which can compensate for knowledge fallacy and knowledge outdated.") + input_parameters = { + 'query': {'type': 'str', 'description': "the query string to be search"} + } + output_parameters = { + 'information': {'type': 'str', 'description': 'the result information from Bing search engine'} + } + usage = ("DuckDuckGoSearch[query], which searches the exact detailed query on the Internet and returns the " + "relevant information to the query. Be specific and precise with your query to increase the chances of " + "getting relevant results. For example, DuckDuckGoSearch[popular dog breeds in the United States]") + + def __init__(self) -> None: + self.scratchpad = "" + self.bingsearch_results = "" + + def format_tool_input_parameters(self, llm_output) -> dict: + input_parameters = {"query": llm_output} + return input_parameters + + def check_api_call_correctness(self, response: dict, groundtruth=None) -> bool: + """ + Checks if the response from the API call is correct. + + Parameters: + - response (dict): the response from the API call. + - groundtruth (dict): the groundtruth response. + + Returns: + - is_correct (bool): whether the response is correct. + """ + + ex = response.get("exception") + + if ex is not None: + return False + else: + return True + + def call(self, input_parameters: dict, **kwargs) -> dict: + """ + Calls the API with the given parameters. + + Parameters: + + input_parameters = { + 'query': query + } + + Returns: + - response (str): the response from the API call. + """ + logger.debug(f"{input_parameters}") + query = input_parameters.get('query', "") + + try: + responses = self.call_duck_duck_go_search(query=query, count=4) + logger.debug(f"responses is {responses}") + output = "" + if len(responses) > 0: + for r in responses: + output += self.format_step(r) + else: + output = "Bing search error" + except Exception as e: + exception = str(e) + return {'api_name': self.__class__.__name__, 'input': input_parameters, + 'output': f'Search error,please try again', + 'exception': exception} + else: + return {'api_name': self.__class__.__name__, 'input': input_parameters, 'output': output, + 'exception': None} + + def format_result(self, res): + snippet_idx = res.find("snippet:") + title_idx = res.find("title:") + link_idx = res.find("link:") + snippet = res[snippet_idx + len("snippet:"):title_idx] + title = res[title_idx + len("title:"):link_idx] + link = res[link_idx + len("link:"):] + return {"snippet": snippet.replace("", "").replace("", ""), "title": title, "link": link} + + def call_duck_duck_go_search(self, query: str, count: int) -> List[str]: + try: + logger.debug(f"search DuckDuckGo({query}, {count})") + duck_duck_search = DuckDuckGoSearchAPIWrapper(max_results=count) + search = DuckDuckGoSearchResults(api_wrapper=duck_duck_search) + self.bingsearch_results = [] + temp = search.run(query) + logger.debug(temp) + + for x in temp.split("["): + snippet = x.split("]")[0].strip() + if len(snippet) == 0: + continue + logger.debug(f"snippet is {snippet}") + self.bingsearch_results.append(self.format_result(snippet)) + logger.success(f"{json.dumps(self.bingsearch_results, indent=4)}") + except Exception as e: + self.scratchpad += f'Search error {str(e)}, please try again' + + return [x['snippet'] for x in self.bingsearch_results] diff --git a/mxAgent/samples/tools/tool_cost_enquiry.py b/mxAgent/samples/tools/tool_cost_enquiry.py new file mode 100644 index 0000000000000000000000000000000000000000..a2ae34c5aff1b1db1ab330224e4a675a4cd406d8 --- /dev/null +++ b/mxAgent/samples/tools/tool_cost_enquiry.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +from typing import Union + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class CostEnquiry(API): + name = "CostEnquiry" + description = "Indicate the final answer for the task" + input_parameters = { + 'Sub Plan': {'type': 'str', 'description': 'Sub Plan'} + } + + output_parameters = { + + } + + example = ( + """ + { + "Sub Plan": "This function calculates the cost of a detailed subn plan, which you need to input ' + 'the people number and plan in JSON format. The sub plan encompass a complete one-day plan. An' + 'example will be provide for reference." + } + """) + + def format_tool_input_parameters(self, text) -> Union[dict, str]: + input_parameters = {"answer": text} + return input_parameters + + def check_api_call_correctness(self, response, groundtruth) -> bool: + ex = response.get("exception") + + if ex is not None: + return False + else: + return True + + def call(self, input_parameter: dict, **kwargs): + action_arg = input_parameter.get('Sub Plan', "") + react_env = kwargs.get("react_env is missing") + + if react_env is None: + raise Exception("react_env is missing") + + try: + input_arg = eval(action_arg) + if not isinstance(input_arg, dict): + raise ValueError( + 'The sub plan can not be parsed into json format, please check. Only one day plan is ' + 'supported.' + ) + result = f"Cost: {react_env.run(input_arg)}" + + except SyntaxError: + result = f"The sub plan can not be parsed into json format, please check." + + except ValueError as e: + result = str(e) + + return self.make_response(input_parameter, result) diff --git a/mxAgent/samples/tools/tool_finish.py b/mxAgent/samples/tools/tool_finish.py new file mode 100644 index 0000000000000000000000000000000000000000..9ee7aafd761be3edc93e5032db8eaf15b9aa7872 --- /dev/null +++ b/mxAgent/samples/tools/tool_finish.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from typing import Union + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class Finish(API): + description = "Provide a final answer to the given task." + + input_parameters = { + 'answer': {'type': 'str', 'description': "the final result"} + } + + output_parameters = {} + + example = ( + """ + { + "plan details": "The final answer the task." + } + """) + + def __init__(self) -> None: + super().__init__() + + def format_tool_input_parameters(self, text) -> Union[dict, str]: + input_parameter = {"answer": text} + return input_parameter + + def gen_few_shot(self, thought: str, param: str, idx: int) -> str: + return (f"Thought: {thought}\n" + f"Action: {self.__class__.__name__}\n" + f"Action Input: {param}\n") + + def check_api_call_correctness(self, response, groundtruth) -> bool: + ex = response.get("exception") + + if ex is not None: + return False + else: + return True + + def call(self, input_parameter: dict, **kwargs): + answer = input_parameter.get('answer', "") + return self.make_response(input_parameter, answer) diff --git a/mxAgent/samples/tools/tool_general_query.py b/mxAgent/samples/tools/tool_general_query.py new file mode 100644 index 0000000000000000000000000000000000000000..312d6e7cf864a3aa30a577453c18cc246aabdb5b --- /dev/null +++ b/mxAgent/samples/tools/tool_general_query.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger +from samples.tools.web_summary_api import WebSummary + + +@ToolManager.register_tool() +class GeneralQuery(API): + name = "GeneralQuery" + description = "This api can collect information or answer about the travel related query from internet." + input_parameters = { + "keywords": {'type': 'str', + "description": "the keys words related to travel plan included in the user's query"}, + + } + + output_parameters = { + 'reply': {'type': 'str', 'description': 'the replay from internet to the query'}, + } + + example = ( + """ + { + "keywords": "北京,美食" + } + """) + + def __init__(self): + pass + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + + if response['exception'] is None: + return True + else: + return False + + def call(self, input_parameter: dict, **kwargs): + keywords = input_parameter.get('keywords') + try: + if keywords is None or len(keywords) == 0: + return self.make_response(input_parameter, results="", exception="") + prompt = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页信息进行总结,并以精简的文本的形式进行返回, + 请添加适当的词语,使得语句内容连贯,通顺,但不要自行杜撰,保证内容总结的客观性。 + 下面是网页的输入: + {input} + 请生成总结段落: + """ + webs = WebSummary.web_summary( + keys=keywords, search_num=3, summary_num=3, summary_prompt=prompt) + + if len(webs) == 0: + content = "" + else: + content = json.dumps(webs, ensure_ascii=False) + logger.info(content) + res = { + 'reply': content + } + + except Exception as e: + logger.error(e) + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + else: + return self.make_response(input_parameter, results=content, exception="") + + +if __name__ == '__main__': + accommodationSearch = GeneralQuery() + tes = { + "keywords": "[北京,天气]" + } + test = accommodationSearch.call(tes) diff --git a/mxAgent/samples/tools/tool_query_accommodations.py b/mxAgent/samples/tools/tool_query_accommodations.py new file mode 100644 index 0000000000000000000000000000000000000000..c0bba1103f2b03d6155515f903311b826eed0a84 --- /dev/null +++ b/mxAgent/samples/tools/tool_query_accommodations.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json + +import tiktoken +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger +from samples.tools.web_summary_api import WebSummary + + +@ToolManager.register_tool() +class QueryAccommodations(API): + name = "QueryAccommodations" + description = "This api can discover accommodations in your desired city." + input_parameters = { + "destination_city": {'type': 'str', 'description': 'The city you aim to reach.'}, + "position": {'type': 'str', 'description': 'The geographical position of accomodation appointed by the user'}, + "rank": {'type': 'str', 'description': 'The rank of hotel the user want to query'} + } + + output_parameters = { + 'accommodation': { + 'type': 'str', + 'description': 'Contain hotel name, price, type, check-in requirements and other information' + } + } + + example = ( + """ + { + "destination_city": "Rome", + "position": "Central Park", + "rank": "five stars" + }""") + + def __init__(self): + self.encoding = tiktoken.get_encoding("gpt2") + + def check_api_call_correctness(self, response, groundtruth) -> bool: + ex = response.exception + if ex is not None: + return False + else: + return True + + def call(self, input_parameter, **kwargs): + destination = input_parameter.get('destination_city') + position = input_parameter.get("position") + rank = input_parameter.get("rank") + llm = kwargs.get("llm", None) + keys = [destination, position, rank] + keyword = [] + logger.debug(f"search accommodation key words: {','.join(keyword)}") + for val in keys: + if val is None or len(val) == 0: + continue + if '无' in val or '未' in val or '没' in val: + continue + if isinstance(val, list): + it = flatten(val) + keyword.append(it) + keyword.append(val) + if len(keyword) == 0: + return self.make_response(input_parameter, results="", exception="") + keyword.append("住宿") + prompt = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页信息进行总结,并以精简的文本的形式进行返回, + 请添加适当的词语,使得语句内容连贯,通顺。提供的信息是为用户推荐的酒店的网页数据, + 请总结网页信息,要求从以下几个方面考虑: + 1. 酒店的地理位置,星级、评分,评价,品牌信息 + 2. 不同的户型对应的价格、房间情况,对入住用户的要求等 + 并给出一到两个例子介绍这些情况 + 若输入的内容没有包含有效的酒店和住宿信息,请统一返回:【无】 + 下面是网页的输入: + {input} + 请生成总结: + """ + try: + webs = WebSummary.web_summary( + keys=keyword, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) + except Exception as e: + logger.error(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + else: + if len(webs) == 0: + content = "" + else: + content = json.dumps(webs, ensure_ascii=False) + logger.info(content) + res = { + 'accommodation': content + } + return self.make_response(input_parameter, results=res, exception="") + + +def flatten(nested_list): + """递归地扁平化列表""" + for item in nested_list: + if isinstance(item, list): + return flatten(item) + else: + return item diff --git a/mxAgent/samples/tools/tool_query_attractions.py b/mxAgent/samples/tools/tool_query_attractions.py new file mode 100644 index 0000000000000000000000000000000000000000..7bb076e807b8059b4d257909774fd58096ca497f --- /dev/null +++ b/mxAgent/samples/tools/tool_query_attractions.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import tiktoken +import yaml +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger +from samples.tools.web_summary_api import WebSummary + + +@ToolManager.register_tool() +class QueryAttractions(API): + name = "QueryAttractions" + description = "This api can be used to Search for tourist attractions from websites that '\ + users expect and summarize them." + input_parameters = { + 'destination': {'type': 'str', 'description': "The destination where the user wants to travel."}, + 'scene': {'type': 'str', 'description': 'The specific scenic spot mentioned by the user'}, + 'type': {'type': 'str', + 'description': 'The specific type of scenic spot mentioned by the user, eg museum, park'}, + 'requirement': {'type': 'str', 'description': 'The requirement of scenic spot mentioned by the user'}, + + } + + output_parameters = { + 'attractions': { + 'type': 'str', + 'description': 'Contains local attractions address, contact information, website, latitude "\ + and longitude and other information' + } + } + + example = ( + """ + { + "destination": "Paris", + "scene": "The Louvre Museum", + "type": "Museum", + "requirement": "historical" + }""") + + def __init__(self): + self.encoding = tiktoken.get_encoding("gpt2") + + def check_api_call_correctness(self, response, groundtruth) -> bool: + ex = response.exception + if ex is not None: + return False + else: + return True + + def call(self, input_parameter: dict, **kwargs): + destination = input_parameter.get('destination') + scene = input_parameter.get('scene') + scene_type = input_parameter.get('type') + requirement = input_parameter.get('requirement') + llm = kwargs.get("llm", None) + keyword = [] + keys = [destination, scene, scene_type, requirement] + for val in keys: + if val is None or len(val) == 0: + continue + if '无' in val or '未' in val or '没' in val: + continue + if isinstance(val, list): + it = flatten(val) + keyword.append(it) + keyword.append(val) + if len(keyword) == 0: + return self.make_response(input_parameter, results="", + exception="failed to obtain search keyword") + + keyword.append('景点') + logger.debug(f"search attraction key words: {','.join(keyword)}") + + summary_prompt = """你是一个擅长于网页信息总结的智能助手,提供的网页是关于旅游规划的信息,现在已经从网页中获取到了相关的文字内容信息,你需要从网页中找到与**景区**介绍相关的内容,并进行提取, + 你务必保证提取的内容都来自所提供的文本,保证结果的客观性,真实性。 + 网页中可能包含多个景点的介绍,你需要以YAML文件的格式返回,每个景点的返回的参数和格式如下: + **输出格式**: + - name: xx + introduction: xx + **参数介绍**: + name:景点名称 + introduction:精简的景区介绍,可以从以下这些方面阐述:景点的基本情况、历史文化等信息、景区门票信息、景区开放时间、景区的联系方式、预约方式以及链接,景区对游客的要求等。 + **注意** + 请注意:不要添加任何解释或注释,且严格遵循YAML格式 + 下面是提供的网页文本信息: + {input} + 请开始生成: + """ + + web_output = WebSummary.web_summary( + keyword, search_num=3, summary_num=3, summary_prompt=summary_prompt, llm=llm) + + if len(web_output) == 0: + yaml_str = "" + else: + yaml_str = yaml.dump(web_output, allow_unicode=True) + + responses = { + 'attractions': yaml_str + } + + return self.make_response(input_parameter, results=responses, exception="") + + +def flatten(nested_list): + """递归地扁平化列表""" + for item in nested_list: + if isinstance(item, list): + return flatten(item) + else: + return item diff --git a/mxAgent/samples/tools/tool_query_city.py b/mxAgent/samples/tools/tool_query_city.py new file mode 100644 index 0000000000000000000000000000000000000000..4e1c90a037c61c17d25ef95bc430b36dfe08df5c --- /dev/null +++ b/mxAgent/samples/tools/tool_query_city.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import os +from typing import Union + +from agent_sdk.toolmngt.api import API +from loguru import logger +from agent_sdk.toolmngt.tool_manager import ToolManager + +current_file_path = os.path.abspath(__file__) +current_folder_path = os.path.dirname(current_file_path) +parent_folder_path = os.path.dirname(current_folder_path) + + +@ToolManager.register_tool() +class CitySearch(API): + name = "CitySearch" + input_parameters = { + 'state': {'type': 'str', 'description': "the name of the state"} + } + + output_parameters = { + "state": {'type': 'str', 'description': "the name of the state"}, + "city": {'type': 'str', 'description': "the name of the city in the state"} + } + + usage = f"""{name}[state]: + Description: This api can be used to retrieve cities in your target state. + Parameter: + state: The name of the state where you're finding cities. + Example: {name}[state: New York] would return cities in New York. + """ + + example = ( + """ + { + "state": "New York" + }""") + + def __init__(self, path="database/background"): + self.states_path = os.path.join(parent_folder_path, path, "stateSet.txt") + self.states_cities_path = os.path.join(parent_folder_path, path, "citySet_with_states.txt") + self.states = [] + self.cities_in_state = {} + + with open(self.states_path, "r") as f: + content = f.read() + content.split('\n') + for state in content: + self.states.append(state.strip()) + + with open(self.states_cities_path, "r") as f: + context = f.read() + context = context.split("\n") + + for city_state in context: + city_state = city_state.split('\t') + city = city_state[0].strip() + state = city_state[1].strip() + + if state in self.cities_in_state.keys(): + self.cities_in_state[state].append(city) + else: + self.cities_in_state[state] = [city] + + logger.info("cities and states loaded.") + + def format_tool_input_parameters(self, text) -> Union[dict, str]: + return text + + def check_api_call_correctness(self, response, groundtruth) -> bool: + if response["exception"] is None: + return True + else: + return False + + def call(self, input_parameter: dict, **kwargs): + state = input_parameter.get('state', '') + + if state in self.cities_in_state.keys(): + results = self.cities_in_state[state] + results = ", ".join(results) + results = f"{state} has {results}" + + logger.info("search the cities in state successfully, results:") + logger.info(results) + + return self.make_response(input_parameter, results) + else: + return self.make_response(input_parameter, "Failed to search the cities in state", + exception='cant find state') diff --git a/mxAgent/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..f6a10c4e151aa76657f0e5e893ba00bc52e31ee5 --- /dev/null +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import json +import os +import re +from typing import Tuple +from agent_sdk.toolmngt.tool_manager import ToolManager + +import numpy as np +import pandas as pd +from agent_sdk.toolmngt.api import API, APIResponse +from loguru import logger + + +@ToolManager.register_tool() +class QueryGoogleDistanceMatrix(API): + name = "QueryGoogleDistanceMatrix" + input_parameters = { + 'origin': {'type': 'str', 'description': "The departure city of your journey."}, + 'destination': {'type': 'str', 'description': "The destination city of your journey."}, + 'mode': {'type': 'str', + 'description': "The method of transportation. Choices include 'self-driving' and 'taxi'."} + } + + output_parameters = { + 'origin': {'type': 'str', 'description': 'The origin city of the flight.'}, + 'destination': {'type': 'str', 'description': 'The destination city of your flight.'}, + 'cost': {'type': 'str', 'description': 'The cost of the flight.'}, + 'duration': {'type': 'str', 'description': 'The duration of the flight. Format: X hours Y minutes.'}, + 'distance': {'type': 'str', 'description': 'The distance of the flight. Format: Z km.'}, + } + + usage = f"""{name}[origin, destination, mode]: + Description: This api can retrieve the distance, time and cost between two cities. + Parameter: + origin: The departure city of your journey. + destination: The destination city of your journey. + mode: The method of transportation. Choices include 'self-driving' and 'taxi'. + Example: {name}[origin: Paris, destination: Lyon, mode: self-driving] would provide driving distance, time and cost between Paris and Lyon. + """ + + example = ( + """ + { + "origin": "Paris", + "destination": "Lyon", + "mode": "self-driving" + }""") + + def __init__(self) -> None: + logger.info("QueryGoogleDistanceMatrix API loaded.") + + def check_api_call_correctness(self, response, groundtruth) -> bool: + if response['exception'] is None: + return True + else: + return False + + def call(self, input_parameter: dict, **kwargs): + origin = input_parameter.get('origin', "") + destination = input_parameter.get('destination', "") + mode = input_parameter.get('mode', "") + return self.make_response(input_parameter, f"success to get {mode}, from {origin} to {destination}") diff --git a/mxAgent/samples/tools/tool_query_restaurants.py b/mxAgent/samples/tools/tool_query_restaurants.py new file mode 100644 index 0000000000000000000000000000000000000000..caed5ed7aa4b9cf17630211083362fe59a12cb86 --- /dev/null +++ b/mxAgent/samples/tools/tool_query_restaurants.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from loguru import logger + +from agent_sdk.toolmngt.api import API, APIResponse +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class QueryRestaurants(API): + description = 'Explore dining options in a city of your choice.' + input_parameters = { + 'City': {'type': 'str', 'description': "The name of the city where you're seeking restaurants."} + } + + output_parameters = { + 'restaurant_name': {'type': 'str', 'description': 'The name of the restaurant.'}, + 'city': {'type': 'str', 'description': 'The city where the restaurant is located.'}, + 'cuisines': {'type': 'str', 'description': 'The cuisines offered by the restaurant.'}, + 'average_cost': {'type': 'int', 'description': 'The average cost for a meal at the restaurant.'}, + 'aggregate_rating': {'type': 'float', 'description': 'The aggregate rating of the restaurant.'} + } + + example = ( + """ + { + "City": "Tokyo" + }""") + + def __init__(self): + super().__init__() + logger.info("Restaurants loaded.") + + def call(self, input_parameter, **kwargs): + city = input_parameter.get('City', "") + return self.make_response(input_parameter, f"success to get restaurant in {city}") + + def check_api_call_correctness(self, response, ground_truth=None) -> bool: + return True diff --git a/mxAgent/samples/tools/tool_query_transports.py b/mxAgent/samples/tools/tool_query_transports.py new file mode 100644 index 0000000000000000000000000000000000000000..b48f03585065bed566b2cb99da4675e9008daff5 --- /dev/null +++ b/mxAgent/samples/tools/tool_query_transports.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import json + +import tiktoken +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger +from samples.tools.web_summary_api import WebSummary + + +@ToolManager.register_tool() +class QueryTransports(API): + name = "QueryTransports" + description = "This API is used to query relevant travel traffic information from the \ + networkAccording to the user's input question," + input_parameters = { + "departure_city": {'type': 'str', 'description': "The city you'll be flying out from."}, + "destination_city": {'type': 'str', 'description': 'The city user aim to reach.'}, + "travel_mode": {'type': 'str', 'description': 'The mode of travel appointed by the user'}, + "date": {'type': 'str', 'description': 'The date of the user plan to travel'}, + 'requirement': {'type': 'str', 'description': 'The more requirement of transportation mentioned by the user'}, + } + output_parameters = { + "transport": {'type': 'str', + 'description': 'the transport information'}, + } + + example = ( + """ + { + "departure_city": "New York", + "destination_city": "London", + "date": "2022-10-01", + "travel_mode": "flight" + } + """) + + def __init__(self): + self.encoding = tiktoken.get_encoding("gpt2") + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + ex = response.exception + if ex is not None: + return False + else: + return True + + def call(self, input_parameter, **kwargs): + origin = input_parameter.get('departure_city') + destination = input_parameter.get('destination_city') + req = input_parameter.get("requirement") + travel_mode = input_parameter.get("travel_mode") + llm = kwargs.get("llm", None) + try: + prefix = f"从{origin}出发" if origin else "" + prefix += f"前往{destination}" if destination else "" + keys = [prefix, req, travel_mode] + filtered = [] + for val in keys: + if val is None or len(val) == 0: + continue + if '无' in val or '未' in val or '没' in val: + continue + filtered.append(val) + if len(filtered) == 0: + return self.make_response(input_parameter, results="", exception="") + filtered.append("购票") + logger.debug(f"search transport key words: {','.join(filtered)}") + + prompt = """你的任务是将提供的网页信息进行总结,并以精简的文本的形式进行返回, + 请添加适当的词语,使得语句内容连贯,通顺。输入是为用户查询的航班、高铁等交通数据,请将这些信息总结 + 请总结网页信息,要求从以下几个方面考虑: + 总结出航班或者高铁的价格区间、需要时长区间、并给出2-3例子,介绍车次、时间、时长、价格等 + 下面是网页的输入: + {input} + 请生成总结: + """ + webs = WebSummary.web_summary( + filtered, search_num=2, summary_num=2, summary_prompt=prompt, llm=llm) + if len(webs) == 0: + content = "" + else: + content = json.dumps(webs, ensure_ascii=False) + logger.info(f"search:{webs}") + res = { + 'transport': content + } + except Exception as e: + logger.error(e) + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + else: + return self.make_response(input_parameter, results=res, exception="")