From f7400f4b764606f81e239f60b860a5175915dca8 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Tue, 19 Nov 2024 09:58:31 +0800 Subject: [PATCH 01/30] init repo --- mxAgent/README.md | 38 ++ mxAgent/agent_sdk/agentchain/base_agent.py | 120 ++++++ mxAgent/agent_sdk/agentchain/react_agent.py | 247 ++++++++++++ mxAgent/agent_sdk/agentchain/recipe_agent.py | 212 ++++++++++ mxAgent/agent_sdk/agentchain/router_agent.py | 64 +++ .../agentchain/single_action_agent.py | 200 ++++++++++ .../agent_sdk/agentchain/tool_less_agent.py | 41 ++ mxAgent/agent_sdk/common/__init__.py | 0 mxAgent/agent_sdk/common/enum_type.py | 24 ++ mxAgent/agent_sdk/executor/common.py | 45 +++ .../executor/recipe_executor/executor.py | 367 ++++++++++++++++++ .../executor/recipe_executor/parser.py | 117 ++++++ .../agent_sdk/executor/recipe_executor/sop.py | 159 ++++++++ .../executor/recipe_executor/state.py | 123 ++++++ .../executor/recipe_executor/task.py | 48 +++ mxAgent/agent_sdk/llms/llm.py | 12 + mxAgent/agent_sdk/llms/openai_compatible.py | 67 ++++ mxAgent/agent_sdk/prompts/pre_prompt.py | 192 +++++++++ mxAgent/agent_sdk/requirements.txt | 19 + mxAgent/agent_sdk/toolmngt/api.py | 97 +++++ mxAgent/agent_sdk/toolmngt/tool_manager.py | 112 ++++++ mxAgent/agent_sdk/utils/constant.py | 4 + mxAgent/samples/basic_demo/agent_test.py | 133 +++++++ .../basic_demo/agent_traj_systhesis.py | 66 ++++ mxAgent/samples/basic_demo/intent_router.py | 34 ++ .../samples/basic_demo/traj_generate_test.py | 105 +++++ mxAgent/samples/prompt_template/prompts.py | 165 ++++++++ mxAgent/samples/tools/__init__.py | 22 ++ mxAgent/samples/tools/google_search_api.py | 67 ++++ mxAgent/samples/tools/tool_cost_enquiry.py | 66 ++++ mxAgent/samples/tools/tool_finish.py | 49 +++ mxAgent/samples/tools/tool_general_query.py | 79 ++++ .../tools/tool_query_accommodations.py | 103 +++++ .../samples/tools/tool_query_attractions.py | 114 ++++++ mxAgent/samples/tools/tool_query_city.py | 92 +++++ .../tools/tool_query_distance_matrix.py | 64 +++ .../samples/tools/tool_query_restaurants.py | 40 ++ .../samples/tools/tool_query_transports.py | 96 +++++ mxAgent/samples/tools/tool_query_weather.py | 185 +++++++++ mxAgent/samples/tools/tool_summary.py | 71 ++++ mxAgent/samples/tools/web_summary_api.py | 176 +++++++++ .../front/chat_bot_release.py | 62 +++ .../samples/travel_agent_demo/travelagent.py | 181 +++++++++ 43 files changed, 4278 insertions(+) create mode 100644 mxAgent/README.md create mode 100644 mxAgent/agent_sdk/agentchain/base_agent.py create mode 100644 mxAgent/agent_sdk/agentchain/react_agent.py create mode 100644 mxAgent/agent_sdk/agentchain/recipe_agent.py create mode 100644 mxAgent/agent_sdk/agentchain/router_agent.py create mode 100644 mxAgent/agent_sdk/agentchain/single_action_agent.py create mode 100644 mxAgent/agent_sdk/agentchain/tool_less_agent.py create mode 100644 mxAgent/agent_sdk/common/__init__.py create mode 100644 mxAgent/agent_sdk/common/enum_type.py create mode 100644 mxAgent/agent_sdk/executor/common.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/executor.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/parser.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/sop.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/state.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/task.py create mode 100644 mxAgent/agent_sdk/llms/llm.py create mode 100644 mxAgent/agent_sdk/llms/openai_compatible.py create mode 100644 mxAgent/agent_sdk/prompts/pre_prompt.py create mode 100644 mxAgent/agent_sdk/requirements.txt create mode 100644 mxAgent/agent_sdk/toolmngt/api.py create mode 100644 mxAgent/agent_sdk/toolmngt/tool_manager.py create mode 100644 mxAgent/agent_sdk/utils/constant.py create mode 100644 mxAgent/samples/basic_demo/agent_test.py create mode 100644 mxAgent/samples/basic_demo/agent_traj_systhesis.py create mode 100644 mxAgent/samples/basic_demo/intent_router.py create mode 100644 mxAgent/samples/basic_demo/traj_generate_test.py create mode 100644 mxAgent/samples/prompt_template/prompts.py create mode 100644 mxAgent/samples/tools/__init__.py create mode 100644 mxAgent/samples/tools/google_search_api.py create mode 100644 mxAgent/samples/tools/tool_cost_enquiry.py create mode 100644 mxAgent/samples/tools/tool_finish.py create mode 100644 mxAgent/samples/tools/tool_general_query.py create mode 100644 mxAgent/samples/tools/tool_query_accommodations.py create mode 100644 mxAgent/samples/tools/tool_query_attractions.py create mode 100644 mxAgent/samples/tools/tool_query_city.py create mode 100644 mxAgent/samples/tools/tool_query_distance_matrix.py create mode 100644 mxAgent/samples/tools/tool_query_restaurants.py create mode 100644 mxAgent/samples/tools/tool_query_transports.py create mode 100644 mxAgent/samples/tools/tool_query_weather.py create mode 100644 mxAgent/samples/tools/tool_summary.py create mode 100644 mxAgent/samples/tools/web_summary_api.py create mode 100644 mxAgent/samples/travel_agent_demo/front/chat_bot_release.py create mode 100644 mxAgent/samples/travel_agent_demo/travelagent.py diff --git a/mxAgent/README.md b/mxAgent/README.md new file mode 100644 index 000000000..a5f081ebe --- /dev/null +++ b/mxAgent/README.md @@ -0,0 +1,38 @@ +# mxAgent: 基于工具调用的多模式LLM Agent框架 +**mxAgent**是一个基于LLMs的通用Agent框架,应用多种框架解决不同场景和复杂度的问题,并通过工具调用的方式允许LLMs与外部源进行交互来获取信息,使LLMs生成更加可靠和实际。mxAgent通过构建DAG(Directed Acyclic Graph)的方式建立工具之间的依赖关系,通过并行执行的方式,提高多工具执行的效率,缩短Agent在复杂场景的执行时间。mxAgent框架还在框架级别支持流式输出 +提供一套Agent实现框架,让用户可以通过框架搭建自己的Agent应用 +## Router Agent +提供意图识别的能力,用户可预设意图的分类,通过Router Agent给出具体问题的分类结果,用于设别不同的问题场景。 +## Recipe Agent +设置复杂问题执行的workflow,在解决具体问题时,将workflow翻译成有向无环图的的节点编排,通过并行的方式执行节点。 +适用于有相对固定workflow的复杂问题场景。 +1)通过自然语言描述复杂问题的workflow, +2)workflow中每一个步骤对应一次工具使用,并描述步骤间关系 +3)recipe Agent将按照workflow的指导完成工具调用 +4)使用模型总结工作流结果,解决复杂问题 +Recipe Agent利用用户所提供的流程指导和工具,使用LLMs生成SOP,并构建DAG图描述Steps之间的依赖关系。agent识别那些可并行的step,通过并行执行提高agent的执行效率。 +使用Recipe Agent,仅需要提供一段解决问题的SOP指导、用于提示最终答案生成的final prompt、解决问题可能使用的工具。 +示例见[travelagent.py](./travel_agent/travelagent.py)运行方式如下: +``` +cd mxAgent +export PYTHONPATH=. +python samples/travel_agent/travelagent.py +``` + +## ReAct Agent +使用Thought、Action、Action Input、Observation的循环流程,解决复杂问题: +1)ReAct通过大模型思考并给出下一步的工具调用, +2)执行工具调用,得到工具执行结果 +3)将工具执行结果应用于下一次的模型思考 +4)循环上述过程,直到模型认为问题得到解决 +## Single Action Agent + +通过模型反思、调用工具执行,总结工具结果的执行轨迹,完成一次复杂问题的处理。Single Action Agent使用一次工具调用帮助完成复杂问题解决 +使用示例: +``` +cd mxAgent +export PYTHONPATH=. +python samples/traj_generate_test.py +``` + +## \ No newline at end of file diff --git a/mxAgent/agent_sdk/agentchain/base_agent.py b/mxAgent/agent_sdk/agentchain/base_agent.py new file mode 100644 index 000000000..65b58feba --- /dev/null +++ b/mxAgent/agent_sdk/agentchain/base_agent.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import re +import time +from abc import ABC +from typing import Union, Generator +from loguru import logger +from pydantic import BaseModel + + +from agent_sdk.toolmngt.tool_manager import ToolManager + + +class AgentRunResult(BaseModel): + query: str + answer: str + scratchpad: str + finished: bool = False + + +class BaseAgent(ABC): + def __init__(self, llm, prompt, tool_list=None, max_steps=15, max_token_number=4096, + max_context_len=14000, max_retries=3, **kwargs): + if tool_list is None: + tool_list = [] + self.llm = llm + self.name = kwargs.get("name", "") + self.description = kwargs.get("description", "") + self.prompt = prompt + self.query = "" + self.answer = "" + self.curr_step = 0 + self.max_steps = max_steps + self.max_context_len = max_context_len + self.max_token_number = max_token_number + self.scratchpad = "" + self.max_retries = max_retries + self.tool_manager = ToolManager() + self.stop_list = ["\n"] + self.tools = "" + self.tool_names = "" + self.tool_desc_for_agent = "" + self.finished = False + self._init_tools(tool_list) + + def reset(self): + self.query = "" + self.answer = "" + self.curr_step = 0 + self.finished = False + self.scratchpad = "" + + def run(self, query: str, **kwargs): + logger.info(f"run query: {query}") + self.query = query + for key, value in kwargs.items(): + setattr(self, key, value) + + while not (self._is_halted() or self.finished): + self._step() + self.curr_step += 1 + return AgentRunResult(query=self.query, answer=self.answer, scratchpad=self.scratchpad) + + def save_agent_status(self, file_path): + pass + + def is_valid_tool(self, name: str): + tool_list = self.tool_names.split(", ") + return True if name in tool_list else False + + def _build_agent_prompt(self, **kwargs): + raise NotImplementedError + + def _init_tools(self, tool_list): + self.tool_names = ", ".join([tool.__name__ for tool in tool_list]) + for idx, tool in enumerate(tool_list): + tool_cls = tool + usage = tool_cls.build_tool_description_for_prompt() + agent_tool_desc = tool_cls.build_tool_description_for_recipe() + self.tools += f"({idx + 1}) {usage}\n" + self.tool_desc_for_agent += f"({idx + 1}) {agent_tool_desc}\n" + + def _parse_response(self, response) -> Union[str, Generator]: + return response + + def _step(self): + logger.info(f"current step {self.curr_step}") + llm_response = self._prompt_agent(self.llm) + self.answer = self._parse_response(llm_response) + + def _prompt_agent(self, llm, stream=False, **kwargs) -> Union[str, Generator]: + result = "" + for _ in range(0, self.max_retries): + try: + prompt = self._build_agent_prompt(**kwargs) + if isinstance(prompt, str): + llm_response = llm( + prompt, stop=self.stop_list, stream=stream) + else: + llm_response = llm( + prompt, stop=self.stop_list, ismessage=True, stream=stream) + if not stream: + llm_response = self._format_step(llm_response) + return llm_response + except Exception as e: + result = str(e) + logger.error(e) + time.sleep(5) + return f"send request to llm failed: {result}" + + def _is_halted(self) -> bool: + logger.debug(f"curr_step={self.curr_step}, max_steps={self.max_steps}, finished={self.finished}") + return self.curr_step >= self.max_steps + + def _format_step(self, text): + return re.sub(r'\n+', '\n', text).strip() + + def _is_correct(self): + pass \ No newline at end of file diff --git a/mxAgent/agent_sdk/agentchain/react_agent.py b/mxAgent/agent_sdk/agentchain/react_agent.py new file mode 100644 index 000000000..630865154 --- /dev/null +++ b/mxAgent/agent_sdk/agentchain/react_agent.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import copy +import json +import os +import re +from abc import ABC +from dataclasses import dataclass +from datetime import datetime, timezone +from enum import Enum +from typing import List +from langchain.prompts import PromptTemplate +from loguru import logger + +from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult +from agent_sdk.prompts.pre_prompt import travel_agent_prompt, reflect_prompt_value, \ + react_reflect_planner_agent_prompt, REFLECTION_HEADER +from agent_sdk.toolmngt.api import APIResponse + + +class ReflexionStrategy(Enum): + REFLEXION = "reflextion" + + +class APIResponseCache(ABC): + def __init__(self, max_size=1024) -> None: + self._max_size = max_size + self._response: dict = {} + + @property + def response(self): + return self._response + + @property + def max_size(self): + return self._max_size + + def add(self, res: APIResponse, action: str, action_input: str): + logger.debug(f"save response cache\n{res}") + if not res.success: + logger.warning("skip failed response") + return + if len(self._response.keys()) > self.max_size: + raise Exception("too many keys") + if self._response.get(res.api_name, None) is None: + logger.info(f"add cache {res}") + else: + logger.warning(f"update cache {res}") + cache = {"name":res.api_name, "Short Description": f"{action}({action_input})", "obj":copy.deepcopy(res)} + self._response[res.api_name] = cache + + def reset(self): + self._response = {} + + def get_tools_response(self) -> list[APIResponse]: + return list(self._response.values()) + + +class ReactAgent(BaseAgent, ABC): + FINAL_ANSWER_ACTION = "Finish" + MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE = "invalid format: missing 'Action:' after 'Thought:'" + MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE = "invalid format: missing 'Action Input:' after 'Action:'" + FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE = ( + "Parsing LLM output produced both a final answer and a parse-able action" + ) + + class ReactStep(Enum): + Thought = 1 + Action = 2 + Observation = 3 + + @dataclass + class ActionResult: + action: str + action_input: str + raw_response: str + + def __init__(self, llm, example="", prompt: PromptTemplate = travel_agent_prompt, + **kwargs) -> None: + super().__init__(llm, prompt, **kwargs) + self.stop_list = ["\nObservation"] + self.example = example + self.api_response_cache = APIResponseCache() + + @staticmethod + def _parse_action(text) -> ActionResult: + regex = r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" + + action_match = re.search(regex, text, re.DOTALL) + if action_match: + action = action_match.group(1).strip() + action_input = action_match.group(2) + tool_input = action_input.strip(" ") + tool_input = tool_input.strip('"') + return ReactAgent.ActionResult(action=action, action_input=tool_input, raw_response=text) + + if not re.search(r"Action\s*\d*\s*:[\s]*(.*?)", text, re.DOTALL): + return ReactAgent.ActionResult(action="ParserException", + action_input=ReactAgent.MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE, + raw_response=text) + elif not re.search(r"[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)", text, re.DOTALL): + return ReactAgent.ActionResult(action="ParserException", + action_input=ReactAgent.MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE, + raw_response=text) + else: + raise Exception(f"Could not parse LLM output: `{text}`") + + def reset(self): + super().reset() + self.api_response_cache.reset() + + def save_agent_status(self, file_path: str): + try: + instruction = self.prompt.format( + tools=self.tools, + times=self.max_steps - 1, + tools_name=self.tool_names, + query=self.query, + example=self.example, + scratchpad="") + traj = self.scratchpad.strip() + + save_dict = { + "instruction": instruction, "input": "", "output": traj, + "status": self.finished, "created_at": str(datetime.now(tz=timezone.utc)), + "task": self.query + } + + parent_folder_path = os.path.dirname(file_path) + os.makedirs(parent_folder_path, exist_ok=True) + mode = 'a' if os.path.exists(file_path) else 'w' + with open(file_path, mode, encoding="utf-8") as f: + json.dump(save_dict, f, ensure_ascii=False) + f.write("\n") + logger.success(f"save {self.__class__.__name__} status done") + except Exception as e: + logger.error(f"prompt = {self.prompt}") + logger.error(e) + + def _add_scratchpad(self, step: ReactStep, message: str): + if self.scratchpad != "": + self.scratchpad += "\n" + + if step == ReactAgent.ReactStep.Observation: + message = f"Observation{self.curr_step + 1}: " + message + + message_lines = message.splitlines() + for idx, message_line in enumerate(message_lines): + if message_line.startswith(step.name): + self.scratchpad += "\n".join(x for x in message_lines[idx:]) + logger.debug(f"self.scratchpad = {self.scratchpad}") + return + self.scratchpad += step.name + ": " + message + + def _parse_response(self, response) -> str: + self._add_scratchpad(ReactAgent.ReactStep.Thought, response) + + action_rst = self._parse_action(response) + if action_rst.action == ReactAgent.FINAL_ANSWER_ACTION: + self.finished = True + return action_rst.action_input + if action_rst.action == "ParserException": + logger.info(f"Observation {self.curr_step}: {action_rst.action_input}") + self._add_scratchpad(ReactAgent.ReactStep.Observation, action_rst.action_input) + return action_rst.action_input + if self.is_valid_tool(action_rst.action): + resp = self.tool_manager.api_call(action_rst.action, action_rst.action_input, llm=self.llm) + self.api_response_cache.add(resp, action=action_rst.action, action_input=action_rst.action_input) + output_str = json.dumps(resp.output, ensure_ascii=False) + result = output_str + if resp.finished: + self.answer = output_str + self.finished = resp.finished + else: + result = f"{action_rst.action} is not a valid tool, try one of [{self.tool_names}]." + self._add_scratchpad(ReactAgent.ReactStep.Observation, result) + return result + + def _build_agent_prompt(self, **kwargs): + pad = self.scratchpad + if pad.startswith("Thought: "): + pad = pad[len("Thought: "):] + return self.prompt.format( + tools=self.tools, + times=self.max_steps - 1, + tools_name=self.tool_names, + query=self.query, + example=self.example, + scratchpad=pad + "\nThought:" if pad != "" else pad) + + +class ReactReflectAgent(ReactAgent, ABC): + def __init__(self, reflect_llm, react_llm, example="", reflect_prompt: PromptTemplate = reflect_prompt_value, + prompt: PromptTemplate = react_reflect_planner_agent_prompt, + **kwargs) -> None: + super().__init__(llm=react_llm, example=example, prompt=prompt, **kwargs) + self.reflect_llm = reflect_llm + self.reflect_prompt = reflect_prompt + self.reflections: List[str] = [] + self.reflections_str: str = '' + + def run(self, query: str, **kwargs) -> AgentRunResult: + logger.debug(f"run query {query}") + self.query = query + for key, value in kwargs.items(): + setattr(self, key, value) + + while not (self._is_halted() or self.finished): + self._step() + self.curr_step += 1 + if not self.finished: + self._reflect(ReflexionStrategy.REFLEXION) + + return AgentRunResult(query=self.query, answer=self.answer, scratchpad=self.scratchpad, finished=self.finished) + + def _reflect(self, strategy: ReflexionStrategy) -> None: + logger.debug('Reflecting...') + if strategy == ReflexionStrategy.REFLEXION: + self.reflections += [self._prompt_agent(self.reflect_llm, type="reflect")] + self.reflections_str = self._format_reflections(self.reflections) + else: + raise NotImplementedError(f'Unknown reflection strategy: {strategy}') + logger.debug(self.reflections_str) + + def _build_agent_prompt(self, **kwargs) -> str: + if kwargs.get('type') == "reflect": + return self.reflect_prompt.format( + query=self.query, + text=self.text, + tools=self.tools, + scratchpad=self.scratchpad) + + return self.prompt.format( + query=self.query, + tools=self.tools, + times=self.max_steps - 1, + example=self.example, + scratchpad=self.scratchpad, + reflections=self.reflections_str) + + def _format_reflections(self, reflections: List[str], + header: str = REFLECTION_HEADER) -> str: + if not reflections: + return '' + else: + return header + 'Reflections:\n- ' + '\n- '.join([r.strip() for r in reflections]) \ No newline at end of file diff --git a/mxAgent/agent_sdk/agentchain/recipe_agent.py b/mxAgent/agent_sdk/agentchain/recipe_agent.py new file mode 100644 index 000000000..8b3423929 --- /dev/null +++ b/mxAgent/agent_sdk/agentchain/recipe_agent.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from abc import ABC +from typing import Generator, Union, List +from loguru import logger +import tiktoken + +from agent_sdk.executor.recipe_executor.executor import AgentExecutor +from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult + + +class RecipeAgent(BaseAgent, ABC): + def __init__(self, llm, tool_list=None, recipe: str = "", max_steps=15, final_prompt="", + max_token_number=4096, max_context_len=14000, max_retries=3, **kwargs): + super().__init__(llm, None, tool_list, max_steps, + max_token_number, max_context_len, max_retries, **kwargs) + self.recipe = recipe + self.reflect = False + self.reflection_result = "" + self.recipe_output = "" + self.final_prompt = final_prompt + self.encoding = tiktoken.get_encoding("gpt2") + self.status = False + self.agent_executor = AgentExecutor(tool_manager=self.tool_manager) + + def run(self, query, stream=False, *args, **kwargs): + self.query = query + + for key, value in kwargs.items(): + setattr(self, key, value) + cur_step = 1 + self.status = False + while not self.status and cur_step < self.max_steps: + self.step() + cur_step += 1 + if len(self.recipe_output) == 0: + prompt = f"你的角色设定如此:{self.description},请利用你自己的知识回答用户的问题,用户的问题如下:{query}" + else: + try: + prompt = self._build_final_prompt(self.recipe_output) + except Exception as e: + logger.error(e) + self.answer = self.recipe_output + else: + # prompt 是str类型 + self.answer = self.llm( + prompt, temperature=0.1, stop=[], stream=stream) + if stream: + return self.answer + else: + return AgentRunResult(query=self.query, answer=self.answer, scratchpad=self.scratchpad) + + def step(self): + tools_usage = self.get_tool_usage() + if not self.reflect: + prompt = INIT_PROMPT.format( + tools_usage=tools_usage, suggestion=SUGGESTION, pesuede_code=self.recipe, question=self.query) + translation_result = self.llm(prompt=prompt, temperature=0.1, stop=[], max_tokens=self.max_token_number) + else: + prompt = INIT_PROMPT.format(tools_usage=tools_usage, + suggestion=SUGGESTION, + translation_result=translation_result, + reflection=self.reflection_result, + pesuede_code=self.recipe, + question=self.query) + translation_result = self.llm(prompt=prompt, temperature=0.1, stop=[ + ], max_tokens=self.max_token_number) + + valid, _ = self.agent_executor.check_valid(translation_result) + if not valid: + self.status = True + self.recipe_output = "" + self.answer = "" + return "" + else: + result = self._execute_recipe(translation_result, self.llm) + self.status = True + self.recipe_output = result + self.answer = result + return result + + def get_tool_usage(self): + return self.tool_desc_for_agent + + def _build_final_prompt(self, text, **kwargs) -> Union[List, str]: + if self.final_prompt is None: + raise Exception("final prompt is None error") + else: + max_input_token_num = self.max_token_number + input_token_len = len(self.encoding.encode(text)) + prompt_len = len(self.encoding.encode(self.final_prompt)) + clip_text_index = int( + len(text) * (max_input_token_num - prompt_len) / input_token_len) + clip_text = text[:clip_text_index] + pmt = self.final_prompt.format(text=clip_text) + return pmt + + def _save_recipe_yaml(self, translation_result): + translation_result = translation_result.strip("'") + translation_result = translation_result.strip() + raise NotImplementedError() + + def _execute_recipe(self, recipefile, llm): + answer = self.agent_executor.async_run(recipefile, llm) + return answer + + def _build_agent_prompt(self, **kwargs): + return self.prompt.format(query=self.query, text=self.text) + + def _parse_response(self, response) -> str | Generator: + return response + + +INIT_PROMPT = """你是一个精通伪代码和YAML格式的高级程序员,擅长将一段文字描述的伪代码翻译成一个以YAML格式表示的有向无环图,有向无环图中的节点代表伪代码中的一个动作, +你只能选择**工具**段落中的工具,有向无环图的边代表动作之间的依赖关系。 +伪代码位于**伪代码**段落,可以使用的工具位于**工具**段落。 +你的任务是仔细阅读**伪代码**段落,将其翻译成一个以YAML格式表示的有向无环图,每一个步骤只能对应一个工具调用 +**工具** +{tools_usage} +**伪代码** +{pesuede_code} + +**输出格式** +【格式】: +- name:query_flight +step:1 +description:XX +toolname: XX +input: {{}} +dependency: [] +- name:XX +step:2, +description:XX +toolname: XX +input: + flight_number: ${{query_flight.flight_number}} +dependency: + - query_flight +... + +【参数】: +name:步骤名称,应该简洁明了,以英文输出,如有多个单词,单词间以'_'连接 +step:步骤序号 +description:步骤行为描述,清晰的描述本节点的功能,以中文输出 +toolname:该步骤使用的工具名称,来源于**工具**段落中的工具的名称,你只能使用**工具**中提供的工具名,不允许自行添加工具 +input: 本步骤对应工具的输入参数列表,每个参数由本工具的参数名称和输入值组成,输入值优先使用依赖节点的输出参数,需要满足二者参数含义的一致性,请不要自行给输入赋值 +dependency: 改步骤依赖其他哪些步骤,字段内容为list,list中每个元素是依赖步骤的step值 + +在你翻译时,重点关注满足如下几个方面要求: +{suggestion} + +用户输入的问题: +{question} + +请注意:翻译结果不要通过//或者 /**/ 添加任何解释或注释,且严格遵循YAML格式 +请开始:""" + +REFLECT_PROMPT = """你是一个精通伪代码和YAML格式的高级程序员,擅长将一段文字描述的伪代码,翻译成一个以YAML格式表示的有向无环图,有向无环图中的节点代表伪代码中的一个动作, +尽量使用**工具**段落中的工具,有向无环图的边代表动作之间的依赖关系。 +伪代码位于**伪代码**段落,可以使用的工具位于**工具**段落。 +你的任务是仔细分析**伪代码**段落,以及一个已经翻译出来的YAML格式表示的位于**翻译结果**段落的有向无环图,然后给出结构化的,严肃的,有价值的改进建议, +使得翻译更加准确的代表伪代码的逻辑。 +**工具** +{tools_usage} +**伪代码** +{pesuede_code} +**翻译结果** +{translation} +在你写改进建议时,重点关注是否有可能改进如下几个方面: +{suggestion} +请按列表方式写出明确的、有价值的、结构化的改进建议. 每条建议应该写清楚属于哪个节点,哪个属性字段。仅写出改进建议,不用写出其他额外的逻辑或者解释性的内容。 + +用户输入的问题: +{question} + +请开始: +""" + +IMPROVE_PROMPT = """你是一个精通伪代码和YAML格式的高级程序员,擅长将一段文字描述的伪代码,翻译成一个以YAML格式表示的有向无环图,有向无环图中的节点代表伪代码中的一个动作, +尽量使用**工具**段落中的工具,有向无环图的边代表动作之间的依赖关系。 +伪代码位于**伪代码**段落,可以使用的工具位于**工具**段落。 +你的任务是仔细分析**伪代码**段落,以及一个已经翻译出来的YAML格式表示的位于**翻译结果**段落的有向无环图,根据**改进建议**段落的改进意见,编辑翻译结果, +使得翻译更加准确匹配伪代码的逻辑。 +**工具** +{tools_usage} +**伪代码** +{pesuede_code} +**翻译结果** +{translation} +**改进建议** +{reflection} +在你改写翻译结果时,重点关注是否有可能改进如下几个方面: +{suggestion} +仅写出最新的翻译结果,不用写出其他额外的逻辑或者解释性的内容。 +用户输入的问题: +{question} + +请开始: +""" + +SUGGESTION = """1. 翻译结果请严格按照YAML格式输出,不要添加任何注释 +2. 翻译出的节点数量是否严格匹配伪代码中的步骤数量,每个步骤只能匹配一个能完成其需求的工具, +3. 不能将伪代码中的一个步骤划分翻译成多个节点,例如不要出现1.1,1.2等step +4. 每个节点的toolname字段必须准确,且是在**工具**段落中存在的 +5. 每个节点的dependency字段必须准确,能匹配伪代码中的依赖关系逻辑,dependency的节点必须是存在的节点 +6. 每个节点的input字段必须有参数,每个节点的input字段名务必准确,必须是工具有的参数名,input字段中的每个参数输入值必须且只能是具体值或者依赖节点的工具输出参数, +不要使用python代码或者其他表达式, +7. 每个节点的input字段的每个参数值,优先使用依赖节点的工具输出参数,若无法通过依赖得到可以问题中提取,若存在多个答案,请使用加号+隔开, +8. 【伪代码】的步骤:一个步骤只能翻译成一个对应的节点 +9. 生成的内容请严格遵循YAML的语法和格式 +""" diff --git a/mxAgent/agent_sdk/agentchain/router_agent.py b/mxAgent/agent_sdk/agentchain/router_agent.py new file mode 100644 index 000000000..7e7d8d190 --- /dev/null +++ b/mxAgent/agent_sdk/agentchain/router_agent.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from abc import ABC +import time + +from langchain.prompts import PromptTemplate + +from .base_agent import AgentRunResult +from .tool_less_agent import ToollessAgent + + +INTENT_PROMPT = """ +给定用户问题, 请根据用户问题代表的意图进行分类,可分类为 {intents}. + +每种分类的含义如下: +{intent_meanings} +请使用一个单词回答. + +用户问题: +{query} + +分类结果:""" + +INTENT_AGENT_PROMPT = PromptTemplate( + input_variables=["intents", "intent_meanings", "query"], + template=INTENT_PROMPT +) + + +class RouterAgent(ToollessAgent, ABC): + def __init__(self, llm, intents: dict, **kwargs): + super().__init__(llm, INTENT_AGENT_PROMPT, **kwargs) + self.intents = intents + self.query = "" + + def _build_agent_prompt(self, **kwargs): + intent_str, intents_meaning_str = "", "" + index = 1 + for intent_key, intent_des in self.intents.items(): + intent_str = intent_str + intent_key + ',' + intents_meaning_str = intents_meaning_str + \ + f"{index}, " + intent_key + ": " + intent_des + "\n" + index += 1 + + intent_str = intent_str.strip(",") + return self.prompt.format( + intents=intent_str, + intent_meanings=intents_meaning_str, + query=self.query + ) + + # def _prompt_agent(self, llm, **kwargs) -> str: + # result = "" + # for _ in range(0, self.max_retries): + # try: + # prompt = self._build_agent_prompt() + # llm_response = self._format_step( + # llm(prompt, temperature=0.1, stop=self.stop_list)) + # return llm_response + # except Exception as e: + # result = str(e) + # time.sleep(5) + # return f"send request to llm failed: {result}" diff --git a/mxAgent/agent_sdk/agentchain/single_action_agent.py b/mxAgent/agent_sdk/agentchain/single_action_agent.py new file mode 100644 index 000000000..7a2de8de9 --- /dev/null +++ b/mxAgent/agent_sdk/agentchain/single_action_agent.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json +import os +import re +import time +from copy import deepcopy +from loguru import logger + +from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from agent_sdk.prompts.pre_prompt import single_action_agent_prompt, final_prompt + + +class SingleActionAgent(BaseAgent): + MISSING_ACTION_ERROR = "Invalid Format: Missing 'Action:' after 'Thought:'" + MISSING_ACTION_INPUT_ERROR = "Invalid Format: Missing 'Action Input:' after 'Action:'" + + def __init__(self, + llm, + tool_list, + prompt=single_action_agent_prompt, + final_prompt=final_prompt, + **kwargs + ) -> None: + + if tool_list is None: + tool_list = [SingleFinish] + else: + tool_list = deepcopy(tool_list) + tool_list.append(SingleFinish) + + super().__init__(llm, prompt, tool_list, **kwargs) + self.stop_list = ["\nObservation"] + self.final_prompt = final_prompt + self.tool_output = "" + + def run(self, query, stream=False, *args, **kwargs): + logger.info(f"run query: {query}") + self.query = query + + for key, value in kwargs.items(): + setattr(self, key, value) + self.finished = False + + while not self.finished and self.curr_step < self.max_steps: + self.step() + self.curr_step += 1 + + prompt = self._build_final_prompt(self.tool_output) + if prompt is None: + self.answer = self.tool_output + elif isinstance(prompt, str): + self.answer = self.llm( + prompt, temperature=0.1, stop=[], stream=stream) + logger.info(f"Summarize={self.answer}") + else: + self.answer = self.llm(prompt, temperature=0.1, stop=[ + ], ismessage=True, stream=stream) + + if stream: + return self.answer + else: + return AgentRunResult(query=self.query, answer=self.answer, scratchpad=self.scratchpad, + finished=self.finished) + + def step(self) -> None: + llm_response = self._prompt_agent(self.llm) + logger.info(f"LLM Response:\n{llm_response}") + self.scratchpad += llm_response + action_type, argument = self._parse_action(llm_response) + logger.info(f"Argument:\n{argument}") + if action_type == "ParserException": + result = argument + else: + if self.is_valid_tool(action_type): + tool_response = self.tool_manager.api_call(action_type, argument, llm=self.llm) + output_str = json.dumps(tool_response.output, ensure_ascii=False) + self.tool_output = output_str + if tool_response.success: + self.finished = True + result = output_str + + else: + result = f"{action_type} 不是一个有效的工具,可以使用工具的列表为[{self.tool_names}]" + logger.info(f"Observation:\n{result}") + self.scratchpad += f"\nObservation: {result}\n" + + def save_agent_status(self, file_path): + try: + instruction = self.prompt.format( + tools=self.tools, + tools_name=self.tool_names, + query=self.query, + scratchpad="" + ) + + traj = self.scratchpad.strip() + save_dict = { + "instruction": instruction, + "input": "", + "output": traj, + "final answer": self.answer, + "status": self.finished + } + mode = "a" if os.path.exists(file_path) else "w" + with open(file_path, mode, encoding="utf-8") as f: + json.dump(save_dict, f, ensure_ascii=False) + f.write("\n") + except Exception as e: + logger.error(f"agent_prompt = {self.prompt}") + logger.error(e) + + def _build_agent_prompt(self, **kwargs): + return self.prompt.format( + tools=self.tools, + tools_name=self.tool_names, + query=self.query, + scratchpad=self.scratchpad + ) + + def _build_final_prompt(self, tool_output, **kwargs): + if self.final_prompt is None: + return None + else: + return self.final_prompt.format( + query=self.query, + answer=tool_output + ) + + def _prompt_agent(self, llm, **kwargs): + result = "" + for _ in range(0, self.max_retries): + try: + prompt = self._build_agent_prompt() + llm_response = self._format_step( + llm(prompt, temperature=0.1, stop=self.stop_list)) + return llm_response + except Exception as e: + result = str(e) + logger.error(e) + time.sleep(5) + return f"send request to llm failed: {result}" + + def _parse_action(self, text: str): + action_match = re.search(r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)", text, + re.DOTALL) + + if action_match: + action = action_match.group(1).strip() + action_input = action_match.group(2) + tool_input = action_input.strip(" ") + tool_input = tool_input.strip('"') + return action, tool_input + + if not re.search(r"Action\s*\d*\s*:[\s]*(.*?)", text, re.DOTALL): + return "ParserException", self.MISSING_ACTION_ERROR + elif not re.search(r"[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)", text, re.DOTALL): + return "ParserException", self.MISSING_ACTION_INPUT_ERROR + else: + raise Exception(f"Could not parse LLM output: {text}") + + +@ToolManager.register_tool() +class SingleFinish(API): + description = "在无法使用其它工具完成任务请求,如'工具'段落没有合适的工具或用户请求无足够信息提取参数等情况,可以使用本工具结束任务." + input_parameters = { + 'answer': {'type': 'str', 'description': "结束任务的答案"} + } + + output_parameters = {} + + example = ( + """ + { + "answer": "Indicate the final answer for the task." + }""" + ) + + def __init__(self) -> None: + super().__init__() + + def gen_few_shot(self, thought: str, param: str, idx) -> 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) -> dict: + answer = input_parameter.get('answer', "") + return self.make_response(input_parameter, answer, True, True) diff --git a/mxAgent/agent_sdk/agentchain/tool_less_agent.py b/mxAgent/agent_sdk/agentchain/tool_less_agent.py new file mode 100644 index 000000000..3546be49d --- /dev/null +++ b/mxAgent/agent_sdk/agentchain/tool_less_agent.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from abc import ABC +from typing import Generator +from langchain.prompts import PromptTemplate + +from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult +from agent_sdk.prompts.pre_prompt import planner_agent_prompt + + +class ToollessAgent(BaseAgent, ABC): + def __init__(self, llm, prompt: PromptTemplate = planner_agent_prompt, **kwargs): + super().__init__(llm, prompt, max_steps=1, **kwargs) + self.stop_list = [] + + def run(self, query: str, stream=False, **kwargs): + self.query = query + for key, value in kwargs.items(): + setattr(self, key, value) + + llm_response = self._prompt_agent(self.llm, stream=stream) + self.curr_step += 1 + self.finished = True + + if stream: + self.answer = llm_response + return self.answer + else: + self.answer = self._parse_response(llm_response) + return AgentRunResult(query=self.query, answer=self.answer, + scratchpad=self.scratchpad, finished=self.finished) + + def _build_agent_prompt(self, **kwargs): + return self.prompt.format( + query=self.query, + text=self.text + ) + + def _parse_response(self, response) -> str | Generator: + return response diff --git a/mxAgent/agent_sdk/common/__init__.py b/mxAgent/agent_sdk/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mxAgent/agent_sdk/common/enum_type.py b/mxAgent/agent_sdk/common/enum_type.py new file mode 100644 index 000000000..1c924ef72 --- /dev/null +++ b/mxAgent/agent_sdk/common/enum_type.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import enum + +from pydantic import BaseModel + + +class PlanStrategyType(enum.Enum): + REACT = 1 + RESEARCH = 2 + COT = 3 + EMPTY = 4 + SOP = 5 + DHP = 6 + + +class AgentRunStatus(BaseModel): + success_cnt: int = 0 + total_cnt: int = 0 + + def __str__(self): + rate = round((self.success_cnt / self.total_cnt) * 100, 2) + return str(rate) + f"% {self.success_cnt}/{self.total_cnt}" \ No newline at end of file diff --git a/mxAgent/agent_sdk/executor/common.py b/mxAgent/agent_sdk/executor/common.py new file mode 100644 index 000000000..2780bd79f --- /dev/null +++ b/mxAgent/agent_sdk/executor/common.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import enum + + +class ErrorType(enum.Enum): + INPUT_NOT_STR = 0 + FILE_TYPE_ERROR = 1 + FILE_READ_ERROR = 2 + NO_DICT_ERROR = 3 + YAML_LOAD_ERROR = 3 + INVALID_TOOL_ERROR = 4 + NO_NAME_ERROR = 5 + INVALID_INPUT_PARAM = 6 + INVALID_OUPUT_PARAM = 7 + NODE_CONFILCT_ERROR = 8 + INVALID_DEPENDENCY = 9 + NODE_NOT_DEPENDENT = 10 + + +ERROR_MAP = { + ErrorType.INPUT_NOT_STR: "Invalid arguements type: the content need a str", + ErrorType.FILE_TYPE_ERROR: "The type of file {filename} is not supported, only json or yaml is fine", + ErrorType.FILE_READ_ERROR: "Failed to read the file {filename}: Error message: {error}", + ErrorType.NO_DICT_ERROR: "The content cannot be converted to dict format.", + ErrorType.YAML_LOAD_ERROR: "Loading content with YAML error: {error}", + ErrorType.INVALID_TOOL_ERROR: "The tool {toolname} doesn't exist.", + ErrorType.NO_NAME_ERROR: "some nodes did not assign a value to a necessary parameter [name] and [toolname].", + ErrorType.INVALID_INPUT_PARAM: "Additional parameters {params} were input during the calling tool {tool}.", + ErrorType.INVALID_OUPUT_PARAM: "These parameters {params} do not exist in the output of the node {node}.", + ErrorType.NODE_CONFILCT_ERROR: "the Node name {name} is duplicated.", + ErrorType.INVALID_DEPENDENCY: "The current node depends on node {node} that does not exist in the plan.", + ErrorType.NODE_NOT_DEPENDENT: "The output of node {node} is being used, but it is not within the dependency" + +} + + +class PlanStrategyType(enum.Enum): + REACT = 1 + RESEARCH = 2 + COT = 3 + EMPTY = 4 + SOP = 5 + DHP = 6 \ No newline at end of file diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/mxAgent/agent_sdk/executor/recipe_executor/executor.py new file mode 100644 index 000000000..011045aac --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/executor.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from typing import Dict +import asyncio +import time +import re +import traceback +import json +import os +import threading +from concurrent.futures import ThreadPoolExecutor, as_completed +import yaml +from loguru import logger + +from agent_sdk.executor.recipe_executor.state import ExecutorState, WorkSpace +from agent_sdk.executor.recipe_executor.parser import Node, Parser, ActionGraph +from agent_sdk.executor.recipe_executor.sop import SopHandler +from agent_sdk.executor.common import ERROR_MAP, ErrorType, PlanStrategyType + +ALL_PLAN_STRATEGIES = { + PlanStrategyType.SOP.value: "sop" +} + + +class AgentExecutor(): + tasks_done: Dict[str, asyncio.Event] + + def __init__( + self, + tool_manager, + plan_strategy_type: PlanStrategyType = None, + ): + self.plan_strategy_type = plan_strategy_type or PlanStrategyType.SOP + self.plan_strategy = ALL_PLAN_STRATEGIES.get( + self.plan_strategy_type.value) + self.parser = Parser() + self.tool_manager = tool_manager + self.operation_handler = SopHandler(tool_manager) + self.lock = threading.Lock() + + @staticmethod + def parser_output(output, cur_operation): + content = output + if isinstance(output, str): + try: + content = json.loads(output) + except json.JSONDecodeError: + content = output + else: + content = output + cur_operation.output = content + history = { + "operation_name": cur_operation.name, + "dependecy": cur_operation.dependency, + "input": cur_operation.input, + "output": content, + "activate": cur_operation.activate, # 激活该分支的条件 + "interaction_history": "", # discuss + "variable_space": "" # discuss + } + cur_operation.history = history + return content, history + + @staticmethod + def update_history(result, executor_state): + name = result['action'] + parsed_out = result['output'] + history = result['history'] + executor_state.workspace.variable_space[name] = parsed_out + executor_state.workspace.update(history) + # 执行完毕,从set中移除 + executor_state.remaining_tasks.remove(name) + executor_state.activated_tasks.remove(name) + executor_state.done_tasks.add(name) + executor_state.workspace.update_last_operation(name) + + @staticmethod + def process_action_args(action: Node, executor_state): + # 处理参数依赖的替换 + history_names = [history["operation_name"] + for history in executor_state.workspace.operation_history] + # if any of a node's previous actions has no input + # ie. this node's previous operation name not in history, this node cannot execute, so pass + if not set(action.dependency).issubset(set(history_names)): + return None + node = action + if node.activate: + # 用表达引擎,执行看是否满足准入要求 + node.activate = sub_placeholder( + node.activate, executor_state.workspace) + bool_activate = expression_engine(node.activate) + + if not bool_activate: + return None + + if action.input: + for key, value in action.input.items(): + if value is None: + logger.warning('the value of [%s] is None', key) + else: + value = sub_placeholder(value, executor_state.workspace) + action.input[key] = value + return action + + def get_executable_actions(self, executor_state): + done_actions = executor_state.done_tasks + graph = executor_state.sop_graph.actions + activated = executor_state.activated_tasks + independent_actions = [ + task_name + for task_name in executor_state.remaining_tasks + if all( + d in done_actions for d in graph[task_name].dependency + ) + ] + executable_actions = [] + pending_actions = [] + for action in independent_actions: + node = graph[action] + if node.activate: + node.activate = sub_placeholder( + node.activate, executor_state.workspace) + if not node.activate: + break + bool_activate = expression_engine(node.activate) + + if not bool_activate: + executor_state.remaining_tasks.remove(action) + executor_state.done_tasks.add(action) + continue + executable_actions.append(action) + + if action in activated: + continue + # TODO : 问题:当前的action并不知道入参来自哪来的时候 + node = self.process_action_args(node, executor_state,) + if node is not None: + pending_actions.append(node) + else: + continue + return pending_actions + + def run_task(self, action, executor_state, llm): + graph = executor_state.sop_graph.actions + sop_handler = self.operation_handler + mommt = time.time() + logger.debug(f'{action.name} start:{mommt}') + output = sop_handler.invoke(action, llm=llm) + + parsed_out, history = self.parser_output(output, action) + graph[action.name].output = parsed_out + + res = { + "action": action.name, + "output": parsed_out, + "history": history + } + + mommt = time.time() + logger.debug(f'step {action.step}. action: {action.name} has finished') + return res + + + + def async_run(self, content, llm): + executor_state = self.init_state(content) + executor = ThreadPoolExecutor(max_workers=5) + with ThreadPoolExecutor(max_workers=5) as executor: + while executor_state.remaining_tasks: # 活跃的 + executable_tasks = self.get_executable_actions(executor_state) + thread_list = [] + for task in executable_tasks: + th = executor.submit(self.run_task, task, executor_state, llm) + thread_list.append(th) + executor_state.activated_tasks.add(task.name) + # TODO 某个task执行失败,会被保留,循环 + for future in as_completed(thread_list): + with self.lock: + self.update_history(future.result(), executor_state) + return executor_state.workspace.get_last_result() + + # 而此处的写法不关注next,关注dependency + def run(self, content): + executor_state = self.init_state(content) + + sop_handler = self.operation_handler + activate_actions = self.get_executable_actions(executor_state) + executor_state.activate_actions = activate_actions + while executor_state.activate_actions: # 活跃的 + + cur_operation = executor_state.activate_actions.pop( + 0) # starts from right + mommt = time.time() + logger.error("start run step %d, action [%s]", cur_operation.step, cur_operation.name) + output = sop_handler.invoke(cur_operation) + parsed_out, history = self.parser_output(output, cur_operation) + executor_state.workspace.update(history) + # 保存结果 + executor_state.workspace.variable_space[cur_operation.name] = parsed_out + executor_state.workspace.update_last_operation( + cur_operation.name) + # 更新状态 + executor_state.remaining_tasks.remove(cur_operation.name) + executor_state.done_tasks.add(cur_operation.name) + executable_actions = self.get_executable_actions(executor_state) + executor_state.activate_actions = (executable_actions) + + return executor_state.workspace.get_last_result() + + def check_valid(self, content): + if not isinstance(content, str): + return False, ERROR_MAP[ErrorType.INPUT_NOT_STR] + absolute_path = os.getcwd() + if os.path.isfile(os.path.join(absolute_path, content)): + raw_dict, err = fetch_file_content(content) + else: + raw_dict, err = fetch_str_content(content) + if len(err) > 0: + return False, err + err = self.operation_handler.check_valid_sop(raw_dict) + return len(err) == 0, err + + def init_state(self, content): + if not isinstance(content, str): + raise TypeError("Invalid arguements type: the content need a str") + absolute_path = os.getcwd() + if os.path.isfile(os.path.join(absolute_path, content)): + raw_dict, err = fetch_file_content(content) + else: + raw_dict, err = fetch_str_content(content) + + filter_opt = [] + for operation in raw_dict: + operation_name = operation['toolname'] + api = self.tool_manager.get_api_by_name(operation_name) + if api is not None: + filter_opt.append(operation) + operations = self.parser.parse(raw_dict=filter_opt) + + execute_state = ExecutorState() + workspace = WorkSpace(operation_history=[], variable_space={}) + graph = ActionGraph(operations) + + execute_state.workspace = workspace + execute_state.sop_graph = graph + execute_state.remaining_tasks = {k for k, _ in graph.actions.items()} + return execute_state + + +def fetch_str_content(content): + start_identify = "```yaml" + end_identify = "```" + start = content.find(start_identify) + end = content.rfind(end_identify) + if start != -1 and end != -1: + content = content[start + len(start_identify): end] + try: + code_seg = content.strip("\n").split("\n") + while code_seg[0] == start_identify: + code_seg.pop(0) + while code_seg[-1] == end_identify: + code_seg.pop() + data = yaml.safe_load("\n".join(code_seg)) + except yaml.YAMLError as e: + logger.error(f"生成yaml代码块错误:{str(e)}") + return [], ERROR_MAP[ErrorType.YAML_LOAD_ERROR] + return data, '' + + +def fetch_file_content(file_path): + # 获取文件扩展名 + file_extension = os.path.splitext(file_path)[1].lower() + if file_extension == '.yaml': + try: + with open(file_path, 'r') as file: + yaml_content = yaml.safe_load(file) + return yaml_content, '' + except Exception as e: + msg = ERROR_MAP[ErrorType.FILE_READ_ERROR].format( + filename=file_path, error=e) + logger.error(msg) + return [], msg + elif file_extension == '.json': + try: + with open(file_path, 'r') as file: + json_content = json.load(file) + return json_content, '' + except Exception as e: + msg = ERROR_MAP[ErrorType.FILE_READ_ERROR].format( + filename=file_path, error=e) + logger.error(msg) + return [], msg + else: + msg = ERROR_MAP[ErrorType.FILE_TYPE_ERROR].format(filename=file_path) + logger.error(msg) + return [], msg + + +def sub_placeholder(expression, workspace, output=None): + # 考虑输入参数可能是数字 + expression = str(expression) + + def replace(match): + keys = match.group(1).split('.') + operation_name = keys[0] + key_name = keys[1] + + if output: + return str(output.get(keys, keys)) + else: + history = workspace.variable_space.get(operation_name) + if isinstance(history, dict): + val = str(history.get(key_name, '')) + return val + # TODO 容易down + return "no value" + + def valid_checking(result, expression): + result = str(result) + if result == expression.replace("{", "").replace("}", "") and "{" in expression and "}" in expression: + return True + return False + + try: + # 匹配exp中占位的子串,并作为参数,传入replace,进行替换 + result = re.sub(r'\$\{([^{^}]*)\}', replace, expression) + if valid_checking(result, expression): + result += "\n [提示]:系统分析您的问题失败,原因可能是: \n①plugin调用不成功" \ + "\n②LLM参数解析失败,\n③SOP字段配置错误。\n您可以尝试再次输入或者调整您的问题" + return result + except TypeError as e: + logger.error(e) + return "" + + +def expression_engine(expression): + ''' + 输入文字表达,返回boolean表达 + ''' + expression = expression.replace("\n", '') + if " 属于 " in expression: + expression = expression.replace(" 属于 ", ".issubset(") + ')' + else: # TODO:更多规则 + pass + res = True + try: + res = bool(eval(expression)) + except Exception: + res = False + + return res + + +async def arun_and_time(func, *args, **kwargs): + """helper function to run and time a function. + Since function can error, we catch the error and return "ERROR" as the result + """ + start = time.time() + try: + result = await func(*args, **kwargs) + except Exception as e: + logger.error(e) + traceback.print_exc() + result = "ERROR" + end = time.time() + return result, end - start diff --git a/mxAgent/agent_sdk/executor/recipe_executor/parser.py b/mxAgent/agent_sdk/executor/recipe_executor/parser.py new file mode 100644 index 000000000..ee4ac0895 --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/parser.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from collections import OrderedDict +import re +from loguru import logger + + +class Node: + def __init__(self, + name, + content, + prompt, + step, + output, + activate, + strategy, + + toolname, + + dependency, + ): + self.step = step + self.name = name + self.input = content + self.prompt = prompt + self.activate = activate + self.strategy = strategy + self.dependency = dependency + self.output = output + self.toolname = toolname + + +class Parser: + def __init__(self, sop_dict=None): + self.sop_dict = sop_dict + + @staticmethod + def construct_graph(nodes): + graph = OrderedDict() + for operation in nodes: + operation = Node(name=operation['name'], + step=operation['step'], + content=operation['input'], + prompt=operation['prompt'], + toolname=operation['toolname'], + dependency=operation['dependency'], + output=operation["output"], + strategy=operation["strategy"], + activate=operation["activate"]) + graph[operation.name] = operation + return graph + + def parse(self, raw_dict): + nodes = [] + for operation in raw_dict: + node = { + "step": None, + "name": None, + "goal": None, + "input": None, # 输入 + "prompt": None, # llm的prompt + "activate": None, # 执行引擎,表达式验证规则 + "toolname": None, + "validation": None, # 校验结果 + "output": None, # 执行完成后赋值 dict类型 + "strategy": None, + "dependency": set(), # 依赖节点 + "err_msg": None, + "err_code": None + } + for key in operation: + node[key] = operation[key] + if key == 'dependency': + # 过滤none + node[key] = [item for item in operation[key] if item is not None] + if not node['prompt']: + node['prompt'] = "当前任务:[{{Operation.name}}],相关参数信息如下:{{Operation.input}}" + nodes.append(node) + + operations = self.construct_graph(nodes) # graph + return operations + + def walk_strings(self, inputs, output=None): + if output is None: + output = set() + regex = re.compile(r'\$v\{(.+?)\.output') + if isinstance(inputs, str): + match = regex.search(inputs) + if match: + output.add(match.group(1)) + elif isinstance(inputs, list): + for element in inputs: + try: + self.walk_strings(element, output) + except TypeError as e: + raise e + elif isinstance(inputs, dict): + for value in inputs.values(): + try: + self.walk_strings(value, output) + except TypeError as e: + raise e + else: + raise TypeError("Invalid inputs type") + return output + + + + +class ActionGraph: + def __init__(self, actions) -> None: + self.actions = actions + if len(actions) == 0: + self.start_node_id = 0 + else: + self.start_node_id = actions[next(iter(actions))] diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py new file mode 100644 index 000000000..fb59a46b8 --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/sop.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import re + +from loguru import logger + +from .parser import Node +from agent_sdk.executor.common import ErrorType, ERROR_MAP + + +class SopHandler: + def __init__(self, tool_manager) -> None: + self.tool_manager = tool_manager + self.error_info = ErrorInfo() + self.opt_map = {} + + async def async_invoke(self, action: Node): + tool_name = action.toolname + args = action.input + res = self.tool_manager.executor_call(tool_name, args) + return res + + def invoke(self, action: Node, llm): + tool_name = action.toolname + args = action.input + res = self.tool_manager.executor_call(tool_name, args, llm=llm) + # TODO 统一为api call + # res = self.tool_manager.api_call(tool_name, args) + return res + + def check_valid_sop(self, sop_dict): + res = self.check_func_param(sop_dict) + err_str = "" + cnt = 1 + for key, value in res.items(): + err_str = err_str + \ + f"{cnt}. 步骤{str(key)}的生成存在以下几个问题:\n" + "\n".join(value) + "\n" + cnt += 1 + if len(err_str) > 0: + logger.error(err_str) + return err_str + + def invalid_input(self, operation_name, key, tool_name): + api = self.tool_manager.get_api_by_name(tool_name) + if api is None: + message = ERROR_MAP[ErrorType.INVALID_TOOL_ERROR].format( + toolname=tool_name) + logger.error(message) + self.error_info.add(operation_name, message) + return False + input_parameters = self.tool_manager.get_api_by_name(tool_name)[ + 'input_parameters'] + if key not in input_parameters: + # 输入多余参数 + message = ERROR_MAP[ErrorType.INVALID_INPUT_PARAM].format( + params=key, tool=tool_name) + logger.error(message) + self.error_info.add(operation_name, message) + return True + + def invalid_output(self, operation_name, key, tool_name): + api = self.tool_manager.get_api_by_name(tool_name) + if api is None: + return False + output_parameters = self.tool_manager.get_api_by_name(tool_name)[ + 'output_parameters'] + if key not in output_parameters: + message = ERROR_MAP[ErrorType.INVALID_OUPUT_PARAM].format( + params=key, tool=tool_name) + self.error_info.add(operation_name, message) + logger.error(message) + return True + + def check_func_param(self, operations): + self.opt_map = {} + for operation in operations: + self.init_operation_dict(operation) + + for operation in operations: + self.check_operation_args(operation) + return self.error_info.store + + def check_operation_args(self, operation): + operation_name = operation['name'] + inputs = operation.get('input', {}) + tool_name = operation.get('toolname', '') + dependency = operation.get('dependency', []) + self.check_invalid_dependency(operation_name, dependency) + if isinstance(inputs, dict): + for key, param in inputs.items(): + if param is None: + continue + if not self.invalid_input(operation_name, key, tool_name): + break + holders = re.findall(r'\$\{([^}]*)\}', str(param)) + self.check_invalid_placeholder( + operation_name, holders, dependency) + else: + pass + # TODO self.error_info.add(operation_name,"input的内容应该为dict类型") + + def init_operation_dict(self, operation): + required_keys = {'name', 'toolname'} + # 检查字典中是否包含所有必要的键, 没看懂 + necessary = True + name = operation['name'] + for key in required_keys: + if key not in operation: + necessary = False + message = ERROR_MAP[ErrorType.NO_NAME_ERROR] + self.error_info.add(operation.get('name', 'None'), message) + logger.error(message) + + if name in self.opt_map.keys(): + message = ERROR_MAP[ErrorType.NODE_CONFILCT_ERROR].format( + name=name) + self.error_info.add(name, message) + logger.error(message) + necessary = False + if not necessary: + return + self.opt_map[name] = operation + + def check_invalid_dependency(self, operation_name, dependency): + for dep in dependency: + if dep is None: + continue + if len(dep) == 0: + continue + if dep not in self.opt_map.keys(): + message = ERROR_MAP[ErrorType.INVALID_DEPENDENCY].format( + node=dep) + self.error_info.add(operation_name, message) + logger.error(message) + + def check_invalid_placeholder(self, operation_name, holders, dependency): + for _, holder in enumerate(holders): + keys = holder.split('.') + holder_name = keys[0] + key_name = keys[1] + opt = self.opt_map.get(holder_name) + if holder_name not in dependency: + message = ERROR_MAP[ErrorType.NODE_NOT_DEPENDENT].format( + node=holder_name) + self.error_info.add(operation_name, message) + logger.error(message) + self.invalid_output(operation_name, key_name, opt['toolname']) + + +class ErrorInfo: + def __init__(self) -> None: + self.store = {} + + def add(self, operation, error): + err_list = self.store.get(operation, []) + err_list.append(error) + self.store[operation] = err_list diff --git a/mxAgent/agent_sdk/executor/recipe_executor/state.py b/mxAgent/agent_sdk/executor/recipe_executor/state.py new file mode 100644 index 000000000..f9379574f --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/state.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from typing import List +import json + + +class Node: + def __init__( + self, + node_id: int, + in_nodes: List = None, + out_nodes: List = None, + is_terminated: bool = False, + is_finished: bool = False, + role: str = None, + thought: str = None, + function_call: str = None, + ori_query: str = None + ): + self._node_id = node_id + self._in_nodes = in_nodes if in_nodes else [] + self._out_nodes = out_nodes if out_nodes else [] + self._is_terminated = is_terminated + self._is_finished = is_finished + self._role = role + self._thought = thought + self._function_call = function_call + self._ori_query = ori_query + + +class ExecutorState: + + def __init__(self, query: str = None, functions: List = None, + restored_node_list: List = None): + self._query = query + self._functions = functions + + # TODO + self._max_node_id = 0 + self._node_ids = [] + self._start = Node(node_id=self._max_node_id, role="start") + self._dealing_node = self._start + self._is_finished = False + self._report_message = None + self._chains: dict = dict() + self._restored_node_list = restored_node_list if restored_node_list else [] + + self.remaining_tasks = set() + self.activated_tasks = set() + self.done_tasks = set() + + self.activate_actions = [] + + # for sop planning type + self.init_query = "" + self.sop_graph = None + self.workspace = None + self.smart_selection_target_name = None + self.wait_user_sop_selection = "" + self.sop_functions = None + self.wait_user_input = False + self.wait_user_input_askuser = False + self.wait_user_feedback = False + self.wait_user_feedback_plugin = False + self.wait_user_plugin_result = False + self.stop_current_chat = False + + self.plugin_result = None + self.llm_response = None + self.validate = None + + +class WorkSpace: + def __init__(self, + operation_history, + variable_space): + self.operation_history = operation_history + self.variable_space = variable_space + self.last_operation = "" + + def update(self, history): + self.operation_history.append(history) + + def retrive_variable(self, operations, argument): + result = [ + self.variable_space[key1][argument] + for key1 in operations + if key1 in self.variable_space and argument in self.variable_space[key1] + ] + result = result[0] if result else [] + return result + + def map_keys(self, input_dict, mapping): + mapped_dict = {} + for key, value in input_dict.items(): + mapped_key = mapping.get(key, key) + if isinstance(value, dict): + mapped_dict[mapped_key] = self.map_keys(value, mapping) + else: + mapped_dict[mapped_key] = value + return mapped_dict + + def update_last_operation(self, operation): + self.last_operation = operation + + def get_last_result(self): + name = self.last_operation + result = self.variable_space.get(name, "") + ans = "" + if isinstance(result, dict): + for _, val in result.items(): + if not isinstance(val, str): + val = json.dumps(val, ensure_ascii=False) + if val: + ans += val + '\n' + return ans + + if result is None or len(result) == 0: + for key, value in self.variable_space.items(): + if value is not None and len(value) > 0: + ans += f"{key}: {str(value)}\n" + return ans diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/mxAgent/agent_sdk/executor/recipe_executor/task.py new file mode 100644 index 000000000..fae038c7f --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/task.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from dataclasses import dataclass +from typing import Any, Callable, Collection, Dict, List, Optional + +from utils.log import LOGGER + + +@dataclass +class Task: + idx: int + name: str + tool: Callable + args: Collection[Any] + dependencies: Collection[int] # 依赖的任务 idx + condition: str # 执行分支条件 + + observation: Optional[str] = None + is_join: bool = False + + async def __call__(self) -> Any: + LOGGER.info("running task") + x = await self.tool(*self.args) + LOGGER.info("done task") + return x + + def get_though_action_observation( + self, include_action=True, include_thought=True, include_action_idx=False + ) -> str: + thought_action_observation = "" + if self.thought and include_thought: + thought_action_observation = f"Thought: {self.thought}\n" + if include_action: + idx = str(self.idx) + ". " if include_action_idx else "" + if self.stringify_rule: + # If the user has specified a custom stringify rule for the + # function argument, use it + thought_action_observation += f"{idx}{self.stringify_rule(self.args)}\n" + else: + # Otherwise, we have a default stringify rule + thought_action_observation += ( + f"{idx}{self.name}" + # f"{_default_stringify_rule_for_arguments(self.args)}\n" + ) + if self.observation is not None: + thought_action_observation += f"Observation: {self.observation}\n" + return thought_action_observation diff --git a/mxAgent/agent_sdk/llms/llm.py b/mxAgent/agent_sdk/llms/llm.py new file mode 100644 index 000000000..8e5a5bd0c --- /dev/null +++ b/mxAgent/agent_sdk/llms/llm.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from .openai_compatible import OpenAICompatibleLLM + +BACKEND_OPENAI_COMPATIBLE = 1 + +def get_llm_backend(backend, api_base, api_key, llm_name): + if backend == BACKEND_OPENAI_COMPATIBLE: + return OpenAICompatibleLLM(api_base, api_key, llm_name) + else: + raise Exception(f"not support backend: {backend}") \ No newline at end of file diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py new file mode 100644 index 000000000..9515c1e90 --- /dev/null +++ b/mxAgent/agent_sdk/llms/openai_compatible.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from typing import List, Dict, Optional +from langchain_openai import OpenAI +from loguru import logger +from openai import OpenAI + + +class OpenAICompatibleLLM: + def __init__(self, base_url, api_key, llm_name): + self.base_url = base_url + self.api_key = api_key + self.llm_name = llm_name + self.client = OpenAI(api_key=api_key, base_url=base_url) + + def run(self, prompt, temperature=0.1, stop=None, max_tokens=4096, ismessage=False, stream=False, **kwargs): + messages = prompt if ismessage else [{"role": "user", "content": prompt}] + + if stream: + res = self._chat_stream(messages, temperature, max_tokens, stop=stop, **kwargs) + else: + res = self._chat_no_stream(messages, temperature, max_tokens, stop=stop, **kwargs) + + return res + + def _chat_stream(self, + messages: List[Dict], + temperature: float, + max_tokens: int, + stop: Optional[List[str]] = None, + **kwargs): + response = self.client.chat.completions.create( + model=self.llm_name, + messages=messages, + stop=stop, + temperature=temperature, + max_tokens=max_tokens, + stream=True, + **kwargs) + #TODO: error handling + for chunk in response: + if hasattr(chunk.choices[0].delta, + 'content') and chunk.choices[0].delta.content: + yield chunk.choices[0].delta.content + + def _chat_no_stream(self, + messages: List[Dict], + temperature: float, + max_tokens: int, + stop: Optional[List[str]] = None, + **kwargs): + response = self.client.chat.completions.create( + model=self.llm_name, + messages=messages, + stop=stop, + temperature=temperature, + max_tokens=max_tokens, + stream=False, + **kwargs) + # TODO: error handling + return response.choices[0].message.content + + + + + diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py new file mode 100644 index 000000000..300d27abb --- /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, +) + +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/requirements.txt b/mxAgent/agent_sdk/requirements.txt new file mode 100644 index 000000000..c96356599 --- /dev/null +++ b/mxAgent/agent_sdk/requirements.txt @@ -0,0 +1,19 @@ +requests=2.27.1 +tqdm +selenium=4.9.0 +bs4 +transformers +openai +pandas +datasets +peft +fschat +langchain +vllm +rouge +langchain_openai +colorlog +rouge-score +langchain-community +loguru +tiktoken \ 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 000000000..eaaec2e40 --- /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: + o = json.loads(output) + output = json.dumps(list(o[: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 000000000..d38d4be7b --- /dev/null +++ b/mxAgent/agent_sdk/toolmngt/tool_manager.py @@ -0,0 +1,112 @@ +# -*- 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}") + return None + + 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/utils/constant.py b/mxAgent/agent_sdk/utils/constant.py new file mode 100644 index 000000000..88152a34f --- /dev/null +++ b/mxAgent/agent_sdk/utils/constant.py @@ -0,0 +1,4 @@ +THOUGHT = "Thought" +ACTION = "Action" +ACTION_INPUT ="Action Input" +OBSERVATION = "Observation" \ No newline at end of file diff --git a/mxAgent/samples/basic_demo/agent_test.py b/mxAgent/samples/basic_demo/agent_test.py new file mode 100644 index 000000000..7cc0e664d --- /dev/null +++ b/mxAgent/samples/basic_demo/agent_test.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import warnings +import os + +from loguru import logger + +from agent_sdk.agentchain.react_agent import ReactAgent, ReactReflectAgent +from agent_sdk.agentchain.tool_less_agent import ToollessAgent +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAccommodations, QueryAttractions, \ + QueryGoogleDistanceMatrix, QueryTransports, Finish + +warnings.filterwarnings('ignore') + +API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") +API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") +LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") + +MAX_CONTEXT_LEN = 4096 + + +EXAMPLE = ''' +Question: Can you help with a 5 day trip from Orlando to New York? Departure date is March 10, 2022. +Thought: To create a travel itinerary, I need to find accommodations, transportation, and attractions in New York. I will first find hotels in New York. +Action: QueryAccommodations +Action Input: {"destination_city": "New York", "position": "Central Park", "rank": "four stars"} +Observation1: [{"title": "紐約市10 大最佳四星級酒店 - Tripadvisor", "url": "https://www.tripadvisor.com.hk/Hotels-g60763-zfc4-New_York_City_New_York-Hotels.html", "snippet": "紐約市四星級酒店 · 1. Moxy NYC Times Square · 3,825 則評論 · 2. 格甚溫酒店 · 1,155 則評論 · 3. 托米哈德森廣場飯店 · 3,277 則評論 · 4. 時代廣場愛迪生酒店 · 5. Hard ..."}, {"title": "中央公園酒店| 人氣優惠及套餐", "url": "https://www.agoda.com/zh-hk/park-central-hotel/hotel/new-york-ny-us.html", "snippet": "中央公園酒店是一家位於紐約市的4.0星級酒店,提供豪華的住宿體驗。酒店於2013年進行了最後一次翻新,確保客人能夠享受現代化的設施和舒適的環境。酒店擁有761間客房,提供 ..."}, {"title": "紐約中央公園艾美酒店(Le Méridien New York, Central Park)", "url": "https://www.agoda.com/zh-hk/viceroy-central-park-new-york_2/hotel/new-york-ny-us.html", "snippet": "紐約中央公園艾美酒店位於美國紐約市,是一家四星級酒店。這家酒店提供240間客房 ... 作為一家五星級酒店,紐約中央公園艾美酒店提供優質的服務和舒適的住宿環境 ..."}] +Thought: Now that I have found some hotels in New York, I will next find transportation options from Orlando to New York for the travel dates specified. I will use the QueryTransports API for this purpose. +Action: QueryTransports +Action Input: {"departure_city": "Orlando", "destination_city": "New York", "date": "2022-03-10", "requirement": "budget-friendly"} +Observation2: [{"title": "从奥兰多出发前往纽约的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/orlb/nyca/orlando-to-new-york.html", "snippet": "查找从奥兰多飞往纽约最便宜的月份 ; 3月. ¥750 起 ; 4月. ¥927 起 ; 5月. ¥1,012 起 ; 6月. ¥1,107 起 ; 7月. ¥1,283 起."}, {"title": "从奥兰多国际出发前往纽约拉瓜迪亚的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/mco/lga/orlando-international-to-new-york-laguardia.html", "snippet": "我们始终关注票价,方便您在几秒钟内锁定好价机票。目前,从奥兰多国际飞往纽约拉瓜迪亚最便宜的月份是十一月。"}] +Thought: Now that I have found some transportation options, I will proceed to find attractions in New York. I will use the QueryAttractions API to search for tourist attractions in New York. +Action: QueryAttractions +Action Input: {"destination": "New York"} +Observation3: - snippet: 從參觀紐約市最知名觀光景點開始:時代廣場、帝國大廈、自由女神像,然後利用剩餘的時間探索周圍地區推薦去處。 探索熱門體驗. 根據評等和預訂次數,看看其他旅客喜歡從事 ... + title: 紐約市10 大最佳旅遊景點(2024) - Tripadvisor + url: https://www.tripadvisor.com.hk/Attractions-g60763-Activities-New_York_City_New_York.html +- snippet: 紐約景點推薦 · 紐約景點#1 紐約中央公園 · 紐約景點#2 范德堡一號大樓 SUMMIT · 紐約景點#3 第五大道(Fifth Avenue) + · 紐約景點#4 大都會藝術博物館The ... + title: 【2024紐約景點】漫遊曼哈頓!26個必去行程&免費景點整理 + url: https://www.klook.com/zh-TW/blog/new-york-must-go/ +- snippet: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 · 紐約NewYork景點推薦-10大必去景點 · 1.中央公園(Central + Park) · 2.第五大道(Fifth Avenue) · 3.大都會 ... + title: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 + url: https://schoolaplus.com/articles-detail.asp?seq=35 +Thought: Now that I have found some attractions in New York, I will summarize the information and create a travel itinerary for the 5-day trip. I will use the Finish tool to provide the final answer. +Action: Finish +Action Input: {"plan details": "Day 1: Depart from Orlando to New York on March 10, 2022. Stay at the Park Central Hotel in Central Park. Visit the Empire State Building and Times Square. Have dinner at Lombardi's Pizza. +Day 2: Visit Central Park, the Metropolitan Museum of Art, and the American Museum of Natural History. Have lunch at Shake Shack and dinner at Le Pain Quotidien. +Day 3: Explore the Brooklyn Bridge, Brooklyn Heights, and DUMBO. Have lunch at Di Fara Pizza and dinner at Peter Luger Steak House. +Day 4: Visit the Statue of Liberty and Ellis Island. Have lunch at The Boil and dinner at Xi'an Famous Foods. +Day 5: Spend the day shopping on Fifth Avenue and visiting the Rockefeller Center. Have lunch at Shake Shack and dinner at Katz's Delicatessen."} +''' + + +def get_default_react_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] + + agent = ReactAgent(llm=llm, tool_list=tool_list, max_context_len=max_context_len) + return agent + + +def get_default_react_agent_fewshot(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] + + agent = ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) + return agent + + +def get_default_toolless_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + agent = ToollessAgent(llm=llm, max_context_len=max_context_len) + return agent + + +def get_default_react_reflect_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] + agent = ReactReflectAgent(reflect_llm=llm, react_llm=llm, example=EXAMPLE, + tool_list=tool_list, max_context_len=max_context_len) + return agent + + +def test_react_agent(): + a = get_default_react_agent_fewshot(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +def test_toolless_agent(): + a = get_default_toolless_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", + text="given information") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +def test_react_reflect_agent(): + a = get_default_react_reflect_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", + text="given information") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +if __name__ == '__main__': + logger.info("react agent test begin") + test_react_agent() + logger.info("react agent test end") + + logger.info("toolless agent test begin") + test_toolless_agent() + logger.info("toolless agent test end") + + logger.info("react reflect agent test begin") + test_react_reflect_agent() + logger.info("react reflect agent test end") + + + + + + + diff --git a/mxAgent/samples/basic_demo/agent_traj_systhesis.py b/mxAgent/samples/basic_demo/agent_traj_systhesis.py new file mode 100644 index 000000000..ced5ad1ae --- /dev/null +++ b/mxAgent/samples/basic_demo/agent_traj_systhesis.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import os +import warnings + +from langchain._api import LangChainDeprecationWarning +from loguru import logger +from tqdm import tqdm + +from agent_sdk.agentchain.react_agent import ReactAgent +from agent_sdk.common.enum_type import AgentRunStatus +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAttractions, QueryTransports, QueryAccommodations, \ + QueryRestaurants, QueryGoogleDistanceMatrix +from mxAgent.samples.basic_demo.agent_test import EXAMPLE + + +warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore', category=DeprecationWarning) +warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) + +os.environ["WORKNING_DIR"] = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") +API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") +LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") + +MAX_CONTEXT_LEN = 4096 + + +def get_default_react_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + tool_list = [QueryAttractions, QueryTransports, QueryAccommodations, QueryRestaurants, QueryGoogleDistanceMatrix] + return ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) + + +if __name__ == '__main__': + agent = get_default_react_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + + queries = [ + "Book a rental car for two people in Salt Lake City from April 15 to April 18, 2022.", + "Research and list down outdoor activities suitable for adrenaline junkies in Moab \ +between April 12 and 14, 2022.", + "Write a short itinerary for a weekend trip to Nashville, starting on April 15, including live music venues." + ] + + s = AgentRunStatus() + + for query in tqdm(queries): + result = agent.run(query) + s.total_cnt += 1 + if agent.finished: + s.success_cnt += 1 + agent.save_agent_status("./save_instructions.jsonl") + agent.reset() + logger.info("\n") + logger.info("*" * 150) + logger.info(f"Question: {query}") + logger.info("*" * 150) + logger.info(f"Final answer: {result.answer}") + logger.info("*" * 150) + logger.info(f"Trajectory Path: {result.scratchpad}") + logger.info("*" * 150) + + logger.info(f"success rates: {s}") + logger.info(f"Total success rates: {s}") diff --git a/mxAgent/samples/basic_demo/intent_router.py b/mxAgent/samples/basic_demo/intent_router.py new file mode 100644 index 000000000..3826da5bd --- /dev/null +++ b/mxAgent/samples/basic_demo/intent_router.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from loguru import logger + +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.router_agent import RouterAgent + +llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run + +INTENT = { + "query_flight": "用户期望查询航班信息", + "query_attraction": "用户期望查询旅游景点信息", + "query_hotel": "用户期望查询酒店和住宿信息", + "plan_attraction": "用户期望给出旅行规划建议", + "whimsical": "异想天开", + "other": "其他不符合上述意图的描述" +} + +querys = [ + "帮我查一下从北京去深圳的机票", + "帮我查一下北京的旅游景点", + "我想去北京旅游", + "去北京旅游可以住在哪里呢,推荐一下", + "帮我去书城买本书", "我想上天" +] + +agent = RouterAgent(llm=llm, intents=INTENT) + +for query in querys: + response = agent.run(query) + agent.reset() + logger.info(f"query: {query}, intent: {response.answer}") diff --git a/mxAgent/samples/basic_demo/traj_generate_test.py b/mxAgent/samples/basic_demo/traj_generate_test.py new file mode 100644 index 000000000..2d5a68797 --- /dev/null +++ b/mxAgent/samples/basic_demo/traj_generate_test.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json +import os +import warnings +from typing import Callable, List +from tqdm import tqdm +from loguru import logger +from langchain._api import LangChainDeprecationWarning + +from agent_sdk.agentchain.base_agent import BaseAgent +from agent_sdk.agentchain.single_action_agent import SingleActionAgent +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAccommodations, QueryAttractions, QueryRestaurants, \ + QueryTransports, QueryGoogleDistanceMatrix + + +API_BASE = "http://10.44.115.98:8006/v1" +API_KEY = "EMPTY" +MODEL_NAME = "Qwen2-7b-Instruct" + +os.environ["OPENAI_API_BASE"] = API_BASE +os.environ["OPENAI_API_KEY"] = API_KEY +os.environ["MODEL_NAME"] = MODEL_NAME +os.environ["WORKING_DIR"] = os.path.dirname( + os.path.dirname(os.path.realpath(__file__))) + +warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore', category=DeprecationWarning) +warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) + + +class TrajectoryGenerator: + + @staticmethod + def generate(output_path: str, agent: BaseAgent, load_dataset: Callable[[], List[str]], **kwargs): + questions = load_dataset() + for q in tqdm(questions): + try: + agent.run(q, **kwargs) + agent.save_agent_status(output_path) + agent.reset() + + except Exception as err: + logger.warning(f"generate traj failed, query: {q}, agent: {agent.name}, err: {err}") + continue + + @staticmethod + def _check_data_format(data): + if not isinstance(data, list): + raise ValueError("Data should be a list of dict") + + if len(data) == 0: + raise ValueError("Data should not be empty") + + if not isinstance(data[0], dict): + raise ValueError("Data item should be a dict") + + alpaca_format_keys = ["instruction", "input", "output", "status"] + data_keys_set = set(data[0].keys()) + + if not all([key in data_keys_set for key in alpaca_format_keys]): + raise ValueError("need alpaca data format") + + def _load_data_from_file(self, data_path): + if not os.path.exists(data_path): + raise FileNotFoundError(f"File not found: {data_path}") + + if data_path.endswith(".jsonl"): + data = [json.loads(l) for l in open(data_path, "r")] + else: + raise ValueError("Unknown file format") + + self._check_data_format(data) + return data + + +def get_single_action_agent(api_base, api_key, llm_name): + tool_list = [ + QueryAccommodations, QueryAttractions, QueryRestaurants, + QueryTransports, QueryGoogleDistanceMatrix + ] + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, + api_base, api_key, llm_name).run + return SingleActionAgent(llm=llm, tool_list=tool_list, max_steps=5) + + +if __name__ == '__main__': + single_agent = get_single_action_agent(API_BASE, API_KEY, MODEL_NAME) + queries = [ + "Write a review of the hotel \"The Beach House\" in Charlotte Amalie.", + "Book a flight from Evansville to Sacramento for April 10th.", + "Create a list of top 5 attractions in Hilo for a solo traveler.", + "Compare the prices of hotels in Newark for a 3-night stay.", + "Book a hotel room in Paducah for April 12th.", + "Write a travel blog post about visiting the Golden Gate Bridge in San Francisco.", + "Recommend the best mode of transportation from Flagstaff to Phoenix.", + "Determine the best time to visit the Statue of Liberty.", + "Compare the prices of car rentals in Seattle.", + "What are the top - rated museums in Harrisburg?" + ] + generator = TrajectoryGenerator() + generator.generate(output_path="./save_instructions.jsonl", agent=single_agent, + load_dataset=lambda: queries) diff --git a/mxAgent/samples/prompt_template/prompts.py b/mxAgent/samples/prompt_template/prompts.py new file mode 100644 index 000000000..e32b24ce6 --- /dev/null +++ b/mxAgent/samples/prompt_template/prompts.py @@ -0,0 +1,165 @@ +PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, please give me a '\ + detailed plan, including specifics such as flight numbers (e.g., F0123456), restaurant names, and accommodation '\ + names. 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} +Travel Plan:""" + +REACT_PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, please give '\ +me a detailed plan, including specifics such as flight numbers (e.g., F0123456), restaurant names, '\ +and hotel names. 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 common sense. Attraction visits and meals are expected to be diverse. 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). Solve this task by alternating between Thought, Action, and '\ +Observation steps. The 'Thought' phase involves reasoning about the '\ +current situation. The 'Action' phase can be of two types: +{tools} +***** 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? +You can call CostEnquiry like CostEnquiry[{{"people_number": 7,"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"}}] +You can call Finish like Finish[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 ***** + +You must use Finish to indict you have finished the task. And each action only calls one function once. +Given information: {text} +Query: {query}{scratchpad} """ + +REACT_REFLECT_PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, "\ +please give me a detailed plan, including specifics such as flight numbers (e.g., F0123456), restaurant names, "\ +and hotel names. 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 "\ +common sense. Attraction visits and meals are expected to be diverse. 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). Solve this task by alternating between Thought, Action, "\ +and Observation steps. The 'Thought' phase involves reasoning about the "\ +current situation. The 'Action' phase can be of two types: +{tools} +***** 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? +You can call CostEnquiry like CostEnquiry[{{"people_number": 7,"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"}}] +You can call Finish like Finish[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 ***** + +{reflections} + +You must use Finish to indict you have finished the task. And each action only calls one function once. +Given information: {text} +Query: {query}{scratchpad} """ + + + + +react_planner_agent_prompt = PromptTemplate( + input_variables=["text", "query", "scratchpad"], + template=REACT_PLANNER_INSTRUCTION, +) +planner_agent_prompt = PromptTemplate( + input_variables=["text", "query"], + template=PLANNER_INSTRUCTION, +) +react_reflect_planner_agent_prompt = PromptTemplate( + input_variables=["text", "query", "reflections", "scratchpad"], + template=REACT_REFLECT_PLANNER_INSTRUCTION, +) \ No newline at end of file diff --git a/mxAgent/samples/tools/__init__.py b/mxAgent/samples/tools/__init__.py new file mode 100644 index 000000000..030d89752 --- /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/google_search_api.py b/mxAgent/samples/tools/google_search_api.py new file mode 100644 index 000000000..760403863 --- /dev/null +++ b/mxAgent/samples/tools/google_search_api.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import re + +from bs4 import BeautifulSoup +from loguru import logger +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.ui import WebDriverWait + +CHROME_DRIVER_PATH = '/usr/local/share/chromedriver/chromedriver' +GOOGLE_URL = 'https://www.google.com/' + + +def google_search(keywords, max_number=3): + chrome_options = Options() + chrome_options.add_argument('--headless') + chrome_options.add_argument('--no-sandbox') + chrome_options.add_argument('--disable-dev-shm-usage') + chrome_options.add_argument('start-maximized') + chrome_options.add_argument("--disable-extensions") + chrome_options.add_argument('--disable-browser-side-navigation') + chrome_options.add_argument('enable-automation') + chrome_options.add_argument('--disable-infobars') + chrome_options.add_argument('enable-features=NetworkServiceInProcess') + driver = webdriver.Chrome( + executable_path=CHROME_DRIVER_PATH, options=chrome_options) + try: + driver.get(GOOGLE_URL) + WebDriverWait(driver, 5).until( + expected_conditions.presence_of_element_located((By.NAME, 'q'))) + box = driver.find_element(By.NAME, 'q') + box.send_keys(keywords) + box.send_keys(Keys.RETURN) + WebDriverWait(driver, 5).until( + expected_conditions.title_contains(keywords)) + bsobj = BeautifulSoup(driver.page_source, 'html.parser') + elements = bsobj.find_all('div', {'class': re.compile('MjjYud')}) + search_res = [] + for element in elements: + if element.a and element.a.h3: + link = element.a['href'] + title = element.a.h3.text.strip() + else: + continue + high_light = element.find('em', {'class': re.compile('t55VCb')}) + if high_light is None: + continue + parent = high_light.parent + res = { + "snippet": parent.text.strip(), + "title": title.strip(), + "url": link.strip() + } + search_res.append(res) + if len(search_res) >= max_number: # 收集指定数量的网页 + break + return search_res + except TimeoutError as e: + logger.error(f"timeout: {e}") + finally: + driver.quit() + return search_res diff --git a/mxAgent/samples/tools/tool_cost_enquiry.py b/mxAgent/samples/tools/tool_cost_enquiry.py new file mode 100644 index 000000000..a2ae34c5a --- /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 000000000..9ee7aafd7 --- /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 000000000..312d6e7cf --- /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 000000000..3cf8f4d4e --- /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 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 000000000..d029d2ce0 --- /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 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 000000000..4e1c90a03 --- /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 000000000..d7d8b42a4 --- /dev/null +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -0,0 +1,64 @@ +# -*- 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 000000000..caed5ed7a --- /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 000000000..bbce62cef --- /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 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="") diff --git a/mxAgent/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py new file mode 100644 index 000000000..24a476705 --- /dev/null +++ b/mxAgent/samples/tools/tool_query_weather.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import datetime +import json +import json +import os +from zoneinfo import ZoneInfo + +import requests +import urllib3 +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger + +AMAP_API_KEY = "75bcb2edf5800884a31172dd0d970369" +WEEK_MAP = { + 0: "Monday", + 1: "Tuesday", + 2: "Wednesday", + 3: "Thursday", + 4: "Friday", + 5: "Saturday", + 6: "Sunday" +} +REQUEST_HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" +} + + +@ToolManager.register_tool() +class QueryWeather(API): + name = "QueryWeather" + description = "This API is used to query weather forecast from the network according to the user's input question," + weekday = WEEK_MAP.get(datetime.datetime.now(ZoneInfo("Asia/Shanghai")).weekday(), '') + input_parameters = { + + 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, + "province": {'type': 'str', 'description': 'The province corresponding to the city'}, + "date": {'type': 'str', + 'description':("The date of the user want to query, today is"+ + f"{datetime.date.today()}, and today is {weekday}, "+ + "please reason the date from user's query, and format with YYYY-MM-DD,") + }, + 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, + } + output_parameters = { + "forecast": {'type': 'str', + 'description': 'the weather forecast information'}, + } + + example = ( + """ + { + "destination_city": "ShenZhen", + "province": "GuangDong", + "date": "2022-10-01" + } + """) + + def __init__(self, ): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + ex = response.exception + if ex is not None: + return False + else: + return True + + def get_forecast(self, url, param, city=""): + headers = REQUEST_HEADERS + response = requests.get(url, params=param, headers=headers, timeout=5) + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + raise Exception(f"获取网页{url}内容失败") + content = response.content + text = json.loads(content) + return text.get("data") + + def get_city2province(self, url, city): + headers = REQUEST_HEADERS + params = { + "city": city, + "source": "pc" + } + response = requests.get(url, params=params, headers=headers, timeout=5) + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + raise Exception(f"获取网页{url}内容失败") + content = response.content + text = json.loads(content) + return text.get("data") + + def format_weather(self, weekly_weather): + # 精简输入 + key_keeps = [ + 'day_weather', 'day_wind_direction', 'day_wind_power', + 'max_degree', 'min_degree', 'night_weather', 'night_wind_direction', 'night_wind_power' + ] + summary_copy = [] + for key, info in weekly_weather.items(): + time = info.get('time', key) + daily = {} + if isinstance(info, dict): + info_keeps = {k: info[k] for k in key_keeps if k in info} + daily[time] = info_keeps + summary_copy.append(daily) + return summary_copy + + def format_request_param(self, data, weather_type): + for key, value in data.items(): + city2province = value.replace(" ", "").split(",") + data[key] = city2province + # 遇到城市同名,认为是市的概率大于县 + _, max_probablity = min(data.items(), key=lambda item: len(item[1])) + if len(max_probablity) >= 2: + province = max_probablity[0] + city = max_probablity[1] + country = max_probablity[2] if len(max_probablity) >= 3 else "" + params = { + "source": "pc", # 请求来源,可以填 pc 即来自PC端 + "province": province, # 省, + "city": city, # 市, + "country": country, # 县区 + "weather_type": weather_type + } + return params + + def call(self, input_parameter, **kwargs): + des = input_parameter.get('destination_city') + departure_date = input_parameter.get("date") + weather_type = "forecast_24h" + + try: + if des is None: + return self.make_response(input_parameter, results="", success=False, exception="") + try: + data = self.get_city2province("https://wis.qq.com/city/like", des) + except Exception as e: + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + if len(data) == 0: + return self.make_response(input_parameter, + results="未能找到所查询城市所在的省份或市", success=False, exception="") + + params = self.format_request_param(data, weather_type) + try: + forecast = self.get_forecast( + "https://wis.qq.com/weather/common", params) + except Exception as e: + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + weekly_weather = forecast.get(weather_type) + summary_copy = self.format_weather(weekly_weather) + if departure_date is None: + res = { + 'forecast': summary_copy + } + return self.make_response(input_parameter, results=res, exception="") + + try: + formated_departure = datetime.datetime.strptime( + departure_date, "%Y-%m-%d").date() + except ValueError as e: + logger.warning(e) + formated_departure = datetime.date.today() + gaps = (formated_departure - datetime.date.today()).days + weather_summary = summary_copy[gaps + 1:] + + if len(weather_summary) == 0: + weather_summary = "**抱歉,我最多只能查询最近7天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ + json.dumps(summary_copy, ensure_ascii=False) + res = { + 'forecast': weather_summary + } + 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="") diff --git a/mxAgent/samples/tools/tool_summary.py b/mxAgent/samples/tools/tool_summary.py new file mode 100644 index 000000000..9d1b706c9 --- /dev/null +++ b/mxAgent/samples/tools/tool_summary.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class PlanSummary(API): + name = "PlanSummary" + description = "this api uesed to summary all the travel plan." + input_parameters = { + 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, + 'accomadation': {'type': 'str', 'description': "the accomodation information"}, + 'transport': {'type': 'str', 'description': "the transport information"}, + 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, + 'duration': {'type': 'str', 'description': "The days of travel"}, + } + + output_parameters = { + 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, + } + + example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ + accomadation,and transport information" + example = ( + """ + { + "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ + Borough High Street in Southwark and King William Street.", + "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ + culture, shopping, and green spaces", + "transport": "10 hours from Beijing to London cost $1000.", + } + """) + + def __init__(self): + pass + + def format_tool_input_parameters(self, llm_output) -> dict: + return llm_output if llm_output else {} + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + if response['exception'] is None: + return True + else: + return False + + def call(self, input_parameters, **kwargs): + # 总的只能输入2500个字左右 + attraction = input_parameters.get('attractions') + hotel = input_parameters.get('accomadation') + transport = input_parameters.get('transport') + weather = input_parameters.get("weather") + duration = input_parameters.get("duration") + + res = "" + if duration is not None: + res += f"【用户需要旅行的天数】:{duration}天\n" + if attraction is not None: + res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" + if hotel is not None: + res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" + if transport is not None: + res = res + f"【交通安排】:\n{str(transport)[:500]}\n" + if weather is not None: + res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" + summary = { + "summary": res + } + return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/samples/tools/web_summary_api.py b/mxAgent/samples/tools/web_summary_api.py new file mode 100644 index 000000000..6b08526bc --- /dev/null +++ b/mxAgent/samples/tools/web_summary_api.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import asyncio +import os +import re +import time +from concurrent.futures import ThreadPoolExecutor, wait, as_completed + +import aiohttp +import requests +import tiktoken +import urllib3 +from bs4 import BeautifulSoup +from loguru import logger +from samples.tools.google_search_api import google_search + + +def check_number_input(num, crow): + if not num.isdigit(): + return False + num = int(num) + if num > crow: + return False + return True + + +async def bai_du(url): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, + headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: + async with session.get(url) as response: + res = await response.text() + return res + + +class WebSummary: + encoder = tiktoken.get_encoding("gpt2") + + @classmethod + def get_detail_copy(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = requests.get( + url, headers=headers, timeout=(3, 3), stream=True) + mommt = time.time() + logger.info(f"finish request website: {mommt},{url}") + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + return '', f"获取网页{url}内容失败" + + content = response.content + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + except Exception as e: + logger.error(e) + return '', e + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, None + + @classmethod + async def get_details(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' + urllib3.disable_warnings() + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = await bai_du(url) + mommt = time.time() + logger.debug(f"finish request website: {mommt},{url}") + content = response + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + if 'PleaseenableJSanddisableanyadblocker' in text: + text = "" + except Exception as e: + logger.error(e) + return '', e + if len(text) == 0: + return "", "no valid website content" + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, None + + @classmethod + def summary_call(cls, web, max_summary_number, summary_prompt): + title = web.get("title", "") + url = web.get("url") + snippet = web.get("snippet", "") + web_summary = {} + if url is None: + return web_summary + + web_summary['title'] = title + web_summary['url'] = url + try: + content = asyncio.run(cls.get_details(url, summary_prompt)) + except Exception as e: + logger.error(e) + if not isinstance(content, str) or len(content) == 0: + web_summary['snippet'] = snippet + else: + web_summary['content'] = content + + return web_summary + + @classmethod + def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): + logger.add('app.log', level='DEBUG') + cls.llm = llm + try: + mommt = time.time() + logger.debug(f"start google search: {mommt}") + if isinstance(keys, list): + keys = ",".join(keys) + search_result = google_search(keys, search_num) + mommt = time.time() + logger.debug(f"finish google search: {mommt}") + except Exception as e: + logger.error(e) + return [] + + max_summary_number = summary_num + + webs = [] + with ThreadPoolExecutor(max_workers=3) as executor: + futures = [] + for web in search_result: + thread = executor.submit( + cls.summary_call, web, max_summary_number, summary_prompt) + futures.append(thread) + for future in as_completed(futures): + webs.append(future.result()) + wait(futures) + return webs + + @classmethod + def build_summary_prompt(cls, query, prompt): + max_input_token_num = 4096 + if len(query) == 0: + return prompt.format(text=query) + input_token_len = len(WebSummary.encoder.encode(query)) + prompt_len = len(WebSummary.encoder.encode(prompt)) + clip_text_index = int( + len(query) * (max_input_token_num - prompt_len) / input_token_len) + clip_text = query[:clip_text_index] + return prompt.format(input=clip_text) + + @classmethod + def generate_content(cls, query, prompt): + max_tokens = 1000 + try: + pmt = WebSummary.build_summary_prompt(query, prompt) + output = cls.llm(prompt=pmt, max_tokens=max_tokens) + except Exception as e: + logger.error(e) + return e + return output diff --git a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py new file mode 100644 index 000000000..21ca5035f --- /dev/null +++ b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py @@ -0,0 +1,62 @@ +import streamlit as st +from samples.travel_agent.travelagent import TravelAgent + +if __name__ == "__main__": + st.set_page_config( + page_title = "旅游规划agent", + page_icon = "./logo.jpg" + ) + st.logo("logo.jpg") + st.markdown('

旅游规划Agent

', unsafe_allow_html=True) + + + placeholder1 = st.empty() + placeholder2 = st.empty() + placeholder3 = st.empty() + + if "messages" not in st.session_state: + st.session_state.messages = [] + + if "aagent" not in st.session_state: + st.session_state.agent = TravelAgent() + + with placeholder1: + container = st.container(height=300,border=False) + with placeholder2: + _,col1,_ = st.columns([10,2,10]) + with col1: + st.image("logo.jpg", use_column_width=True) + with placeholder3: + _,col2,_ = st.columns([1,20,1]) + helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

+

例如:从北京到西安旅游规划

+

例如:西安有哪些免费的博物馆景点

+

例如:查一下西安的酒店

""" + with col2: + st.markdown(helloinfo,unsafe_allow_html=True) + + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.empty() + st.markdown(message["content"]) + + if prompt := st.chat_input("send message"): + st.session_state.messages.append({"role":"user", "content":prompt}) + placeholder1.empty() + placeholder2.empty() + placeholder3.empty() + + agent = st.session_state["agent"] + + with st.chat_message("user"): + st.markdown(prompt) + + with st.chat_message("assistant"): + with st.spinner("thinking..."): + response = agent.run(query=prompt, stream=True) + if isinstance(response, str): + st.markdown(response) + else: + response = st.write_stream(response) + + st.session_state.messages.append({"role":"assistant", "content":response}) diff --git a/mxAgent/samples/travel_agent_demo/travelagent.py b/mxAgent/samples/travel_agent_demo/travelagent.py new file mode 100644 index 000000000..d7dec2b8a --- /dev/null +++ b/mxAgent/samples/travel_agent_demo/travelagent.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import enum +from abc import ABC +from loguru import logger + +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.recipe_agent import RecipeAgent +from agent_sdk.agentchain.router_agent import RouterAgent +from agent_sdk.agentchain.tool_less_agent import ToollessAgent +from agent_sdk.agentchain.base_agent import AgentRunResult + +from samples.tools.tool_query_accommodations import QueryAccommodations +from samples.tools.tool_query_transports import QueryTransports +from samples.tools.tool_query_attractions import QueryAttractions +from samples.tools.tool_summary import PlanSummary +from samples.tools.tool_general_query import GeneralQuery +from samples.tools.tool_query_weather import QueryWeather + + +PESUEDE_CODE = """步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息 +步骤2:根据用户的问题,从网络中查询相关的出行交通信息。 +步骤3:根据用户的问题,从网络中搜索相关的住宿和酒店信息; +步骤4:根据用户的问题,查询用户需要的城市天气情况; +步骤5:总结以上的出行信息、景点游玩、住宿信息等。""" +TRANSPORT_INST = """步骤1:根据用户的输入问题,从网络中查询相关的出行交通信息""" +ATTRACTION_INST = "步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息" +HOTEL_INST = """步骤1:根据用户的问题,从网络中搜索相关的住宿和酒店信息,""" +WEATHER_INST = "步骤一:根据用户的问题,查询用户需要的城市天气情况" +OTHER_INST = """步骤1:根据用户的输入,从互联网中查询相关的解答""" + + +GENERAL_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页摘要信息进行总结,并以markdown的格式进行返回, +请添加适当的词语,使得语句内容连贯,通顺 +请将content和snippet的信息进行综合处理,进行总结,生成一个段落。 +涉及到url字段时,使用超链接的格式将网页url链接到网页title上。 +参数介绍】: +title:网页标题 +url:网页链接 +snippet:网页摘要信息 +content:网页的内容总结 +下面是JSON格式的输入: +{text} +请生成markdown段落:""" + +WEATHER_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手, +当前的工作场景是:天气出行建议;输入的内容是JSON格式的用户所查询城市未来的天气预报,请将这些信息总结为的自然语言展示天气预报的信息,并对用户的出游给除建议, +根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 +请添加适当的词语,使得语句内容连贯,通顺,并尽可能保留输入的信息和数据,但不要自行杜撰信息。 +提供的信息以JSON的格式进行展示 +【参数介绍】: +date:日期 +day_weather:白天的天气情况 +day_wind_direction:白天风向 +day_wind_power: 白天风力 +night_weather:夜晚的天气情况 +night_wind_direction:夜晚风向 +night_wind_power: 夜晚风力 +max_degree: 最高温 +min_degree:最低温 +下面是JSON格式的输入: +{text} +请生成markdown段落: +""" +PLANNER_FINAL_PROMPT = """你是一个擅长规划和文字处理的智能助手,你需要将提供的信息按照下面的步骤撰写一份旅游攻略,输出markdown格式的段落, +你可以添加适当的语句,使得段落通顺,但不要自己杜撰信息。 +步骤】 +1. 根据【用户需要旅行的天数】,将输入的景点分配到每一天的行程中,每天2-3个景点,并介绍景点的详细情况 +2. 叙述输入中推荐的住宿情况,详细介绍酒店的详细情况,和预定链接 +3. 叙述输入中查询的交通安排,详细介绍每个出行方案的价格、时间、时长等详细情况,和预定链接 +4. 介绍输入中天气预报的情况,根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 +【参数介绍】: +title:网页标题 +url:网页链接,满足用户需求的酒店筛选结果 +content:网页主要内容提取物 +snippet:网页摘要信息 +输入的信息以JSON格式,下面是的输入: +{text} +请生成markdown段落:""" + + + +TRAVEL_PLAN = "TRAVEL_PLAN" +QUERY_ATTRACTION = "QUERY_ATTRACTION" +QUERY_HOTEL = "QUERY_HOTEL" +QUERY_TRANSPORT = "QUERY_TRANSPORT" +QUERY_WEATHER = "QUERY_WEATHER" +OTHERS = "OTHERS" + +classifer = [TRAVEL_PLAN, QUERY_ATTRACTION, QUERY_HOTEL, QUERY_TRANSPORT, QUERY_WEATHER, OTHERS] + +INST_MAP = { + TRAVEL_PLAN :PESUEDE_CODE, + QUERY_ATTRACTION :ATTRACTION_INST, + QUERY_HOTEL:HOTEL_INST, + QUERY_TRANSPORT :TRANSPORT_INST, + QUERY_WEATHER :WEATHER_INST, + OTHERS:OTHER_INST +} + +FINAL_PMT_MAP = { + TRAVEL_PLAN :PLANNER_FINAL_PROMPT, + QUERY_ATTRACTION :GENERAL_FINAL_PROMPT, + QUERY_HOTEL:GENERAL_FINAL_PROMPT, + QUERY_TRANSPORT :GENERAL_FINAL_PROMPT, + QUERY_WEATHER :WEATHER_FINAL_PROMPT, + OTHERS: GENERAL_FINAL_PROMPT + +} + +TOOL_LIST_MAP = { + TRAVEL_PLAN :[QueryAccommodations, QueryAttractions, QueryTransports, PlanSummary, QueryWeather], + QUERY_ATTRACTION :[QueryAttractions], + QUERY_HOTEL:[QueryAccommodations], + QUERY_TRANSPORT :[QueryTransports], + QUERY_WEATHER : [QueryWeather], + OTHERS:[] +} + +intents = { + TRAVEL_PLAN :"询问旅行规划,问题中要求旅游项目日程安排、交通查询、查询当地住宿等方面的能力", + QUERY_ATTRACTION :"查询旅游项目、景区、旅游活动", + QUERY_HOTEL: "仅查询酒店和住宿信息", + QUERY_TRANSPORT : "与现实中出行、乘坐交通、如高铁、动车、飞机、火车等相关的意图", + QUERY_WEATHER :"包括气温、湿度、降水等与天气、天气预报相关的意图", + OTHERS :"与旅游场景不相干的查询" +} +llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run + + +class TalkShowAgent(ToollessAgent, ABC): + def __init__(self, llm, prompt="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:'\ + 目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐,请利用你的知识回答问题,这是用户的问题:{query}", + **kwargs): + super().__init__(llm, prompt, **kwargs) + self.query = "" + + def _build_agent_prompt(self, **kwargs): + return self.prompt.format( + query=self.query + ) +class TravelAgent: + @classmethod + def route_query(cls, query): + router_agent = RouterAgent(llm=llm, intents=intents) + classify = router_agent.run(query).answer + if classify not in classifer or classify == OTHERS: + return TalkShowAgent(llm=llm) + return RecipeAgent(name=classify, + description="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐", + llm=llm, + tool_list=TOOL_LIST_MAP[classify], + recipe=INST_MAP[classify], + max_steps=3, + max_token_number=4096, + final_prompt=FINAL_PMT_MAP[classify]) + def run(self, query, stream): + agent = self.route_query(query) + return agent.run(query, stream=stream) + +if __name__ == "__main__": + # query = "去北京的旅游规划" + # query = "从北京到西安的机票" + # query = "查询北京王府井附近的高档酒店" + # query = "泰国有哪些值得推荐的景点" + # query = "帮我查一下北京最近的天气" + # query = "上海酒店查询" + # query = "北京到上海的高铁" + # query = "上海天气怎么样" + query = "帮我制定一份从北京到上海6天的旅游计划" + + travel_agent = TravelAgent() + res = travel_agent.run(query, stream=True) + if isinstance(res, AgentRunResult): + logger.info("-----------run agent success-------------") + logger.info(res.answer) + else: + for char in res: + print(char, end="") -- Gitee From a37b7347db21da7d5bb8f24cc57a0039af322eed Mon Sep 17 00:00:00 2001 From: huxinjia Date: Tue, 19 Nov 2024 15:15:36 +0800 Subject: [PATCH 02/30] Update react_agent.py --- mxAgent/agent_sdk/agentchain/react_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mxAgent/agent_sdk/agentchain/react_agent.py b/mxAgent/agent_sdk/agentchain/react_agent.py index 630865154..0a98c1740 100644 --- a/mxAgent/agent_sdk/agentchain/react_agent.py +++ b/mxAgent/agent_sdk/agentchain/react_agent.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# Copyright (c) LangChain, Inc. import copy import json -- Gitee From bc66895e41fec98b629b8b60f9d095546bfe1cef Mon Sep 17 00:00:00 2001 From: huxinjia Date: Tue, 19 Nov 2024 15:26:49 +0800 Subject: [PATCH 03/30] add copyright --- mxAgent/agent_sdk/executor/recipe_executor/task.py | 2 +- mxAgent/agent_sdk/llms/openai_compatible.py | 1 + mxAgent/agent_sdk/prompts/pre_prompt.py | 1 + mxAgent/agent_sdk/toolmngt/tool_manager.py | 1 + mxAgent/samples/tools/tool_query_distance_matrix.py | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/mxAgent/agent_sdk/executor/recipe_executor/task.py index fae038c7f..cf7f9c53b 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/task.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/task.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - +# Copyright (c) 2023 Sehoon Kim from dataclasses import dataclass from typing import Any, Callable, Collection, Dict, List, Optional diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py index 9515c1e90..6d75f4d5f 100644 --- a/mxAgent/agent_sdk/llms/openai_compatible.py +++ b/mxAgent/agent_sdk/llms/openai_compatible.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# Copyright 2022-2023 Alibaba ModelScope. All rights reserved. from typing import List, Dict, Optional from langchain_openai import OpenAI diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py index 300d27abb..2bdcf5d82 100644 --- a/mxAgent/agent_sdk/prompts/pre_prompt.py +++ b/mxAgent/agent_sdk/prompts/pre_prompt.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# Copyright (c) 2024 OSU Natural Language Processing from datetime import date from langchain.prompts import PromptTemplate diff --git a/mxAgent/agent_sdk/toolmngt/tool_manager.py b/mxAgent/agent_sdk/toolmngt/tool_manager.py index d38d4be7b..90eb3c723 100644 --- a/mxAgent/agent_sdk/toolmngt/tool_manager.py +++ b/mxAgent/agent_sdk/toolmngt/tool_manager.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# Copyright (c) 2022 Alibaba Research import importlib.util import json diff --git a/mxAgent/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py index d7d8b42a4..7cfa880d7 100644 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# Copyright (c) 2024 OSU Natural Language Processing import json import os -- Gitee From 7d67e826e8af6a5a6bf2263314340e0e5177e2a9 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Wed, 20 Nov 2024 14:41:56 +0800 Subject: [PATCH 04/30] fix clean code --- .vscode/settings.json | 3 +++ .../agentchain/single_action_agent.py | 11 ++++----- .../executor/recipe_executor/executor.py | 14 ++++++----- .../agent_sdk/executor/recipe_executor/sop.py | 4 ++-- .../executor/recipe_executor/state.py | 3 --- .../executor/recipe_executor/task.py | 1 + mxAgent/agent_sdk/llms/llm.py | 1 + mxAgent/agent_sdk/llms/openai_compatible.py | 2 -- mxAgent/agent_sdk/toolmngt/api.py | 4 ++-- mxAgent/agent_sdk/toolmngt/tool_manager.py | 2 +- mxAgent/agent_sdk/utils/constant.py | 2 +- .../samples/basic_demo/traj_generate_test.py | 6 ++--- .../tools/tool_query_accommodations.py | 2 +- .../samples/tools/tool_query_attractions.py | 2 +- .../samples/tools/tool_query_transports.py | 2 +- mxAgent/samples/tools/tool_query_weather.py | 4 ++-- mxAgent/samples/tools/web_summary_api.py | 4 ++-- .../front/chat_bot_release.py | 12 +++++----- .../samples/travel_agent_demo/travelagent.py | 23 +++++++++++-------- 19 files changed, 52 insertions(+), 50 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..5d035e1ca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "D:/2024/agent-sdk/mindxsdk-referenceapps/contrib/CarPlateRecognition" +} \ No newline at end of file diff --git a/mxAgent/agent_sdk/agentchain/single_action_agent.py b/mxAgent/agent_sdk/agentchain/single_action_agent.py index 7a2de8de9..122d22089 100644 --- a/mxAgent/agent_sdk/agentchain/single_action_agent.py +++ b/mxAgent/agent_sdk/agentchain/single_action_agent.py @@ -122,13 +122,10 @@ class SingleActionAgent(BaseAgent): ) def _build_final_prompt(self, tool_output, **kwargs): - if self.final_prompt is None: - return None - else: - return self.final_prompt.format( - query=self.query, - answer=tool_output - ) + return self.final_prompt.format( + query=self.query, + answer=tool_output + ) def _prompt_agent(self, llm, **kwargs): result = "" diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/mxAgent/agent_sdk/executor/recipe_executor/executor.py index 011045aac..5b30c3497 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/executor.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/executor.py @@ -83,7 +83,7 @@ class AgentExecutor(): # if any of a node's previous actions has no input # ie. this node's previous operation name not in history, this node cannot execute, so pass if not set(action.dependency).issubset(set(history_names)): - return None + raise Exception("dependency action hasn't executed") node = action if node.activate: # 用表达引擎,执行看是否满足准入要求 @@ -92,7 +92,7 @@ class AgentExecutor(): bool_activate = expression_engine(node.activate) if not bool_activate: - return None + raise Exception("action hasn't activate") if action.input: for key, value in action.input.items(): @@ -133,8 +133,10 @@ class AgentExecutor(): if action in activated: continue - # TODO : 问题:当前的action并不知道入参来自哪来的时候 - node = self.process_action_args(node, executor_state,) + try: + node = self.process_action_args(node, executor_state,) + except Exception as e: + continue if node is not None: pending_actions.append(node) else: @@ -174,7 +176,7 @@ class AgentExecutor(): th = executor.submit(self.run_task, task, executor_state, llm) thread_list.append(th) executor_state.activated_tasks.add(task.name) - # TODO 某个task执行失败,会被保留,循环 + # todo 某个task执行失败,会被保留,循环 for future in as_completed(thread_list): with self.lock: self.update_history(future.result(), executor_state) @@ -313,7 +315,7 @@ def sub_placeholder(expression, workspace, output=None): if isinstance(history, dict): val = str(history.get(key_name, '')) return val - # TODO 容易down + # todo 容易down return "no value" def valid_checking(result, expression): diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py index fb59a46b8..2f26c786b 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/sop.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/sop.py @@ -26,7 +26,7 @@ class SopHandler: tool_name = action.toolname args = action.input res = self.tool_manager.executor_call(tool_name, args, llm=llm) - # TODO 统一为api call + # 统一为api call # res = self.tool_manager.api_call(tool_name, args) return res @@ -99,7 +99,7 @@ class SopHandler: operation_name, holders, dependency) else: pass - # TODO self.error_info.add(operation_name,"input的内容应该为dict类型") + # todo self.error_info.add(operation_name,"input的内容应该为dict类型") def init_operation_dict(self, operation): required_keys = {'name', 'toolname'} diff --git a/mxAgent/agent_sdk/executor/recipe_executor/state.py b/mxAgent/agent_sdk/executor/recipe_executor/state.py index f9379574f..eeae064e7 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/state.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/state.py @@ -35,8 +35,6 @@ class ExecutorState: restored_node_list: List = None): self._query = query self._functions = functions - - # TODO self._max_node_id = 0 self._node_ids = [] self._start = Node(node_id=self._max_node_id, role="start") @@ -49,7 +47,6 @@ class ExecutorState: self.remaining_tasks = set() self.activated_tasks = set() self.done_tasks = set() - self.activate_actions = [] # for sop planning type diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/mxAgent/agent_sdk/executor/recipe_executor/task.py index cf7f9c53b..99b47aecb 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/task.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/task.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. # Copyright (c) 2023 Sehoon Kim + from dataclasses import dataclass from typing import Any, Callable, Collection, Dict, List, Optional diff --git a/mxAgent/agent_sdk/llms/llm.py b/mxAgent/agent_sdk/llms/llm.py index 8e5a5bd0c..a8d5da6e8 100644 --- a/mxAgent/agent_sdk/llms/llm.py +++ b/mxAgent/agent_sdk/llms/llm.py @@ -5,6 +5,7 @@ from .openai_compatible import OpenAICompatibleLLM BACKEND_OPENAI_COMPATIBLE = 1 + def get_llm_backend(backend, api_base, api_key, llm_name): if backend == BACKEND_OPENAI_COMPATIBLE: return OpenAICompatibleLLM(api_base, api_key, llm_name) diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py index 6d75f4d5f..e30976eb3 100644 --- a/mxAgent/agent_sdk/llms/openai_compatible.py +++ b/mxAgent/agent_sdk/llms/openai_compatible.py @@ -39,7 +39,6 @@ class OpenAICompatibleLLM: max_tokens=max_tokens, stream=True, **kwargs) - #TODO: error handling for chunk in response: if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: @@ -59,7 +58,6 @@ class OpenAICompatibleLLM: max_tokens=max_tokens, stream=False, **kwargs) - # TODO: error handling return response.choices[0].message.content diff --git a/mxAgent/agent_sdk/toolmngt/api.py b/mxAgent/agent_sdk/toolmngt/api.py index eaaec2e40..1230ecd61 100644 --- a/mxAgent/agent_sdk/toolmngt/api.py +++ b/mxAgent/agent_sdk/toolmngt/api.py @@ -50,8 +50,8 @@ class API(ABC): p = self.format_tool_input_parameter(param) output = self.call(p).output try: - o = json.loads(output) - output = json.dumps(list(o[:1])) + output_json = json.loads(output) + output = json.dumps(list(output_json[:1])) except Exception as e: logger.error(e) diff --git a/mxAgent/agent_sdk/toolmngt/tool_manager.py b/mxAgent/agent_sdk/toolmngt/tool_manager.py index 90eb3c723..3db994f3f 100644 --- a/mxAgent/agent_sdk/toolmngt/tool_manager.py +++ b/mxAgent/agent_sdk/toolmngt/tool_manager.py @@ -63,7 +63,7 @@ class ToolManager: if _name == name: return api logger.error(f"failed to get_api_by_name={name}") - return None + 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() diff --git a/mxAgent/agent_sdk/utils/constant.py b/mxAgent/agent_sdk/utils/constant.py index 88152a34f..eb1202dca 100644 --- a/mxAgent/agent_sdk/utils/constant.py +++ b/mxAgent/agent_sdk/utils/constant.py @@ -1,4 +1,4 @@ THOUGHT = "Thought" ACTION = "Action" -ACTION_INPUT ="Action Input" +ACTION_INPUT = "Action Input" OBSERVATION = "Observation" \ No newline at end of file diff --git a/mxAgent/samples/basic_demo/traj_generate_test.py b/mxAgent/samples/basic_demo/traj_generate_test.py index 2d5a68797..2c8704ec8 100644 --- a/mxAgent/samples/basic_demo/traj_generate_test.py +++ b/mxAgent/samples/basic_demo/traj_generate_test.py @@ -68,12 +68,12 @@ class TrajectoryGenerator: raise FileNotFoundError(f"File not found: {data_path}") if data_path.endswith(".jsonl"): - data = [json.loads(l) for l in open(data_path, "r")] + traj_data = [json.loads(l) for l in open(data_path, "r")] else: raise ValueError("Unknown file format") - self._check_data_format(data) - return data + self._check_data_format(traj_data) + return traj_data def get_single_action_agent(api_base, api_key, llm_name): diff --git a/mxAgent/samples/tools/tool_query_accommodations.py b/mxAgent/samples/tools/tool_query_accommodations.py index 3cf8f4d4e..c0bba1103 100644 --- a/mxAgent/samples/tools/tool_query_accommodations.py +++ b/mxAgent/samples/tools/tool_query_accommodations.py @@ -56,7 +56,7 @@ class QueryAccommodations(API): for val in keys: if val is None or len(val) == 0: continue - if '无' in val or '未' in val or '没' in val or '否' in val: + if '无' in val or '未' in val or '没' in val: continue if isinstance(val, list): it = flatten(val) diff --git a/mxAgent/samples/tools/tool_query_attractions.py b/mxAgent/samples/tools/tool_query_attractions.py index d029d2ce0..7bb076e80 100644 --- a/mxAgent/samples/tools/tool_query_attractions.py +++ b/mxAgent/samples/tools/tool_query_attractions.py @@ -61,7 +61,7 @@ class QueryAttractions(API): for val in keys: if val is None or len(val) == 0: continue - if '无' in val or '未' in val or '没' in val or '否' in val: + if '无' in val or '未' in val or '没' in val: continue if isinstance(val, list): it = flatten(val) diff --git a/mxAgent/samples/tools/tool_query_transports.py b/mxAgent/samples/tools/tool_query_transports.py index bbce62cef..b48f03585 100644 --- a/mxAgent/samples/tools/tool_query_transports.py +++ b/mxAgent/samples/tools/tool_query_transports.py @@ -62,7 +62,7 @@ class QueryTransports(API): for val in keys: if val is None or len(val) == 0: continue - if '无' in val or '未' in val or '没' in val or '否' in val: + if '无' in val or '未' in val or '没' in val: continue filtered.append(val) if len(filtered) == 0: diff --git a/mxAgent/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py index 24a476705..7fe739bc3 100644 --- a/mxAgent/samples/tools/tool_query_weather.py +++ b/mxAgent/samples/tools/tool_query_weather.py @@ -39,8 +39,8 @@ class QueryWeather(API): 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, "province": {'type': 'str', 'description': 'The province corresponding to the city'}, "date": {'type': 'str', - 'description':("The date of the user want to query, today is"+ - f"{datetime.date.today()}, and today is {weekday}, "+ + 'description': ("The date of the user want to query, today is" + + f"{datetime.date.today()}, and today is {weekday}, " + "please reason the date from user's query, and format with YYYY-MM-DD,") }, 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, diff --git a/mxAgent/samples/tools/web_summary_api.py b/mxAgent/samples/tools/web_summary_api.py index 6b08526bc..feeb4bb5b 100644 --- a/mxAgent/samples/tools/web_summary_api.py +++ b/mxAgent/samples/tools/web_summary_api.py @@ -71,7 +71,7 @@ class WebSummary: res = cls.generate_content(text, summary_prompt) mommt = time.time() logger.info(f"finish summary website: {mommt},{url}") - return res, None + return res, "" @classmethod async def get_details(cls, url, summary_prompt): @@ -98,7 +98,7 @@ class WebSummary: res = cls.generate_content(text, summary_prompt) mommt = time.time() logger.info(f"finish summary website: {mommt},{url}") - return res, None + return res, "" @classmethod def summary_call(cls, web, max_summary_number, summary_prompt): diff --git a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py index 21ca5035f..f2ad8673b 100644 --- a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py +++ b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py @@ -3,8 +3,8 @@ from samples.travel_agent.travelagent import TravelAgent if __name__ == "__main__": st.set_page_config( - page_title = "旅游规划agent", - page_icon = "./logo.jpg" + page_title="旅游规划agent", + page_icon="./logo.jpg" ) st.logo("logo.jpg") st.markdown('

旅游规划Agent

', unsafe_allow_html=True) @@ -21,19 +21,19 @@ if __name__ == "__main__": st.session_state.agent = TravelAgent() with placeholder1: - container = st.container(height=300,border=False) + container = st.container(height=300, border=False) with placeholder2: - _,col1,_ = st.columns([10,2,10]) + _,col1,_ = st.columns([10, 2, 10]) with col1: st.image("logo.jpg", use_column_width=True) with placeholder3: - _,col2,_ = st.columns([1,20,1]) + _,col2,_ = st.columns([1, 20, 1]) helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

例如:从北京到西安旅游规划

例如:西安有哪些免费的博物馆景点

例如:查一下西安的酒店

""" with col2: - st.markdown(helloinfo,unsafe_allow_html=True) + st.markdown(helloinfo, unsafe_allow_html=True) for message in st.session_state.messages: with st.chat_message(message["role"]): diff --git a/mxAgent/samples/travel_agent_demo/travelagent.py b/mxAgent/samples/travel_agent_demo/travelagent.py index d7dec2b8a..ab39a9c76 100644 --- a/mxAgent/samples/travel_agent_demo/travelagent.py +++ b/mxAgent/samples/travel_agent_demo/travelagent.py @@ -141,6 +141,8 @@ class TalkShowAgent(ToollessAgent, ABC): return self.prompt.format( query=self.query ) + + class TravelAgent: @classmethod def route_query(cls, query): @@ -156,23 +158,24 @@ class TravelAgent: max_steps=3, max_token_number=4096, final_prompt=FINAL_PMT_MAP[classify]) + def run(self, query, stream): agent = self.route_query(query) return agent.run(query, stream=stream) if __name__ == "__main__": - # query = "去北京的旅游规划" - # query = "从北京到西安的机票" - # query = "查询北京王府井附近的高档酒店" - # query = "泰国有哪些值得推荐的景点" - # query = "帮我查一下北京最近的天气" - # query = "上海酒店查询" - # query = "北京到上海的高铁" - # query = "上海天气怎么样" - query = "帮我制定一份从北京到上海6天的旅游计划" + # request = "去北京的旅游规划" + # request = "从北京到西安的机票" + # request = "查询北京王府井附近的高档酒店" + # request = "泰国有哪些值得推荐的景点" + # request = "帮我查一下北京最近的天气" + # request = "上海酒店查询" + # request = "北京到上海的高铁" + # request = "上海天气怎么样" + request = "帮我制定一份从北京到上海6天的旅游计划" travel_agent = TravelAgent() - res = travel_agent.run(query, stream=True) + res = travel_agent.run(request, stream=True) if isinstance(res, AgentRunResult): logger.info("-----------run agent success-------------") logger.info(res.answer) -- Gitee From 1c7e313c751d9310a501fe748274fa0635221a5e Mon Sep 17 00:00:00 2001 From: huxinjia Date: Wed, 20 Nov 2024 15:55:03 +0800 Subject: [PATCH 05/30] clean code --- mxAgent/agent_sdk/agentchain/react_agent.py | 1 - mxAgent/agent_sdk/agentchain/single_action_agent.py | 4 ++-- .../agent_sdk/executor/recipe_executor/executor.py | 13 ++++--------- mxAgent/agent_sdk/executor/recipe_executor/sop.py | 4 +--- mxAgent/agent_sdk/executor/recipe_executor/task.py | 1 - mxAgent/agent_sdk/llms/openai_compatible.py | 2 +- mxAgent/agent_sdk/prompts/pre_prompt.py | 3 +-- mxAgent/agent_sdk/toolmngt/tool_manager.py | 2 +- mxAgent/samples/tools/tool_query_distance_matrix.py | 2 +- .../travel_agent_demo/front/chat_bot_release.py | 8 ++++---- mxAgent/samples/travel_agent_demo/travelagent.py | 10 +++++----- 11 files changed, 20 insertions(+), 30 deletions(-) diff --git a/mxAgent/agent_sdk/agentchain/react_agent.py b/mxAgent/agent_sdk/agentchain/react_agent.py index 0a98c1740..630865154 100644 --- a/mxAgent/agent_sdk/agentchain/react_agent.py +++ b/mxAgent/agent_sdk/agentchain/react_agent.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. -# Copyright (c) LangChain, Inc. import copy import json diff --git a/mxAgent/agent_sdk/agentchain/single_action_agent.py b/mxAgent/agent_sdk/agentchain/single_action_agent.py index 122d22089..6e95fec1e 100644 --- a/mxAgent/agent_sdk/agentchain/single_action_agent.py +++ b/mxAgent/agent_sdk/agentchain/single_action_agent.py @@ -11,7 +11,7 @@ from loguru import logger from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager -from agent_sdk.prompts.pre_prompt import single_action_agent_prompt, final_prompt +from agent_sdk.prompts.pre_prompt import single_action_agent_prompt, single_action_final_prompt class SingleActionAgent(BaseAgent): @@ -22,7 +22,7 @@ class SingleActionAgent(BaseAgent): llm, tool_list, prompt=single_action_agent_prompt, - final_prompt=final_prompt, + final_prompt=single_action_final_prompt, **kwargs ) -> None: diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/mxAgent/agent_sdk/executor/recipe_executor/executor.py index 5b30c3497..1a53f9516 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/executor.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/executor.py @@ -107,13 +107,10 @@ class AgentExecutor(): done_actions = executor_state.done_tasks graph = executor_state.sop_graph.actions activated = executor_state.activated_tasks - independent_actions = [ - task_name - for task_name in executor_state.remaining_tasks - if all( - d in done_actions for d in graph[task_name].dependency - ) - ] + independent_actions = [] + for task_name in executor_state.remaining_tasks: + if all(d in done_actions for d in graph[task_name].dependency): + independent_actions.append(task_name) executable_actions = [] pending_actions = [] for action in independent_actions: @@ -176,7 +173,6 @@ class AgentExecutor(): th = executor.submit(self.run_task, task, executor_state, llm) thread_list.append(th) executor_state.activated_tasks.add(task.name) - # todo 某个task执行失败,会被保留,循环 for future in as_completed(thread_list): with self.lock: self.update_history(future.result(), executor_state) @@ -315,7 +311,6 @@ def sub_placeholder(expression, workspace, output=None): if isinstance(history, dict): val = str(history.get(key_name, '')) return val - # todo 容易down return "no value" def valid_checking(result, expression): diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py index 2f26c786b..576ede785 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/sop.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/sop.py @@ -6,9 +6,8 @@ import re from loguru import logger -from .parser import Node from agent_sdk.executor.common import ErrorType, ERROR_MAP - +from .parser import Node class SopHandler: def __init__(self, tool_manager) -> None: @@ -99,7 +98,6 @@ class SopHandler: operation_name, holders, dependency) else: pass - # todo self.error_info.add(operation_name,"input的内容应该为dict类型") def init_operation_dict(self, operation): required_keys = {'name', 'toolname'} diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/mxAgent/agent_sdk/executor/recipe_executor/task.py index 99b47aecb..fae038c7f 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/task.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/task.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. -# Copyright (c) 2023 Sehoon Kim from dataclasses import dataclass from typing import Any, Callable, Collection, Dict, List, Optional diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py index e30976eb3..d66ac952b 100644 --- a/mxAgent/agent_sdk/llms/openai_compatible.py +++ b/mxAgent/agent_sdk/llms/openai_compatible.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. -# Copyright 2022-2023 Alibaba ModelScope. All rights reserved. + from typing import List, Dict, Optional from langchain_openai import OpenAI diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py index 2bdcf5d82..51451e0f8 100644 --- a/mxAgent/agent_sdk/prompts/pre_prompt.py +++ b/mxAgent/agent_sdk/prompts/pre_prompt.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. -# Copyright (c) 2024 OSU Natural Language Processing from datetime import date from langchain.prompts import PromptTemplate @@ -182,7 +181,7 @@ single_action_agent_prompt = PromptTemplate( template=SINGLE_AGENT_ACTION_INSTRUCTION, ) -final_prompt = PromptTemplate( +single_action_final_prompt = PromptTemplate( input_variables=["query", "answer"], template=FINAL_PROMPT, ) diff --git a/mxAgent/agent_sdk/toolmngt/tool_manager.py b/mxAgent/agent_sdk/toolmngt/tool_manager.py index 3db994f3f..e2d1f56b7 100644 --- a/mxAgent/agent_sdk/toolmngt/tool_manager.py +++ b/mxAgent/agent_sdk/toolmngt/tool_manager.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. -# Copyright (c) 2022 Alibaba Research + import importlib.util import json diff --git a/mxAgent/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py index 7cfa880d7..f6a10c4e1 100644 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. -# Copyright (c) 2024 OSU Natural Language Processing + import json import os diff --git a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py index f2ad8673b..d74e03696 100644 --- a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py +++ b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py @@ -23,24 +23,24 @@ if __name__ == "__main__": with placeholder1: container = st.container(height=300, border=False) with placeholder2: - _,col1,_ = st.columns([10, 2, 10]) + _, col1, _ = st.columns([10, 2, 10]) with col1: st.image("logo.jpg", use_column_width=True) with placeholder3: - _,col2,_ = st.columns([1, 20, 1]) + _, col2, _ = st.columns([1, 20, 1]) helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

例如:从北京到西安旅游规划

例如:西安有哪些免费的博物馆景点

例如:查一下西安的酒店

""" with col2: - st.markdown(helloinfo, unsafe_allow_html=True) + st.markdown(helloinfo, unsafe_allow_html=True) for message in st.session_state.messages: with st.chat_message(message["role"]): st.empty() st.markdown(message["content"]) - if prompt := st.chat_input("send message"): + if prompt = st.chat_input("send message"): st.session_state.messages.append({"role":"user", "content":prompt}) placeholder1.empty() placeholder2.empty() diff --git a/mxAgent/samples/travel_agent_demo/travelagent.py b/mxAgent/samples/travel_agent_demo/travelagent.py index ab39a9c76..247402940 100644 --- a/mxAgent/samples/travel_agent_demo/travelagent.py +++ b/mxAgent/samples/travel_agent_demo/travelagent.py @@ -126,7 +126,7 @@ intents = { QUERY_WEATHER :"包括气温、湿度、降水等与天气、天气预报相关的意图", OTHERS :"与旅游场景不相干的查询" } -llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, +LLM_MODEL = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run @@ -146,13 +146,13 @@ class TalkShowAgent(ToollessAgent, ABC): class TravelAgent: @classmethod def route_query(cls, query): - router_agent = RouterAgent(llm=llm, intents=intents) + router_agent = RouterAgent(llm=LLM_MODEL, intents=intents) classify = router_agent.run(query).answer if classify not in classifer or classify == OTHERS: - return TalkShowAgent(llm=llm) + return TalkShowAgent(llm=LLM_MODEL) return RecipeAgent(name=classify, description="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐", - llm=llm, + llm=LLM_MODEL, tool_list=TOOL_LIST_MAP[classify], recipe=INST_MAP[classify], max_steps=3, @@ -181,4 +181,4 @@ if __name__ == "__main__": logger.info(res.answer) else: for char in res: - print(char, end="") + logger.info(char) -- Gitee From ae24d027144ab403d6012d1bc3dc9298144b42ba Mon Sep 17 00:00:00 2001 From: huxinjia Date: Wed, 20 Nov 2024 16:11:24 +0800 Subject: [PATCH 06/30] fix clean code --- mxAgent/agent_sdk/executor/recipe_executor/executor.py | 2 +- mxAgent/agent_sdk/executor/recipe_executor/sop.py | 1 + mxAgent/samples/basic_demo/traj_generate_test.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/mxAgent/agent_sdk/executor/recipe_executor/executor.py index 1a53f9516..93581364d 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/executor.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/executor.py @@ -133,7 +133,7 @@ class AgentExecutor(): try: node = self.process_action_args(node, executor_state,) except Exception as e: - continue + node = None if node is not None: pending_actions.append(node) else: diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py index 576ede785..436902c7b 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/sop.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/sop.py @@ -9,6 +9,7 @@ from loguru import logger from agent_sdk.executor.common import ErrorType, ERROR_MAP from .parser import Node + class SopHandler: def __init__(self, tool_manager) -> None: self.tool_manager = tool_manager diff --git a/mxAgent/samples/basic_demo/traj_generate_test.py b/mxAgent/samples/basic_demo/traj_generate_test.py index 2c8704ec8..bd4b73048 100644 --- a/mxAgent/samples/basic_demo/traj_generate_test.py +++ b/mxAgent/samples/basic_demo/traj_generate_test.py @@ -68,7 +68,7 @@ class TrajectoryGenerator: raise FileNotFoundError(f"File not found: {data_path}") if data_path.endswith(".jsonl"): - traj_data = [json.loads(l) for l in open(data_path, "r")] + traj_data = [json.loads(line) for line in open(data_path, "r")] else: raise ValueError("Unknown file format") -- Gitee From c92b7f804529eec08586843d37c3df02fae2df28 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 09:31:03 +0800 Subject: [PATCH 07/30] push repo --- mxAgent/agent_sdk/executor/common.py | 45 --- .../executor/recipe_executor/executor.py | 364 ------------------ .../executor/recipe_executor/parser.py | 117 ------ .../agent_sdk/executor/recipe_executor/sop.py | 158 -------- .../executor/recipe_executor/state.py | 120 ------ .../executor/recipe_executor/task.py | 48 --- mxAgent/agent_sdk/prompts/pre_prompt.py | 192 --------- mxAgent/agent_sdk/toolmngt/api.py | 97 ----- mxAgent/agent_sdk/toolmngt/tool_manager.py | 113 ------ mxAgent/samples/basic_demo/agent_test.py | 133 ------- .../basic_demo/agent_traj_systhesis.py | 66 ---- mxAgent/samples/basic_demo/intent_router.py | 34 -- .../samples/basic_demo/traj_generate_test.py | 105 ----- mxAgent/samples/prompt_template/prompts.py | 165 -------- mxAgent/samples/tools/__init__.py | 22 -- mxAgent/samples/tools/google_search_api.py | 67 ---- mxAgent/samples/tools/tool_cost_enquiry.py | 66 ---- mxAgent/samples/tools/tool_finish.py | 49 --- mxAgent/samples/tools/tool_general_query.py | 79 ---- .../tools/tool_query_accommodations.py | 103 ----- .../samples/tools/tool_query_attractions.py | 114 ------ mxAgent/samples/tools/tool_query_city.py | 92 ----- .../tools/tool_query_distance_matrix.py | 65 ---- .../samples/tools/tool_query_restaurants.py | 40 -- .../samples/tools/tool_query_transports.py | 96 ----- mxAgent/samples/tools/tool_query_weather.py | 185 --------- mxAgent/samples/tools/tool_summary.py | 71 ---- mxAgent/samples/tools/web_summary_api.py | 176 --------- .../front/chat_bot_release.py | 62 --- .../samples/travel_agent_demo/travelagent.py | 184 --------- 30 files changed, 3228 deletions(-) delete mode 100644 mxAgent/agent_sdk/executor/common.py delete mode 100644 mxAgent/agent_sdk/executor/recipe_executor/executor.py delete mode 100644 mxAgent/agent_sdk/executor/recipe_executor/parser.py delete mode 100644 mxAgent/agent_sdk/executor/recipe_executor/sop.py delete mode 100644 mxAgent/agent_sdk/executor/recipe_executor/state.py delete mode 100644 mxAgent/agent_sdk/executor/recipe_executor/task.py delete mode 100644 mxAgent/agent_sdk/prompts/pre_prompt.py delete mode 100644 mxAgent/agent_sdk/toolmngt/api.py delete mode 100644 mxAgent/agent_sdk/toolmngt/tool_manager.py delete mode 100644 mxAgent/samples/basic_demo/agent_test.py delete mode 100644 mxAgent/samples/basic_demo/agent_traj_systhesis.py delete mode 100644 mxAgent/samples/basic_demo/intent_router.py delete mode 100644 mxAgent/samples/basic_demo/traj_generate_test.py delete mode 100644 mxAgent/samples/prompt_template/prompts.py delete mode 100644 mxAgent/samples/tools/__init__.py delete mode 100644 mxAgent/samples/tools/google_search_api.py delete mode 100644 mxAgent/samples/tools/tool_cost_enquiry.py delete mode 100644 mxAgent/samples/tools/tool_finish.py delete mode 100644 mxAgent/samples/tools/tool_general_query.py delete mode 100644 mxAgent/samples/tools/tool_query_accommodations.py delete mode 100644 mxAgent/samples/tools/tool_query_attractions.py delete mode 100644 mxAgent/samples/tools/tool_query_city.py delete mode 100644 mxAgent/samples/tools/tool_query_distance_matrix.py delete mode 100644 mxAgent/samples/tools/tool_query_restaurants.py delete mode 100644 mxAgent/samples/tools/tool_query_transports.py delete mode 100644 mxAgent/samples/tools/tool_query_weather.py delete mode 100644 mxAgent/samples/tools/tool_summary.py delete mode 100644 mxAgent/samples/tools/web_summary_api.py delete mode 100644 mxAgent/samples/travel_agent_demo/front/chat_bot_release.py delete mode 100644 mxAgent/samples/travel_agent_demo/travelagent.py diff --git a/mxAgent/agent_sdk/executor/common.py b/mxAgent/agent_sdk/executor/common.py deleted file mode 100644 index 2780bd79f..000000000 --- a/mxAgent/agent_sdk/executor/common.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import enum - - -class ErrorType(enum.Enum): - INPUT_NOT_STR = 0 - FILE_TYPE_ERROR = 1 - FILE_READ_ERROR = 2 - NO_DICT_ERROR = 3 - YAML_LOAD_ERROR = 3 - INVALID_TOOL_ERROR = 4 - NO_NAME_ERROR = 5 - INVALID_INPUT_PARAM = 6 - INVALID_OUPUT_PARAM = 7 - NODE_CONFILCT_ERROR = 8 - INVALID_DEPENDENCY = 9 - NODE_NOT_DEPENDENT = 10 - - -ERROR_MAP = { - ErrorType.INPUT_NOT_STR: "Invalid arguements type: the content need a str", - ErrorType.FILE_TYPE_ERROR: "The type of file {filename} is not supported, only json or yaml is fine", - ErrorType.FILE_READ_ERROR: "Failed to read the file {filename}: Error message: {error}", - ErrorType.NO_DICT_ERROR: "The content cannot be converted to dict format.", - ErrorType.YAML_LOAD_ERROR: "Loading content with YAML error: {error}", - ErrorType.INVALID_TOOL_ERROR: "The tool {toolname} doesn't exist.", - ErrorType.NO_NAME_ERROR: "some nodes did not assign a value to a necessary parameter [name] and [toolname].", - ErrorType.INVALID_INPUT_PARAM: "Additional parameters {params} were input during the calling tool {tool}.", - ErrorType.INVALID_OUPUT_PARAM: "These parameters {params} do not exist in the output of the node {node}.", - ErrorType.NODE_CONFILCT_ERROR: "the Node name {name} is duplicated.", - ErrorType.INVALID_DEPENDENCY: "The current node depends on node {node} that does not exist in the plan.", - ErrorType.NODE_NOT_DEPENDENT: "The output of node {node} is being used, but it is not within the dependency" - -} - - -class PlanStrategyType(enum.Enum): - REACT = 1 - RESEARCH = 2 - COT = 3 - EMPTY = 4 - SOP = 5 - DHP = 6 \ No newline at end of file diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/mxAgent/agent_sdk/executor/recipe_executor/executor.py deleted file mode 100644 index 93581364d..000000000 --- a/mxAgent/agent_sdk/executor/recipe_executor/executor.py +++ /dev/null @@ -1,364 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from typing import Dict -import asyncio -import time -import re -import traceback -import json -import os -import threading -from concurrent.futures import ThreadPoolExecutor, as_completed -import yaml -from loguru import logger - -from agent_sdk.executor.recipe_executor.state import ExecutorState, WorkSpace -from agent_sdk.executor.recipe_executor.parser import Node, Parser, ActionGraph -from agent_sdk.executor.recipe_executor.sop import SopHandler -from agent_sdk.executor.common import ERROR_MAP, ErrorType, PlanStrategyType - -ALL_PLAN_STRATEGIES = { - PlanStrategyType.SOP.value: "sop" -} - - -class AgentExecutor(): - tasks_done: Dict[str, asyncio.Event] - - def __init__( - self, - tool_manager, - plan_strategy_type: PlanStrategyType = None, - ): - self.plan_strategy_type = plan_strategy_type or PlanStrategyType.SOP - self.plan_strategy = ALL_PLAN_STRATEGIES.get( - self.plan_strategy_type.value) - self.parser = Parser() - self.tool_manager = tool_manager - self.operation_handler = SopHandler(tool_manager) - self.lock = threading.Lock() - - @staticmethod - def parser_output(output, cur_operation): - content = output - if isinstance(output, str): - try: - content = json.loads(output) - except json.JSONDecodeError: - content = output - else: - content = output - cur_operation.output = content - history = { - "operation_name": cur_operation.name, - "dependecy": cur_operation.dependency, - "input": cur_operation.input, - "output": content, - "activate": cur_operation.activate, # 激活该分支的条件 - "interaction_history": "", # discuss - "variable_space": "" # discuss - } - cur_operation.history = history - return content, history - - @staticmethod - def update_history(result, executor_state): - name = result['action'] - parsed_out = result['output'] - history = result['history'] - executor_state.workspace.variable_space[name] = parsed_out - executor_state.workspace.update(history) - # 执行完毕,从set中移除 - executor_state.remaining_tasks.remove(name) - executor_state.activated_tasks.remove(name) - executor_state.done_tasks.add(name) - executor_state.workspace.update_last_operation(name) - - @staticmethod - def process_action_args(action: Node, executor_state): - # 处理参数依赖的替换 - history_names = [history["operation_name"] - for history in executor_state.workspace.operation_history] - # if any of a node's previous actions has no input - # ie. this node's previous operation name not in history, this node cannot execute, so pass - if not set(action.dependency).issubset(set(history_names)): - raise Exception("dependency action hasn't executed") - node = action - if node.activate: - # 用表达引擎,执行看是否满足准入要求 - node.activate = sub_placeholder( - node.activate, executor_state.workspace) - bool_activate = expression_engine(node.activate) - - if not bool_activate: - raise Exception("action hasn't activate") - - if action.input: - for key, value in action.input.items(): - if value is None: - logger.warning('the value of [%s] is None', key) - else: - value = sub_placeholder(value, executor_state.workspace) - action.input[key] = value - return action - - def get_executable_actions(self, executor_state): - done_actions = executor_state.done_tasks - graph = executor_state.sop_graph.actions - activated = executor_state.activated_tasks - independent_actions = [] - for task_name in executor_state.remaining_tasks: - if all(d in done_actions for d in graph[task_name].dependency): - independent_actions.append(task_name) - executable_actions = [] - pending_actions = [] - for action in independent_actions: - node = graph[action] - if node.activate: - node.activate = sub_placeholder( - node.activate, executor_state.workspace) - if not node.activate: - break - bool_activate = expression_engine(node.activate) - - if not bool_activate: - executor_state.remaining_tasks.remove(action) - executor_state.done_tasks.add(action) - continue - executable_actions.append(action) - - if action in activated: - continue - try: - node = self.process_action_args(node, executor_state,) - except Exception as e: - node = None - if node is not None: - pending_actions.append(node) - else: - continue - return pending_actions - - def run_task(self, action, executor_state, llm): - graph = executor_state.sop_graph.actions - sop_handler = self.operation_handler - mommt = time.time() - logger.debug(f'{action.name} start:{mommt}') - output = sop_handler.invoke(action, llm=llm) - - parsed_out, history = self.parser_output(output, action) - graph[action.name].output = parsed_out - - res = { - "action": action.name, - "output": parsed_out, - "history": history - } - - mommt = time.time() - logger.debug(f'step {action.step}. action: {action.name} has finished') - return res - - - - def async_run(self, content, llm): - executor_state = self.init_state(content) - executor = ThreadPoolExecutor(max_workers=5) - with ThreadPoolExecutor(max_workers=5) as executor: - while executor_state.remaining_tasks: # 活跃的 - executable_tasks = self.get_executable_actions(executor_state) - thread_list = [] - for task in executable_tasks: - th = executor.submit(self.run_task, task, executor_state, llm) - thread_list.append(th) - executor_state.activated_tasks.add(task.name) - for future in as_completed(thread_list): - with self.lock: - self.update_history(future.result(), executor_state) - return executor_state.workspace.get_last_result() - - # 而此处的写法不关注next,关注dependency - def run(self, content): - executor_state = self.init_state(content) - - sop_handler = self.operation_handler - activate_actions = self.get_executable_actions(executor_state) - executor_state.activate_actions = activate_actions - while executor_state.activate_actions: # 活跃的 - - cur_operation = executor_state.activate_actions.pop( - 0) # starts from right - mommt = time.time() - logger.error("start run step %d, action [%s]", cur_operation.step, cur_operation.name) - output = sop_handler.invoke(cur_operation) - parsed_out, history = self.parser_output(output, cur_operation) - executor_state.workspace.update(history) - # 保存结果 - executor_state.workspace.variable_space[cur_operation.name] = parsed_out - executor_state.workspace.update_last_operation( - cur_operation.name) - # 更新状态 - executor_state.remaining_tasks.remove(cur_operation.name) - executor_state.done_tasks.add(cur_operation.name) - executable_actions = self.get_executable_actions(executor_state) - executor_state.activate_actions = (executable_actions) - - return executor_state.workspace.get_last_result() - - def check_valid(self, content): - if not isinstance(content, str): - return False, ERROR_MAP[ErrorType.INPUT_NOT_STR] - absolute_path = os.getcwd() - if os.path.isfile(os.path.join(absolute_path, content)): - raw_dict, err = fetch_file_content(content) - else: - raw_dict, err = fetch_str_content(content) - if len(err) > 0: - return False, err - err = self.operation_handler.check_valid_sop(raw_dict) - return len(err) == 0, err - - def init_state(self, content): - if not isinstance(content, str): - raise TypeError("Invalid arguements type: the content need a str") - absolute_path = os.getcwd() - if os.path.isfile(os.path.join(absolute_path, content)): - raw_dict, err = fetch_file_content(content) - else: - raw_dict, err = fetch_str_content(content) - - filter_opt = [] - for operation in raw_dict: - operation_name = operation['toolname'] - api = self.tool_manager.get_api_by_name(operation_name) - if api is not None: - filter_opt.append(operation) - operations = self.parser.parse(raw_dict=filter_opt) - - execute_state = ExecutorState() - workspace = WorkSpace(operation_history=[], variable_space={}) - graph = ActionGraph(operations) - - execute_state.workspace = workspace - execute_state.sop_graph = graph - execute_state.remaining_tasks = {k for k, _ in graph.actions.items()} - return execute_state - - -def fetch_str_content(content): - start_identify = "```yaml" - end_identify = "```" - start = content.find(start_identify) - end = content.rfind(end_identify) - if start != -1 and end != -1: - content = content[start + len(start_identify): end] - try: - code_seg = content.strip("\n").split("\n") - while code_seg[0] == start_identify: - code_seg.pop(0) - while code_seg[-1] == end_identify: - code_seg.pop() - data = yaml.safe_load("\n".join(code_seg)) - except yaml.YAMLError as e: - logger.error(f"生成yaml代码块错误:{str(e)}") - return [], ERROR_MAP[ErrorType.YAML_LOAD_ERROR] - return data, '' - - -def fetch_file_content(file_path): - # 获取文件扩展名 - file_extension = os.path.splitext(file_path)[1].lower() - if file_extension == '.yaml': - try: - with open(file_path, 'r') as file: - yaml_content = yaml.safe_load(file) - return yaml_content, '' - except Exception as e: - msg = ERROR_MAP[ErrorType.FILE_READ_ERROR].format( - filename=file_path, error=e) - logger.error(msg) - return [], msg - elif file_extension == '.json': - try: - with open(file_path, 'r') as file: - json_content = json.load(file) - return json_content, '' - except Exception as e: - msg = ERROR_MAP[ErrorType.FILE_READ_ERROR].format( - filename=file_path, error=e) - logger.error(msg) - return [], msg - else: - msg = ERROR_MAP[ErrorType.FILE_TYPE_ERROR].format(filename=file_path) - logger.error(msg) - return [], msg - - -def sub_placeholder(expression, workspace, output=None): - # 考虑输入参数可能是数字 - expression = str(expression) - - def replace(match): - keys = match.group(1).split('.') - operation_name = keys[0] - key_name = keys[1] - - if output: - return str(output.get(keys, keys)) - else: - history = workspace.variable_space.get(operation_name) - if isinstance(history, dict): - val = str(history.get(key_name, '')) - return val - return "no value" - - def valid_checking(result, expression): - result = str(result) - if result == expression.replace("{", "").replace("}", "") and "{" in expression and "}" in expression: - return True - return False - - try: - # 匹配exp中占位的子串,并作为参数,传入replace,进行替换 - result = re.sub(r'\$\{([^{^}]*)\}', replace, expression) - if valid_checking(result, expression): - result += "\n [提示]:系统分析您的问题失败,原因可能是: \n①plugin调用不成功" \ - "\n②LLM参数解析失败,\n③SOP字段配置错误。\n您可以尝试再次输入或者调整您的问题" - return result - except TypeError as e: - logger.error(e) - return "" - - -def expression_engine(expression): - ''' - 输入文字表达,返回boolean表达 - ''' - expression = expression.replace("\n", '') - if " 属于 " in expression: - expression = expression.replace(" 属于 ", ".issubset(") + ')' - else: # TODO:更多规则 - pass - res = True - try: - res = bool(eval(expression)) - except Exception: - res = False - - return res - - -async def arun_and_time(func, *args, **kwargs): - """helper function to run and time a function. - Since function can error, we catch the error and return "ERROR" as the result - """ - start = time.time() - try: - result = await func(*args, **kwargs) - except Exception as e: - logger.error(e) - traceback.print_exc() - result = "ERROR" - end = time.time() - return result, end - start diff --git a/mxAgent/agent_sdk/executor/recipe_executor/parser.py b/mxAgent/agent_sdk/executor/recipe_executor/parser.py deleted file mode 100644 index ee4ac0895..000000000 --- a/mxAgent/agent_sdk/executor/recipe_executor/parser.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from collections import OrderedDict -import re -from loguru import logger - - -class Node: - def __init__(self, - name, - content, - prompt, - step, - output, - activate, - strategy, - - toolname, - - dependency, - ): - self.step = step - self.name = name - self.input = content - self.prompt = prompt - self.activate = activate - self.strategy = strategy - self.dependency = dependency - self.output = output - self.toolname = toolname - - -class Parser: - def __init__(self, sop_dict=None): - self.sop_dict = sop_dict - - @staticmethod - def construct_graph(nodes): - graph = OrderedDict() - for operation in nodes: - operation = Node(name=operation['name'], - step=operation['step'], - content=operation['input'], - prompt=operation['prompt'], - toolname=operation['toolname'], - dependency=operation['dependency'], - output=operation["output"], - strategy=operation["strategy"], - activate=operation["activate"]) - graph[operation.name] = operation - return graph - - def parse(self, raw_dict): - nodes = [] - for operation in raw_dict: - node = { - "step": None, - "name": None, - "goal": None, - "input": None, # 输入 - "prompt": None, # llm的prompt - "activate": None, # 执行引擎,表达式验证规则 - "toolname": None, - "validation": None, # 校验结果 - "output": None, # 执行完成后赋值 dict类型 - "strategy": None, - "dependency": set(), # 依赖节点 - "err_msg": None, - "err_code": None - } - for key in operation: - node[key] = operation[key] - if key == 'dependency': - # 过滤none - node[key] = [item for item in operation[key] if item is not None] - if not node['prompt']: - node['prompt'] = "当前任务:[{{Operation.name}}],相关参数信息如下:{{Operation.input}}" - nodes.append(node) - - operations = self.construct_graph(nodes) # graph - return operations - - def walk_strings(self, inputs, output=None): - if output is None: - output = set() - regex = re.compile(r'\$v\{(.+?)\.output') - if isinstance(inputs, str): - match = regex.search(inputs) - if match: - output.add(match.group(1)) - elif isinstance(inputs, list): - for element in inputs: - try: - self.walk_strings(element, output) - except TypeError as e: - raise e - elif isinstance(inputs, dict): - for value in inputs.values(): - try: - self.walk_strings(value, output) - except TypeError as e: - raise e - else: - raise TypeError("Invalid inputs type") - return output - - - - -class ActionGraph: - def __init__(self, actions) -> None: - self.actions = actions - if len(actions) == 0: - self.start_node_id = 0 - else: - self.start_node_id = actions[next(iter(actions))] diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py deleted file mode 100644 index 436902c7b..000000000 --- a/mxAgent/agent_sdk/executor/recipe_executor/sop.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - -import re - -from loguru import logger - -from agent_sdk.executor.common import ErrorType, ERROR_MAP -from .parser import Node - - -class SopHandler: - def __init__(self, tool_manager) -> None: - self.tool_manager = tool_manager - self.error_info = ErrorInfo() - self.opt_map = {} - - async def async_invoke(self, action: Node): - tool_name = action.toolname - args = action.input - res = self.tool_manager.executor_call(tool_name, args) - return res - - def invoke(self, action: Node, llm): - tool_name = action.toolname - args = action.input - res = self.tool_manager.executor_call(tool_name, args, llm=llm) - # 统一为api call - # res = self.tool_manager.api_call(tool_name, args) - return res - - def check_valid_sop(self, sop_dict): - res = self.check_func_param(sop_dict) - err_str = "" - cnt = 1 - for key, value in res.items(): - err_str = err_str + \ - f"{cnt}. 步骤{str(key)}的生成存在以下几个问题:\n" + "\n".join(value) + "\n" - cnt += 1 - if len(err_str) > 0: - logger.error(err_str) - return err_str - - def invalid_input(self, operation_name, key, tool_name): - api = self.tool_manager.get_api_by_name(tool_name) - if api is None: - message = ERROR_MAP[ErrorType.INVALID_TOOL_ERROR].format( - toolname=tool_name) - logger.error(message) - self.error_info.add(operation_name, message) - return False - input_parameters = self.tool_manager.get_api_by_name(tool_name)[ - 'input_parameters'] - if key not in input_parameters: - # 输入多余参数 - message = ERROR_MAP[ErrorType.INVALID_INPUT_PARAM].format( - params=key, tool=tool_name) - logger.error(message) - self.error_info.add(operation_name, message) - return True - - def invalid_output(self, operation_name, key, tool_name): - api = self.tool_manager.get_api_by_name(tool_name) - if api is None: - return False - output_parameters = self.tool_manager.get_api_by_name(tool_name)[ - 'output_parameters'] - if key not in output_parameters: - message = ERROR_MAP[ErrorType.INVALID_OUPUT_PARAM].format( - params=key, tool=tool_name) - self.error_info.add(operation_name, message) - logger.error(message) - return True - - def check_func_param(self, operations): - self.opt_map = {} - for operation in operations: - self.init_operation_dict(operation) - - for operation in operations: - self.check_operation_args(operation) - return self.error_info.store - - def check_operation_args(self, operation): - operation_name = operation['name'] - inputs = operation.get('input', {}) - tool_name = operation.get('toolname', '') - dependency = operation.get('dependency', []) - self.check_invalid_dependency(operation_name, dependency) - if isinstance(inputs, dict): - for key, param in inputs.items(): - if param is None: - continue - if not self.invalid_input(operation_name, key, tool_name): - break - holders = re.findall(r'\$\{([^}]*)\}', str(param)) - self.check_invalid_placeholder( - operation_name, holders, dependency) - else: - pass - - def init_operation_dict(self, operation): - required_keys = {'name', 'toolname'} - # 检查字典中是否包含所有必要的键, 没看懂 - necessary = True - name = operation['name'] - for key in required_keys: - if key not in operation: - necessary = False - message = ERROR_MAP[ErrorType.NO_NAME_ERROR] - self.error_info.add(operation.get('name', 'None'), message) - logger.error(message) - - if name in self.opt_map.keys(): - message = ERROR_MAP[ErrorType.NODE_CONFILCT_ERROR].format( - name=name) - self.error_info.add(name, message) - logger.error(message) - necessary = False - if not necessary: - return - self.opt_map[name] = operation - - def check_invalid_dependency(self, operation_name, dependency): - for dep in dependency: - if dep is None: - continue - if len(dep) == 0: - continue - if dep not in self.opt_map.keys(): - message = ERROR_MAP[ErrorType.INVALID_DEPENDENCY].format( - node=dep) - self.error_info.add(operation_name, message) - logger.error(message) - - def check_invalid_placeholder(self, operation_name, holders, dependency): - for _, holder in enumerate(holders): - keys = holder.split('.') - holder_name = keys[0] - key_name = keys[1] - opt = self.opt_map.get(holder_name) - if holder_name not in dependency: - message = ERROR_MAP[ErrorType.NODE_NOT_DEPENDENT].format( - node=holder_name) - self.error_info.add(operation_name, message) - logger.error(message) - self.invalid_output(operation_name, key_name, opt['toolname']) - - -class ErrorInfo: - def __init__(self) -> None: - self.store = {} - - def add(self, operation, error): - err_list = self.store.get(operation, []) - err_list.append(error) - self.store[operation] = err_list diff --git a/mxAgent/agent_sdk/executor/recipe_executor/state.py b/mxAgent/agent_sdk/executor/recipe_executor/state.py deleted file mode 100644 index eeae064e7..000000000 --- a/mxAgent/agent_sdk/executor/recipe_executor/state.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from typing import List -import json - - -class Node: - def __init__( - self, - node_id: int, - in_nodes: List = None, - out_nodes: List = None, - is_terminated: bool = False, - is_finished: bool = False, - role: str = None, - thought: str = None, - function_call: str = None, - ori_query: str = None - ): - self._node_id = node_id - self._in_nodes = in_nodes if in_nodes else [] - self._out_nodes = out_nodes if out_nodes else [] - self._is_terminated = is_terminated - self._is_finished = is_finished - self._role = role - self._thought = thought - self._function_call = function_call - self._ori_query = ori_query - - -class ExecutorState: - - def __init__(self, query: str = None, functions: List = None, - restored_node_list: List = None): - self._query = query - self._functions = functions - self._max_node_id = 0 - self._node_ids = [] - self._start = Node(node_id=self._max_node_id, role="start") - self._dealing_node = self._start - self._is_finished = False - self._report_message = None - self._chains: dict = dict() - self._restored_node_list = restored_node_list if restored_node_list else [] - - self.remaining_tasks = set() - self.activated_tasks = set() - self.done_tasks = set() - self.activate_actions = [] - - # for sop planning type - self.init_query = "" - self.sop_graph = None - self.workspace = None - self.smart_selection_target_name = None - self.wait_user_sop_selection = "" - self.sop_functions = None - self.wait_user_input = False - self.wait_user_input_askuser = False - self.wait_user_feedback = False - self.wait_user_feedback_plugin = False - self.wait_user_plugin_result = False - self.stop_current_chat = False - - self.plugin_result = None - self.llm_response = None - self.validate = None - - -class WorkSpace: - def __init__(self, - operation_history, - variable_space): - self.operation_history = operation_history - self.variable_space = variable_space - self.last_operation = "" - - def update(self, history): - self.operation_history.append(history) - - def retrive_variable(self, operations, argument): - result = [ - self.variable_space[key1][argument] - for key1 in operations - if key1 in self.variable_space and argument in self.variable_space[key1] - ] - result = result[0] if result else [] - return result - - def map_keys(self, input_dict, mapping): - mapped_dict = {} - for key, value in input_dict.items(): - mapped_key = mapping.get(key, key) - if isinstance(value, dict): - mapped_dict[mapped_key] = self.map_keys(value, mapping) - else: - mapped_dict[mapped_key] = value - return mapped_dict - - def update_last_operation(self, operation): - self.last_operation = operation - - def get_last_result(self): - name = self.last_operation - result = self.variable_space.get(name, "") - ans = "" - if isinstance(result, dict): - for _, val in result.items(): - if not isinstance(val, str): - val = json.dumps(val, ensure_ascii=False) - if val: - ans += val + '\n' - return ans - - if result is None or len(result) == 0: - for key, value in self.variable_space.items(): - if value is not None and len(value) > 0: - ans += f"{key}: {str(value)}\n" - return ans diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/mxAgent/agent_sdk/executor/recipe_executor/task.py deleted file mode 100644 index fae038c7f..000000000 --- a/mxAgent/agent_sdk/executor/recipe_executor/task.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from dataclasses import dataclass -from typing import Any, Callable, Collection, Dict, List, Optional - -from utils.log import LOGGER - - -@dataclass -class Task: - idx: int - name: str - tool: Callable - args: Collection[Any] - dependencies: Collection[int] # 依赖的任务 idx - condition: str # 执行分支条件 - - observation: Optional[str] = None - is_join: bool = False - - async def __call__(self) -> Any: - LOGGER.info("running task") - x = await self.tool(*self.args) - LOGGER.info("done task") - return x - - def get_though_action_observation( - self, include_action=True, include_thought=True, include_action_idx=False - ) -> str: - thought_action_observation = "" - if self.thought and include_thought: - thought_action_observation = f"Thought: {self.thought}\n" - if include_action: - idx = str(self.idx) + ". " if include_action_idx else "" - if self.stringify_rule: - # If the user has specified a custom stringify rule for the - # function argument, use it - thought_action_observation += f"{idx}{self.stringify_rule(self.args)}\n" - else: - # Otherwise, we have a default stringify rule - thought_action_observation += ( - f"{idx}{self.name}" - # f"{_default_stringify_rule_for_arguments(self.args)}\n" - ) - if self.observation is not None: - thought_action_observation += f"Observation: {self.observation}\n" - return thought_action_observation diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py deleted file mode 100644 index 51451e0f8..000000000 --- a/mxAgent/agent_sdk/prompts/pre_prompt.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- 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 deleted file mode 100644 index 1230ecd61..000000000 --- a/mxAgent/agent_sdk/toolmngt/api.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- 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 deleted file mode 100644 index e2d1f56b7..000000000 --- a/mxAgent/agent_sdk/toolmngt/tool_manager.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- 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/samples/basic_demo/agent_test.py b/mxAgent/samples/basic_demo/agent_test.py deleted file mode 100644 index 7cc0e664d..000000000 --- a/mxAgent/samples/basic_demo/agent_test.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import warnings -import os - -from loguru import logger - -from agent_sdk.agentchain.react_agent import ReactAgent, ReactReflectAgent -from agent_sdk.agentchain.tool_less_agent import ToollessAgent -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from samples.tools import QueryAccommodations, QueryAttractions, \ - QueryGoogleDistanceMatrix, QueryTransports, Finish - -warnings.filterwarnings('ignore') - -API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") -API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") -LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") - -MAX_CONTEXT_LEN = 4096 - - -EXAMPLE = ''' -Question: Can you help with a 5 day trip from Orlando to New York? Departure date is March 10, 2022. -Thought: To create a travel itinerary, I need to find accommodations, transportation, and attractions in New York. I will first find hotels in New York. -Action: QueryAccommodations -Action Input: {"destination_city": "New York", "position": "Central Park", "rank": "four stars"} -Observation1: [{"title": "紐約市10 大最佳四星級酒店 - Tripadvisor", "url": "https://www.tripadvisor.com.hk/Hotels-g60763-zfc4-New_York_City_New_York-Hotels.html", "snippet": "紐約市四星級酒店 · 1. Moxy NYC Times Square · 3,825 則評論 · 2. 格甚溫酒店 · 1,155 則評論 · 3. 托米哈德森廣場飯店 · 3,277 則評論 · 4. 時代廣場愛迪生酒店 · 5. Hard ..."}, {"title": "中央公園酒店| 人氣優惠及套餐", "url": "https://www.agoda.com/zh-hk/park-central-hotel/hotel/new-york-ny-us.html", "snippet": "中央公園酒店是一家位於紐約市的4.0星級酒店,提供豪華的住宿體驗。酒店於2013年進行了最後一次翻新,確保客人能夠享受現代化的設施和舒適的環境。酒店擁有761間客房,提供 ..."}, {"title": "紐約中央公園艾美酒店(Le Méridien New York, Central Park)", "url": "https://www.agoda.com/zh-hk/viceroy-central-park-new-york_2/hotel/new-york-ny-us.html", "snippet": "紐約中央公園艾美酒店位於美國紐約市,是一家四星級酒店。這家酒店提供240間客房 ... 作為一家五星級酒店,紐約中央公園艾美酒店提供優質的服務和舒適的住宿環境 ..."}] -Thought: Now that I have found some hotels in New York, I will next find transportation options from Orlando to New York for the travel dates specified. I will use the QueryTransports API for this purpose. -Action: QueryTransports -Action Input: {"departure_city": "Orlando", "destination_city": "New York", "date": "2022-03-10", "requirement": "budget-friendly"} -Observation2: [{"title": "从奥兰多出发前往纽约的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/orlb/nyca/orlando-to-new-york.html", "snippet": "查找从奥兰多飞往纽约最便宜的月份 ; 3月. ¥750 起 ; 4月. ¥927 起 ; 5月. ¥1,012 起 ; 6月. ¥1,107 起 ; 7月. ¥1,283 起."}, {"title": "从奥兰多国际出发前往纽约拉瓜迪亚的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/mco/lga/orlando-international-to-new-york-laguardia.html", "snippet": "我们始终关注票价,方便您在几秒钟内锁定好价机票。目前,从奥兰多国际飞往纽约拉瓜迪亚最便宜的月份是十一月。"}] -Thought: Now that I have found some transportation options, I will proceed to find attractions in New York. I will use the QueryAttractions API to search for tourist attractions in New York. -Action: QueryAttractions -Action Input: {"destination": "New York"} -Observation3: - snippet: 從參觀紐約市最知名觀光景點開始:時代廣場、帝國大廈、自由女神像,然後利用剩餘的時間探索周圍地區推薦去處。 探索熱門體驗. 根據評等和預訂次數,看看其他旅客喜歡從事 ... - title: 紐約市10 大最佳旅遊景點(2024) - Tripadvisor - url: https://www.tripadvisor.com.hk/Attractions-g60763-Activities-New_York_City_New_York.html -- snippet: 紐約景點推薦 · 紐約景點#1 紐約中央公園 · 紐約景點#2 范德堡一號大樓 SUMMIT · 紐約景點#3 第五大道(Fifth Avenue) - · 紐約景點#4 大都會藝術博物館The ... - title: 【2024紐約景點】漫遊曼哈頓!26個必去行程&免費景點整理 - url: https://www.klook.com/zh-TW/blog/new-york-must-go/ -- snippet: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 · 紐約NewYork景點推薦-10大必去景點 · 1.中央公園(Central - Park) · 2.第五大道(Fifth Avenue) · 3.大都會 ... - title: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 - url: https://schoolaplus.com/articles-detail.asp?seq=35 -Thought: Now that I have found some attractions in New York, I will summarize the information and create a travel itinerary for the 5-day trip. I will use the Finish tool to provide the final answer. -Action: Finish -Action Input: {"plan details": "Day 1: Depart from Orlando to New York on March 10, 2022. Stay at the Park Central Hotel in Central Park. Visit the Empire State Building and Times Square. Have dinner at Lombardi's Pizza. -Day 2: Visit Central Park, the Metropolitan Museum of Art, and the American Museum of Natural History. Have lunch at Shake Shack and dinner at Le Pain Quotidien. -Day 3: Explore the Brooklyn Bridge, Brooklyn Heights, and DUMBO. Have lunch at Di Fara Pizza and dinner at Peter Luger Steak House. -Day 4: Visit the Statue of Liberty and Ellis Island. Have lunch at The Boil and dinner at Xi'an Famous Foods. -Day 5: Spend the day shopping on Fifth Avenue and visiting the Rockefeller Center. Have lunch at Shake Shack and dinner at Katz's Delicatessen."} -''' - - -def get_default_react_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] - - agent = ReactAgent(llm=llm, tool_list=tool_list, max_context_len=max_context_len) - return agent - - -def get_default_react_agent_fewshot(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] - - agent = ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) - return agent - - -def get_default_toolless_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - agent = ToollessAgent(llm=llm, max_context_len=max_context_len) - return agent - - -def get_default_react_reflect_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] - agent = ReactReflectAgent(reflect_llm=llm, react_llm=llm, example=EXAMPLE, - tool_list=tool_list, max_context_len=max_context_len) - return agent - - -def test_react_agent(): - a = get_default_react_agent_fewshot(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.") - - logger.info(f"5 day trip from Orlando to Paris:{response.answer}") - - -def test_toolless_agent(): - a = get_default_toolless_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", - text="given information") - - logger.info(f"5 day trip from Orlando to Paris:{response.answer}") - - -def test_react_reflect_agent(): - a = get_default_react_reflect_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", - text="given information") - - logger.info(f"5 day trip from Orlando to Paris:{response.answer}") - - -if __name__ == '__main__': - logger.info("react agent test begin") - test_react_agent() - logger.info("react agent test end") - - logger.info("toolless agent test begin") - test_toolless_agent() - logger.info("toolless agent test end") - - logger.info("react reflect agent test begin") - test_react_reflect_agent() - logger.info("react reflect agent test end") - - - - - - - diff --git a/mxAgent/samples/basic_demo/agent_traj_systhesis.py b/mxAgent/samples/basic_demo/agent_traj_systhesis.py deleted file mode 100644 index ced5ad1ae..000000000 --- a/mxAgent/samples/basic_demo/agent_traj_systhesis.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import os -import warnings - -from langchain._api import LangChainDeprecationWarning -from loguru import logger -from tqdm import tqdm - -from agent_sdk.agentchain.react_agent import ReactAgent -from agent_sdk.common.enum_type import AgentRunStatus -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from samples.tools import QueryAttractions, QueryTransports, QueryAccommodations, \ - QueryRestaurants, QueryGoogleDistanceMatrix -from mxAgent.samples.basic_demo.agent_test import EXAMPLE - - -warnings.filterwarnings('ignore') -warnings.filterwarnings('ignore', category=DeprecationWarning) -warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) - -os.environ["WORKNING_DIR"] = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") -API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") -LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") - -MAX_CONTEXT_LEN = 4096 - - -def get_default_react_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - tool_list = [QueryAttractions, QueryTransports, QueryAccommodations, QueryRestaurants, QueryGoogleDistanceMatrix] - return ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) - - -if __name__ == '__main__': - agent = get_default_react_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - - queries = [ - "Book a rental car for two people in Salt Lake City from April 15 to April 18, 2022.", - "Research and list down outdoor activities suitable for adrenaline junkies in Moab \ -between April 12 and 14, 2022.", - "Write a short itinerary for a weekend trip to Nashville, starting on April 15, including live music venues." - ] - - s = AgentRunStatus() - - for query in tqdm(queries): - result = agent.run(query) - s.total_cnt += 1 - if agent.finished: - s.success_cnt += 1 - agent.save_agent_status("./save_instructions.jsonl") - agent.reset() - logger.info("\n") - logger.info("*" * 150) - logger.info(f"Question: {query}") - logger.info("*" * 150) - logger.info(f"Final answer: {result.answer}") - logger.info("*" * 150) - logger.info(f"Trajectory Path: {result.scratchpad}") - logger.info("*" * 150) - - logger.info(f"success rates: {s}") - logger.info(f"Total success rates: {s}") diff --git a/mxAgent/samples/basic_demo/intent_router.py b/mxAgent/samples/basic_demo/intent_router.py deleted file mode 100644 index 3826da5bd..000000000 --- a/mxAgent/samples/basic_demo/intent_router.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from loguru import logger - -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from agent_sdk.agentchain.router_agent import RouterAgent - -llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, - api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run - -INTENT = { - "query_flight": "用户期望查询航班信息", - "query_attraction": "用户期望查询旅游景点信息", - "query_hotel": "用户期望查询酒店和住宿信息", - "plan_attraction": "用户期望给出旅行规划建议", - "whimsical": "异想天开", - "other": "其他不符合上述意图的描述" -} - -querys = [ - "帮我查一下从北京去深圳的机票", - "帮我查一下北京的旅游景点", - "我想去北京旅游", - "去北京旅游可以住在哪里呢,推荐一下", - "帮我去书城买本书", "我想上天" -] - -agent = RouterAgent(llm=llm, intents=INTENT) - -for query in querys: - response = agent.run(query) - agent.reset() - logger.info(f"query: {query}, intent: {response.answer}") diff --git a/mxAgent/samples/basic_demo/traj_generate_test.py b/mxAgent/samples/basic_demo/traj_generate_test.py deleted file mode 100644 index bd4b73048..000000000 --- a/mxAgent/samples/basic_demo/traj_generate_test.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import json -import os -import warnings -from typing import Callable, List -from tqdm import tqdm -from loguru import logger -from langchain._api import LangChainDeprecationWarning - -from agent_sdk.agentchain.base_agent import BaseAgent -from agent_sdk.agentchain.single_action_agent import SingleActionAgent -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from samples.tools import QueryAccommodations, QueryAttractions, QueryRestaurants, \ - QueryTransports, QueryGoogleDistanceMatrix - - -API_BASE = "http://10.44.115.98:8006/v1" -API_KEY = "EMPTY" -MODEL_NAME = "Qwen2-7b-Instruct" - -os.environ["OPENAI_API_BASE"] = API_BASE -os.environ["OPENAI_API_KEY"] = API_KEY -os.environ["MODEL_NAME"] = MODEL_NAME -os.environ["WORKING_DIR"] = os.path.dirname( - os.path.dirname(os.path.realpath(__file__))) - -warnings.filterwarnings('ignore') -warnings.filterwarnings('ignore', category=DeprecationWarning) -warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) - - -class TrajectoryGenerator: - - @staticmethod - def generate(output_path: str, agent: BaseAgent, load_dataset: Callable[[], List[str]], **kwargs): - questions = load_dataset() - for q in tqdm(questions): - try: - agent.run(q, **kwargs) - agent.save_agent_status(output_path) - agent.reset() - - except Exception as err: - logger.warning(f"generate traj failed, query: {q}, agent: {agent.name}, err: {err}") - continue - - @staticmethod - def _check_data_format(data): - if not isinstance(data, list): - raise ValueError("Data should be a list of dict") - - if len(data) == 0: - raise ValueError("Data should not be empty") - - if not isinstance(data[0], dict): - raise ValueError("Data item should be a dict") - - alpaca_format_keys = ["instruction", "input", "output", "status"] - data_keys_set = set(data[0].keys()) - - if not all([key in data_keys_set for key in alpaca_format_keys]): - raise ValueError("need alpaca data format") - - def _load_data_from_file(self, data_path): - if not os.path.exists(data_path): - raise FileNotFoundError(f"File not found: {data_path}") - - if data_path.endswith(".jsonl"): - traj_data = [json.loads(line) for line in open(data_path, "r")] - else: - raise ValueError("Unknown file format") - - self._check_data_format(traj_data) - return traj_data - - -def get_single_action_agent(api_base, api_key, llm_name): - tool_list = [ - QueryAccommodations, QueryAttractions, QueryRestaurants, - QueryTransports, QueryGoogleDistanceMatrix - ] - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, - api_base, api_key, llm_name).run - return SingleActionAgent(llm=llm, tool_list=tool_list, max_steps=5) - - -if __name__ == '__main__': - single_agent = get_single_action_agent(API_BASE, API_KEY, MODEL_NAME) - queries = [ - "Write a review of the hotel \"The Beach House\" in Charlotte Amalie.", - "Book a flight from Evansville to Sacramento for April 10th.", - "Create a list of top 5 attractions in Hilo for a solo traveler.", - "Compare the prices of hotels in Newark for a 3-night stay.", - "Book a hotel room in Paducah for April 12th.", - "Write a travel blog post about visiting the Golden Gate Bridge in San Francisco.", - "Recommend the best mode of transportation from Flagstaff to Phoenix.", - "Determine the best time to visit the Statue of Liberty.", - "Compare the prices of car rentals in Seattle.", - "What are the top - rated museums in Harrisburg?" - ] - generator = TrajectoryGenerator() - generator.generate(output_path="./save_instructions.jsonl", agent=single_agent, - load_dataset=lambda: queries) diff --git a/mxAgent/samples/prompt_template/prompts.py b/mxAgent/samples/prompt_template/prompts.py deleted file mode 100644 index e32b24ce6..000000000 --- a/mxAgent/samples/prompt_template/prompts.py +++ /dev/null @@ -1,165 +0,0 @@ -PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, please give me a '\ - detailed plan, including specifics such as flight numbers (e.g., F0123456), restaurant names, and accommodation '\ - names. 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} -Travel Plan:""" - -REACT_PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, please give '\ -me a detailed plan, including specifics such as flight numbers (e.g., F0123456), restaurant names, '\ -and hotel names. 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 common sense. Attraction visits and meals are expected to be diverse. 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). Solve this task by alternating between Thought, Action, and '\ -Observation steps. The 'Thought' phase involves reasoning about the '\ -current situation. The 'Action' phase can be of two types: -{tools} -***** 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? -You can call CostEnquiry like CostEnquiry[{{"people_number": 7,"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"}}] -You can call Finish like Finish[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 ***** - -You must use Finish to indict you have finished the task. And each action only calls one function once. -Given information: {text} -Query: {query}{scratchpad} """ - -REACT_REFLECT_PLANNER_INSTRUCTION = """You are a proficient planner. Based on the provided information and query, "\ -please give me a detailed plan, including specifics such as flight numbers (e.g., F0123456), restaurant names, "\ -and hotel names. 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 "\ -common sense. Attraction visits and meals are expected to be diverse. 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). Solve this task by alternating between Thought, Action, "\ -and Observation steps. The 'Thought' phase involves reasoning about the "\ -current situation. The 'Action' phase can be of two types: -{tools} -***** 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? -You can call CostEnquiry like CostEnquiry[{{"people_number": 7,"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"}}] -You can call Finish like Finish[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 ***** - -{reflections} - -You must use Finish to indict you have finished the task. And each action only calls one function once. -Given information: {text} -Query: {query}{scratchpad} """ - - - - -react_planner_agent_prompt = PromptTemplate( - input_variables=["text", "query", "scratchpad"], - template=REACT_PLANNER_INSTRUCTION, -) -planner_agent_prompt = PromptTemplate( - input_variables=["text", "query"], - template=PLANNER_INSTRUCTION, -) -react_reflect_planner_agent_prompt = PromptTemplate( - input_variables=["text", "query", "reflections", "scratchpad"], - template=REACT_REFLECT_PLANNER_INSTRUCTION, -) \ No newline at end of file diff --git a/mxAgent/samples/tools/__init__.py b/mxAgent/samples/tools/__init__.py deleted file mode 100644 index 030d89752..000000000 --- a/mxAgent/samples/tools/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- 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/google_search_api.py b/mxAgent/samples/tools/google_search_api.py deleted file mode 100644 index 760403863..000000000 --- a/mxAgent/samples/tools/google_search_api.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import re - -from bs4 import BeautifulSoup -from loguru import logger -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support import expected_conditions -from selenium.webdriver.support.ui import WebDriverWait - -CHROME_DRIVER_PATH = '/usr/local/share/chromedriver/chromedriver' -GOOGLE_URL = 'https://www.google.com/' - - -def google_search(keywords, max_number=3): - chrome_options = Options() - chrome_options.add_argument('--headless') - chrome_options.add_argument('--no-sandbox') - chrome_options.add_argument('--disable-dev-shm-usage') - chrome_options.add_argument('start-maximized') - chrome_options.add_argument("--disable-extensions") - chrome_options.add_argument('--disable-browser-side-navigation') - chrome_options.add_argument('enable-automation') - chrome_options.add_argument('--disable-infobars') - chrome_options.add_argument('enable-features=NetworkServiceInProcess') - driver = webdriver.Chrome( - executable_path=CHROME_DRIVER_PATH, options=chrome_options) - try: - driver.get(GOOGLE_URL) - WebDriverWait(driver, 5).until( - expected_conditions.presence_of_element_located((By.NAME, 'q'))) - box = driver.find_element(By.NAME, 'q') - box.send_keys(keywords) - box.send_keys(Keys.RETURN) - WebDriverWait(driver, 5).until( - expected_conditions.title_contains(keywords)) - bsobj = BeautifulSoup(driver.page_source, 'html.parser') - elements = bsobj.find_all('div', {'class': re.compile('MjjYud')}) - search_res = [] - for element in elements: - if element.a and element.a.h3: - link = element.a['href'] - title = element.a.h3.text.strip() - else: - continue - high_light = element.find('em', {'class': re.compile('t55VCb')}) - if high_light is None: - continue - parent = high_light.parent - res = { - "snippet": parent.text.strip(), - "title": title.strip(), - "url": link.strip() - } - search_res.append(res) - if len(search_res) >= max_number: # 收集指定数量的网页 - break - return search_res - except TimeoutError as e: - logger.error(f"timeout: {e}") - finally: - driver.quit() - return search_res diff --git a/mxAgent/samples/tools/tool_cost_enquiry.py b/mxAgent/samples/tools/tool_cost_enquiry.py deleted file mode 100644 index a2ae34c5a..000000000 --- a/mxAgent/samples/tools/tool_cost_enquiry.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- 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 deleted file mode 100644 index 9ee7aafd7..000000000 --- a/mxAgent/samples/tools/tool_finish.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- 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 deleted file mode 100644 index 312d6e7cf..000000000 --- a/mxAgent/samples/tools/tool_general_query.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- 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 deleted file mode 100644 index c0bba1103..000000000 --- a/mxAgent/samples/tools/tool_query_accommodations.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- 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 deleted file mode 100644 index 7bb076e80..000000000 --- a/mxAgent/samples/tools/tool_query_attractions.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- 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 deleted file mode 100644 index 4e1c90a03..000000000 --- a/mxAgent/samples/tools/tool_query_city.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- 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 deleted file mode 100644 index f6a10c4e1..000000000 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- 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 deleted file mode 100644 index caed5ed7a..000000000 --- a/mxAgent/samples/tools/tool_query_restaurants.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- 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 deleted file mode 100644 index b48f03585..000000000 --- a/mxAgent/samples/tools/tool_query_transports.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- 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="") diff --git a/mxAgent/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py deleted file mode 100644 index 7fe739bc3..000000000 --- a/mxAgent/samples/tools/tool_query_weather.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import datetime -import json -import json -import os -from zoneinfo import ZoneInfo - -import requests -import urllib3 -from agent_sdk.toolmngt.api import API -from agent_sdk.toolmngt.tool_manager import ToolManager -from loguru import logger - -AMAP_API_KEY = "75bcb2edf5800884a31172dd0d970369" -WEEK_MAP = { - 0: "Monday", - 1: "Tuesday", - 2: "Wednesday", - 3: "Thursday", - 4: "Friday", - 5: "Saturday", - 6: "Sunday" -} -REQUEST_HEADERS = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" -} - - -@ToolManager.register_tool() -class QueryWeather(API): - name = "QueryWeather" - description = "This API is used to query weather forecast from the network according to the user's input question," - weekday = WEEK_MAP.get(datetime.datetime.now(ZoneInfo("Asia/Shanghai")).weekday(), '') - input_parameters = { - - 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, - "province": {'type': 'str', 'description': 'The province corresponding to the city'}, - "date": {'type': 'str', - 'description': ("The date of the user want to query, today is" + - f"{datetime.date.today()}, and today is {weekday}, " + - "please reason the date from user's query, and format with YYYY-MM-DD,") - }, - 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, - } - output_parameters = { - "forecast": {'type': 'str', - 'description': 'the weather forecast information'}, - } - - example = ( - """ - { - "destination_city": "ShenZhen", - "province": "GuangDong", - "date": "2022-10-01" - } - """) - - def __init__(self, ): - os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 - urllib3.disable_warnings() - - def check_api_call_correctness(self, response, groundtruth=None) -> bool: - ex = response.exception - if ex is not None: - return False - else: - return True - - def get_forecast(self, url, param, city=""): - headers = REQUEST_HEADERS - response = requests.get(url, params=param, headers=headers, timeout=5) - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - raise Exception(f"获取网页{url}内容失败") - content = response.content - text = json.loads(content) - return text.get("data") - - def get_city2province(self, url, city): - headers = REQUEST_HEADERS - params = { - "city": city, - "source": "pc" - } - response = requests.get(url, params=params, headers=headers, timeout=5) - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - raise Exception(f"获取网页{url}内容失败") - content = response.content - text = json.loads(content) - return text.get("data") - - def format_weather(self, weekly_weather): - # 精简输入 - key_keeps = [ - 'day_weather', 'day_wind_direction', 'day_wind_power', - 'max_degree', 'min_degree', 'night_weather', 'night_wind_direction', 'night_wind_power' - ] - summary_copy = [] - for key, info in weekly_weather.items(): - time = info.get('time', key) - daily = {} - if isinstance(info, dict): - info_keeps = {k: info[k] for k in key_keeps if k in info} - daily[time] = info_keeps - summary_copy.append(daily) - return summary_copy - - def format_request_param(self, data, weather_type): - for key, value in data.items(): - city2province = value.replace(" ", "").split(",") - data[key] = city2province - # 遇到城市同名,认为是市的概率大于县 - _, max_probablity = min(data.items(), key=lambda item: len(item[1])) - if len(max_probablity) >= 2: - province = max_probablity[0] - city = max_probablity[1] - country = max_probablity[2] if len(max_probablity) >= 3 else "" - params = { - "source": "pc", # 请求来源,可以填 pc 即来自PC端 - "province": province, # 省, - "city": city, # 市, - "country": country, # 县区 - "weather_type": weather_type - } - return params - - def call(self, input_parameter, **kwargs): - des = input_parameter.get('destination_city') - departure_date = input_parameter.get("date") - weather_type = "forecast_24h" - - try: - if des is None: - return self.make_response(input_parameter, results="", success=False, exception="") - try: - data = self.get_city2province("https://wis.qq.com/city/like", des) - except Exception as e: - e = str(e) - return self.make_response(input_parameter, results=e, success=False, exception=e) - if len(data) == 0: - return self.make_response(input_parameter, - results="未能找到所查询城市所在的省份或市", success=False, exception="") - - params = self.format_request_param(data, weather_type) - try: - forecast = self.get_forecast( - "https://wis.qq.com/weather/common", params) - except Exception as e: - e = str(e) - return self.make_response(input_parameter, results=e, success=False, exception=e) - weekly_weather = forecast.get(weather_type) - summary_copy = self.format_weather(weekly_weather) - if departure_date is None: - res = { - 'forecast': summary_copy - } - return self.make_response(input_parameter, results=res, exception="") - - try: - formated_departure = datetime.datetime.strptime( - departure_date, "%Y-%m-%d").date() - except ValueError as e: - logger.warning(e) - formated_departure = datetime.date.today() - gaps = (formated_departure - datetime.date.today()).days - weather_summary = summary_copy[gaps + 1:] - - if len(weather_summary) == 0: - weather_summary = "**抱歉,我最多只能查询最近7天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ - json.dumps(summary_copy, ensure_ascii=False) - res = { - 'forecast': weather_summary - } - 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="") diff --git a/mxAgent/samples/tools/tool_summary.py b/mxAgent/samples/tools/tool_summary.py deleted file mode 100644 index 9d1b706c9..000000000 --- a/mxAgent/samples/tools/tool_summary.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from agent_sdk.toolmngt.api import API -from agent_sdk.toolmngt.tool_manager import ToolManager - - -@ToolManager.register_tool() -class PlanSummary(API): - name = "PlanSummary" - description = "this api uesed to summary all the travel plan." - input_parameters = { - 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, - 'accomadation': {'type': 'str', 'description': "the accomodation information"}, - 'transport': {'type': 'str', 'description': "the transport information"}, - 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, - 'duration': {'type': 'str', 'description': "The days of travel"}, - } - - output_parameters = { - 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, - } - - example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ - accomadation,and transport information" - example = ( - """ - { - "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ - Borough High Street in Southwark and King William Street.", - "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ - culture, shopping, and green spaces", - "transport": "10 hours from Beijing to London cost $1000.", - } - """) - - def __init__(self): - pass - - def format_tool_input_parameters(self, llm_output) -> dict: - return llm_output if llm_output else {} - - def check_api_call_correctness(self, response, groundtruth=None) -> bool: - if response['exception'] is None: - return True - else: - return False - - def call(self, input_parameters, **kwargs): - # 总的只能输入2500个字左右 - attraction = input_parameters.get('attractions') - hotel = input_parameters.get('accomadation') - transport = input_parameters.get('transport') - weather = input_parameters.get("weather") - duration = input_parameters.get("duration") - - res = "" - if duration is not None: - res += f"【用户需要旅行的天数】:{duration}天\n" - if attraction is not None: - res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" - if hotel is not None: - res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" - if transport is not None: - res = res + f"【交通安排】:\n{str(transport)[:500]}\n" - if weather is not None: - res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" - summary = { - "summary": res - } - return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/samples/tools/web_summary_api.py b/mxAgent/samples/tools/web_summary_api.py deleted file mode 100644 index feeb4bb5b..000000000 --- a/mxAgent/samples/tools/web_summary_api.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - -import asyncio -import os -import re -import time -from concurrent.futures import ThreadPoolExecutor, wait, as_completed - -import aiohttp -import requests -import tiktoken -import urllib3 -from bs4 import BeautifulSoup -from loguru import logger -from samples.tools.google_search_api import google_search - - -def check_number_input(num, crow): - if not num.isdigit(): - return False - num = int(num) - if num > crow: - return False - return True - - -async def bai_du(url): - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" - } - async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, - headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: - async with session.get(url) as response: - res = await response.text() - return res - - -class WebSummary: - encoder = tiktoken.get_encoding("gpt2") - - @classmethod - def get_detail_copy(cls, url, summary_prompt): - os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 - urllib3.disable_warnings() - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" - } - try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = requests.get( - url, headers=headers, timeout=(3, 3), stream=True) - mommt = time.time() - logger.info(f"finish request website: {mommt},{url}") - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - return '', f"获取网页{url}内容失败" - - content = response.content - bsobj = BeautifulSoup(content, 'html.parser') - txt = bsobj.get_text() - text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') - text = re.sub(r'\n{2,}', '\n', text) - except Exception as e: - logger.error(e) - return '', e - res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") - return res, "" - - @classmethod - async def get_details(cls, url, summary_prompt): - os.environ['CURL_CA_BUNDLE'] = '' - urllib3.disable_warnings() - try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = await bai_du(url) - mommt = time.time() - logger.debug(f"finish request website: {mommt},{url}") - content = response - bsobj = BeautifulSoup(content, 'html.parser') - txt = bsobj.get_text() - text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') - text = re.sub(r'\n{2,}', '\n', text) - if 'PleaseenableJSanddisableanyadblocker' in text: - text = "" - except Exception as e: - logger.error(e) - return '', e - if len(text) == 0: - return "", "no valid website content" - res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") - return res, "" - - @classmethod - def summary_call(cls, web, max_summary_number, summary_prompt): - title = web.get("title", "") - url = web.get("url") - snippet = web.get("snippet", "") - web_summary = {} - if url is None: - return web_summary - - web_summary['title'] = title - web_summary['url'] = url - try: - content = asyncio.run(cls.get_details(url, summary_prompt)) - except Exception as e: - logger.error(e) - if not isinstance(content, str) or len(content) == 0: - web_summary['snippet'] = snippet - else: - web_summary['content'] = content - - return web_summary - - @classmethod - def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): - logger.add('app.log', level='DEBUG') - cls.llm = llm - try: - mommt = time.time() - logger.debug(f"start google search: {mommt}") - if isinstance(keys, list): - keys = ",".join(keys) - search_result = google_search(keys, search_num) - mommt = time.time() - logger.debug(f"finish google search: {mommt}") - except Exception as e: - logger.error(e) - return [] - - max_summary_number = summary_num - - webs = [] - with ThreadPoolExecutor(max_workers=3) as executor: - futures = [] - for web in search_result: - thread = executor.submit( - cls.summary_call, web, max_summary_number, summary_prompt) - futures.append(thread) - for future in as_completed(futures): - webs.append(future.result()) - wait(futures) - return webs - - @classmethod - def build_summary_prompt(cls, query, prompt): - max_input_token_num = 4096 - if len(query) == 0: - return prompt.format(text=query) - input_token_len = len(WebSummary.encoder.encode(query)) - prompt_len = len(WebSummary.encoder.encode(prompt)) - clip_text_index = int( - len(query) * (max_input_token_num - prompt_len) / input_token_len) - clip_text = query[:clip_text_index] - return prompt.format(input=clip_text) - - @classmethod - def generate_content(cls, query, prompt): - max_tokens = 1000 - try: - pmt = WebSummary.build_summary_prompt(query, prompt) - output = cls.llm(prompt=pmt, max_tokens=max_tokens) - except Exception as e: - logger.error(e) - return e - return output diff --git a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py deleted file mode 100644 index d74e03696..000000000 --- a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py +++ /dev/null @@ -1,62 +0,0 @@ -import streamlit as st -from samples.travel_agent.travelagent import TravelAgent - -if __name__ == "__main__": - st.set_page_config( - page_title="旅游规划agent", - page_icon="./logo.jpg" - ) - st.logo("logo.jpg") - st.markdown('

旅游规划Agent

', unsafe_allow_html=True) - - - placeholder1 = st.empty() - placeholder2 = st.empty() - placeholder3 = st.empty() - - if "messages" not in st.session_state: - st.session_state.messages = [] - - if "aagent" not in st.session_state: - st.session_state.agent = TravelAgent() - - with placeholder1: - container = st.container(height=300, border=False) - with placeholder2: - _, col1, _ = st.columns([10, 2, 10]) - with col1: - st.image("logo.jpg", use_column_width=True) - with placeholder3: - _, col2, _ = st.columns([1, 20, 1]) - helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

-

例如:从北京到西安旅游规划

-

例如:西安有哪些免费的博物馆景点

-

例如:查一下西安的酒店

""" - with col2: - st.markdown(helloinfo, unsafe_allow_html=True) - - for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.empty() - st.markdown(message["content"]) - - if prompt = st.chat_input("send message"): - st.session_state.messages.append({"role":"user", "content":prompt}) - placeholder1.empty() - placeholder2.empty() - placeholder3.empty() - - agent = st.session_state["agent"] - - with st.chat_message("user"): - st.markdown(prompt) - - with st.chat_message("assistant"): - with st.spinner("thinking..."): - response = agent.run(query=prompt, stream=True) - if isinstance(response, str): - st.markdown(response) - else: - response = st.write_stream(response) - - st.session_state.messages.append({"role":"assistant", "content":response}) diff --git a/mxAgent/samples/travel_agent_demo/travelagent.py b/mxAgent/samples/travel_agent_demo/travelagent.py deleted file mode 100644 index 247402940..000000000 --- a/mxAgent/samples/travel_agent_demo/travelagent.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import enum -from abc import ABC -from loguru import logger - -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from agent_sdk.agentchain.recipe_agent import RecipeAgent -from agent_sdk.agentchain.router_agent import RouterAgent -from agent_sdk.agentchain.tool_less_agent import ToollessAgent -from agent_sdk.agentchain.base_agent import AgentRunResult - -from samples.tools.tool_query_accommodations import QueryAccommodations -from samples.tools.tool_query_transports import QueryTransports -from samples.tools.tool_query_attractions import QueryAttractions -from samples.tools.tool_summary import PlanSummary -from samples.tools.tool_general_query import GeneralQuery -from samples.tools.tool_query_weather import QueryWeather - - -PESUEDE_CODE = """步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息 -步骤2:根据用户的问题,从网络中查询相关的出行交通信息。 -步骤3:根据用户的问题,从网络中搜索相关的住宿和酒店信息; -步骤4:根据用户的问题,查询用户需要的城市天气情况; -步骤5:总结以上的出行信息、景点游玩、住宿信息等。""" -TRANSPORT_INST = """步骤1:根据用户的输入问题,从网络中查询相关的出行交通信息""" -ATTRACTION_INST = "步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息" -HOTEL_INST = """步骤1:根据用户的问题,从网络中搜索相关的住宿和酒店信息,""" -WEATHER_INST = "步骤一:根据用户的问题,查询用户需要的城市天气情况" -OTHER_INST = """步骤1:根据用户的输入,从互联网中查询相关的解答""" - - -GENERAL_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页摘要信息进行总结,并以markdown的格式进行返回, -请添加适当的词语,使得语句内容连贯,通顺 -请将content和snippet的信息进行综合处理,进行总结,生成一个段落。 -涉及到url字段时,使用超链接的格式将网页url链接到网页title上。 -参数介绍】: -title:网页标题 -url:网页链接 -snippet:网页摘要信息 -content:网页的内容总结 -下面是JSON格式的输入: -{text} -请生成markdown段落:""" - -WEATHER_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手, -当前的工作场景是:天气出行建议;输入的内容是JSON格式的用户所查询城市未来的天气预报,请将这些信息总结为的自然语言展示天气预报的信息,并对用户的出游给除建议, -根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 -请添加适当的词语,使得语句内容连贯,通顺,并尽可能保留输入的信息和数据,但不要自行杜撰信息。 -提供的信息以JSON的格式进行展示 -【参数介绍】: -date:日期 -day_weather:白天的天气情况 -day_wind_direction:白天风向 -day_wind_power: 白天风力 -night_weather:夜晚的天气情况 -night_wind_direction:夜晚风向 -night_wind_power: 夜晚风力 -max_degree: 最高温 -min_degree:最低温 -下面是JSON格式的输入: -{text} -请生成markdown段落: -""" -PLANNER_FINAL_PROMPT = """你是一个擅长规划和文字处理的智能助手,你需要将提供的信息按照下面的步骤撰写一份旅游攻略,输出markdown格式的段落, -你可以添加适当的语句,使得段落通顺,但不要自己杜撰信息。 -步骤】 -1. 根据【用户需要旅行的天数】,将输入的景点分配到每一天的行程中,每天2-3个景点,并介绍景点的详细情况 -2. 叙述输入中推荐的住宿情况,详细介绍酒店的详细情况,和预定链接 -3. 叙述输入中查询的交通安排,详细介绍每个出行方案的价格、时间、时长等详细情况,和预定链接 -4. 介绍输入中天气预报的情况,根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 -【参数介绍】: -title:网页标题 -url:网页链接,满足用户需求的酒店筛选结果 -content:网页主要内容提取物 -snippet:网页摘要信息 -输入的信息以JSON格式,下面是的输入: -{text} -请生成markdown段落:""" - - - -TRAVEL_PLAN = "TRAVEL_PLAN" -QUERY_ATTRACTION = "QUERY_ATTRACTION" -QUERY_HOTEL = "QUERY_HOTEL" -QUERY_TRANSPORT = "QUERY_TRANSPORT" -QUERY_WEATHER = "QUERY_WEATHER" -OTHERS = "OTHERS" - -classifer = [TRAVEL_PLAN, QUERY_ATTRACTION, QUERY_HOTEL, QUERY_TRANSPORT, QUERY_WEATHER, OTHERS] - -INST_MAP = { - TRAVEL_PLAN :PESUEDE_CODE, - QUERY_ATTRACTION :ATTRACTION_INST, - QUERY_HOTEL:HOTEL_INST, - QUERY_TRANSPORT :TRANSPORT_INST, - QUERY_WEATHER :WEATHER_INST, - OTHERS:OTHER_INST -} - -FINAL_PMT_MAP = { - TRAVEL_PLAN :PLANNER_FINAL_PROMPT, - QUERY_ATTRACTION :GENERAL_FINAL_PROMPT, - QUERY_HOTEL:GENERAL_FINAL_PROMPT, - QUERY_TRANSPORT :GENERAL_FINAL_PROMPT, - QUERY_WEATHER :WEATHER_FINAL_PROMPT, - OTHERS: GENERAL_FINAL_PROMPT - -} - -TOOL_LIST_MAP = { - TRAVEL_PLAN :[QueryAccommodations, QueryAttractions, QueryTransports, PlanSummary, QueryWeather], - QUERY_ATTRACTION :[QueryAttractions], - QUERY_HOTEL:[QueryAccommodations], - QUERY_TRANSPORT :[QueryTransports], - QUERY_WEATHER : [QueryWeather], - OTHERS:[] -} - -intents = { - TRAVEL_PLAN :"询问旅行规划,问题中要求旅游项目日程安排、交通查询、查询当地住宿等方面的能力", - QUERY_ATTRACTION :"查询旅游项目、景区、旅游活动", - QUERY_HOTEL: "仅查询酒店和住宿信息", - QUERY_TRANSPORT : "与现实中出行、乘坐交通、如高铁、动车、飞机、火车等相关的意图", - QUERY_WEATHER :"包括气温、湿度、降水等与天气、天气预报相关的意图", - OTHERS :"与旅游场景不相干的查询" -} -LLM_MODEL = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, - api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run - - -class TalkShowAgent(ToollessAgent, ABC): - def __init__(self, llm, prompt="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:'\ - 目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐,请利用你的知识回答问题,这是用户的问题:{query}", - **kwargs): - super().__init__(llm, prompt, **kwargs) - self.query = "" - - def _build_agent_prompt(self, **kwargs): - return self.prompt.format( - query=self.query - ) - - -class TravelAgent: - @classmethod - def route_query(cls, query): - router_agent = RouterAgent(llm=LLM_MODEL, intents=intents) - classify = router_agent.run(query).answer - if classify not in classifer or classify == OTHERS: - return TalkShowAgent(llm=LLM_MODEL) - return RecipeAgent(name=classify, - description="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐", - llm=LLM_MODEL, - tool_list=TOOL_LIST_MAP[classify], - recipe=INST_MAP[classify], - max_steps=3, - max_token_number=4096, - final_prompt=FINAL_PMT_MAP[classify]) - - def run(self, query, stream): - agent = self.route_query(query) - return agent.run(query, stream=stream) - -if __name__ == "__main__": - # request = "去北京的旅游规划" - # request = "从北京到西安的机票" - # request = "查询北京王府井附近的高档酒店" - # request = "泰国有哪些值得推荐的景点" - # request = "帮我查一下北京最近的天气" - # request = "上海酒店查询" - # request = "北京到上海的高铁" - # request = "上海天气怎么样" - request = "帮我制定一份从北京到上海6天的旅游计划" - - travel_agent = TravelAgent() - res = travel_agent.run(request, stream=True) - if isinstance(res, AgentRunResult): - logger.info("-----------run agent success-------------") - logger.info(res.answer) - else: - for char in res: - logger.info(char) -- Gitee From 8536890c3a701355ba3fcaac64a61caaca816061 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 09:34:45 +0800 Subject: [PATCH 08/30] mat code --- mxAgent/agent_sdk/llms/llm.py | 13 ---- mxAgent/agent_sdk/llms/openai_compatible.py | 66 --------------------- 2 files changed, 79 deletions(-) delete mode 100644 mxAgent/agent_sdk/llms/llm.py delete mode 100644 mxAgent/agent_sdk/llms/openai_compatible.py diff --git a/mxAgent/agent_sdk/llms/llm.py b/mxAgent/agent_sdk/llms/llm.py deleted file mode 100644 index a8d5da6e8..000000000 --- a/mxAgent/agent_sdk/llms/llm.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from .openai_compatible import OpenAICompatibleLLM - -BACKEND_OPENAI_COMPATIBLE = 1 - - -def get_llm_backend(backend, api_base, api_key, llm_name): - if backend == BACKEND_OPENAI_COMPATIBLE: - return OpenAICompatibleLLM(api_base, api_key, llm_name) - else: - raise Exception(f"not support backend: {backend}") \ No newline at end of file diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py deleted file mode 100644 index d66ac952b..000000000 --- a/mxAgent/agent_sdk/llms/openai_compatible.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - -from typing import List, Dict, Optional -from langchain_openai import OpenAI -from loguru import logger -from openai import OpenAI - - -class OpenAICompatibleLLM: - def __init__(self, base_url, api_key, llm_name): - self.base_url = base_url - self.api_key = api_key - self.llm_name = llm_name - self.client = OpenAI(api_key=api_key, base_url=base_url) - - def run(self, prompt, temperature=0.1, stop=None, max_tokens=4096, ismessage=False, stream=False, **kwargs): - messages = prompt if ismessage else [{"role": "user", "content": prompt}] - - if stream: - res = self._chat_stream(messages, temperature, max_tokens, stop=stop, **kwargs) - else: - res = self._chat_no_stream(messages, temperature, max_tokens, stop=stop, **kwargs) - - return res - - def _chat_stream(self, - messages: List[Dict], - temperature: float, - max_tokens: int, - stop: Optional[List[str]] = None, - **kwargs): - response = self.client.chat.completions.create( - model=self.llm_name, - messages=messages, - stop=stop, - temperature=temperature, - max_tokens=max_tokens, - stream=True, - **kwargs) - for chunk in response: - if hasattr(chunk.choices[0].delta, - 'content') and chunk.choices[0].delta.content: - yield chunk.choices[0].delta.content - - def _chat_no_stream(self, - messages: List[Dict], - temperature: float, - max_tokens: int, - stop: Optional[List[str]] = None, - **kwargs): - response = self.client.chat.completions.create( - model=self.llm_name, - messages=messages, - stop=stop, - temperature=temperature, - max_tokens=max_tokens, - stream=False, - **kwargs) - return response.choices[0].message.content - - - - - -- Gitee From 614fba5a96ef949f72a1203bd838c31d64c9da7a Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 16:07:59 +0800 Subject: [PATCH 09/30] clean code --- mxAgent/agent_sdk/agentchain/react_agent.py | 12 ++++++------ mxAgent/agent_sdk/agentchain/recipe_agent.py | 14 ++++---------- .../agent_sdk/agentchain/single_action_agent.py | 13 ++++++------- .../agent_sdk/common/{enum_type.py => constant.py} | 8 +++++++- mxAgent/agent_sdk/utils/constant.py | 4 ---- 5 files changed, 23 insertions(+), 28 deletions(-) rename mxAgent/agent_sdk/common/{enum_type.py => constant.py} (82%) delete mode 100644 mxAgent/agent_sdk/utils/constant.py diff --git a/mxAgent/agent_sdk/agentchain/react_agent.py b/mxAgent/agent_sdk/agentchain/react_agent.py index 630865154..7afbb8f5a 100644 --- a/mxAgent/agent_sdk/agentchain/react_agent.py +++ b/mxAgent/agent_sdk/agentchain/react_agent.py @@ -4,6 +4,7 @@ import copy import json import os +import stat import re from abc import ABC from dataclasses import dataclass @@ -37,7 +38,6 @@ class APIResponseCache(ABC): return self._max_size def add(self, res: APIResponse, action: str, action_input: str): - logger.debug(f"save response cache\n{res}") if not res.success: logger.warning("skip failed response") return @@ -129,10 +129,11 @@ class ReactAgent(BaseAgent, ABC): parent_folder_path = os.path.dirname(file_path) os.makedirs(parent_folder_path, exist_ok=True) - mode = 'a' if os.path.exists(file_path) else 'w' - with open(file_path, mode, encoding="utf-8") as f: - json.dump(save_dict, f, ensure_ascii=False) - f.write("\n") + flag = os.O_WRONLY | os.O_CREAT + mode = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(file_path, flags=flag, mode=mode), "w") as fout: + json.dump(save_dict, fout, ensure_ascii=False) + fout.write("\n") logger.success(f"save {self.__class__.__name__} status done") except Exception as e: logger.error(f"prompt = {self.prompt}") @@ -215,7 +216,6 @@ class ReactReflectAgent(ReactAgent, ABC): return AgentRunResult(query=self.query, answer=self.answer, scratchpad=self.scratchpad, finished=self.finished) def _reflect(self, strategy: ReflexionStrategy) -> None: - logger.debug('Reflecting...') if strategy == ReflexionStrategy.REFLEXION: self.reflections += [self._prompt_agent(self.reflect_llm, type="reflect")] self.reflections_str = self._format_reflections(self.reflections) diff --git a/mxAgent/agent_sdk/agentchain/recipe_agent.py b/mxAgent/agent_sdk/agentchain/recipe_agent.py index 8b3423929..ec20af472 100644 --- a/mxAgent/agent_sdk/agentchain/recipe_agent.py +++ b/mxAgent/agent_sdk/agentchain/recipe_agent.py @@ -21,7 +21,6 @@ class RecipeAgent(BaseAgent, ABC): self.recipe_output = "" self.final_prompt = final_prompt self.encoding = tiktoken.get_encoding("gpt2") - self.status = False self.agent_executor = AgentExecutor(tool_manager=self.tool_manager) def run(self, query, stream=False, *args, **kwargs): @@ -30,8 +29,8 @@ class RecipeAgent(BaseAgent, ABC): for key, value in kwargs.items(): setattr(self, key, value) cur_step = 1 - self.status = False - while not self.status and cur_step < self.max_steps: + self.finished = False + while not self.finished and cur_step < self.max_steps: self.step() cur_step += 1 if len(self.recipe_output) == 0: @@ -69,13 +68,13 @@ class RecipeAgent(BaseAgent, ABC): valid, _ = self.agent_executor.check_valid(translation_result) if not valid: - self.status = True + self.finished = True self.recipe_output = "" self.answer = "" return "" else: result = self._execute_recipe(translation_result, self.llm) - self.status = True + self.finished = True self.recipe_output = result self.answer = result return result @@ -96,11 +95,6 @@ class RecipeAgent(BaseAgent, ABC): pmt = self.final_prompt.format(text=clip_text) return pmt - def _save_recipe_yaml(self, translation_result): - translation_result = translation_result.strip("'") - translation_result = translation_result.strip() - raise NotImplementedError() - def _execute_recipe(self, recipefile, llm): answer = self.agent_executor.async_run(recipefile, llm) return answer diff --git a/mxAgent/agent_sdk/agentchain/single_action_agent.py b/mxAgent/agent_sdk/agentchain/single_action_agent.py index 6e95fec1e..c5b4b55d3 100644 --- a/mxAgent/agent_sdk/agentchain/single_action_agent.py +++ b/mxAgent/agent_sdk/agentchain/single_action_agent.py @@ -3,6 +3,7 @@ import json import os +import stat import re import time from copy import deepcopy @@ -29,7 +30,6 @@ class SingleActionAgent(BaseAgent): if tool_list is None: tool_list = [SingleFinish] else: - tool_list = deepcopy(tool_list) tool_list.append(SingleFinish) super().__init__(llm, prompt, tool_list, **kwargs) @@ -68,10 +68,8 @@ class SingleActionAgent(BaseAgent): def step(self) -> None: llm_response = self._prompt_agent(self.llm) - logger.info(f"LLM Response:\n{llm_response}") self.scratchpad += llm_response action_type, argument = self._parse_action(llm_response) - logger.info(f"Argument:\n{argument}") if action_type == "ParserException": result = argument else: @@ -105,10 +103,11 @@ class SingleActionAgent(BaseAgent): "final answer": self.answer, "status": self.finished } - mode = "a" if os.path.exists(file_path) else "w" - with open(file_path, mode, encoding="utf-8") as f: - json.dump(save_dict, f, ensure_ascii=False) - f.write("\n") + flag = os.O_WRONLY | os.O_CREAT + mode = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(file_path, flags=flag, mode=mode), "w") as fout: + json.dump(save_dict, fout, ensure_ascii=False) + fout.write("\n") except Exception as e: logger.error(f"agent_prompt = {self.prompt}") logger.error(e) diff --git a/mxAgent/agent_sdk/common/enum_type.py b/mxAgent/agent_sdk/common/constant.py similarity index 82% rename from mxAgent/agent_sdk/common/enum_type.py rename to mxAgent/agent_sdk/common/constant.py index 1c924ef72..6ae285e4c 100644 --- a/mxAgent/agent_sdk/common/enum_type.py +++ b/mxAgent/agent_sdk/common/constant.py @@ -21,4 +21,10 @@ class AgentRunStatus(BaseModel): def __str__(self): rate = round((self.success_cnt / self.total_cnt) * 100, 2) - return str(rate) + f"% {self.success_cnt}/{self.total_cnt}" \ No newline at end of file + return str(rate) + f"% {self.success_cnt}/{self.total_cnt}" + + +THOUGHT = "Thought" +ACTION = "Action" +ACTION_INPUT = "Action Input" +OBSERVATION = "Observation" \ No newline at end of file diff --git a/mxAgent/agent_sdk/utils/constant.py b/mxAgent/agent_sdk/utils/constant.py deleted file mode 100644 index eb1202dca..000000000 --- a/mxAgent/agent_sdk/utils/constant.py +++ /dev/null @@ -1,4 +0,0 @@ -THOUGHT = "Thought" -ACTION = "Action" -ACTION_INPUT = "Action Input" -OBSERVATION = "Observation" \ No newline at end of file -- Gitee From 9d638439fb52a88bd27255a024844ae6899cd2ce Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 16:28:38 +0800 Subject: [PATCH 10/30] delete files --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5d035e1ca..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cmake.sourceDirectory": "D:/2024/agent-sdk/mindxsdk-referenceapps/contrib/CarPlateRecognition" -} \ No newline at end of file -- Gitee From 4572c3644b4b75cffcbcb56fa411e669a3090c60 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 17:32:31 +0800 Subject: [PATCH 11/30] add executor --- mxAgent/agent_sdk/executor/common.py | 45 +++ .../executor/recipe_executor/executor.py | 364 ++++++++++++++++++ .../executor/recipe_executor/parser.py | 117 ++++++ .../agent_sdk/executor/recipe_executor/sop.py | 158 ++++++++ .../executor/recipe_executor/state.py | 120 ++++++ .../executor/recipe_executor/task.py | 48 +++ mxAgent/agent_sdk/llms/llm.py | 13 + mxAgent/agent_sdk/llms/openai_compatible.py | 66 ++++ 8 files changed, 931 insertions(+) create mode 100644 mxAgent/agent_sdk/executor/common.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/executor.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/parser.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/sop.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/state.py create mode 100644 mxAgent/agent_sdk/executor/recipe_executor/task.py create mode 100644 mxAgent/agent_sdk/llms/llm.py create mode 100644 mxAgent/agent_sdk/llms/openai_compatible.py diff --git a/mxAgent/agent_sdk/executor/common.py b/mxAgent/agent_sdk/executor/common.py new file mode 100644 index 000000000..2780bd79f --- /dev/null +++ b/mxAgent/agent_sdk/executor/common.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import enum + + +class ErrorType(enum.Enum): + INPUT_NOT_STR = 0 + FILE_TYPE_ERROR = 1 + FILE_READ_ERROR = 2 + NO_DICT_ERROR = 3 + YAML_LOAD_ERROR = 3 + INVALID_TOOL_ERROR = 4 + NO_NAME_ERROR = 5 + INVALID_INPUT_PARAM = 6 + INVALID_OUPUT_PARAM = 7 + NODE_CONFILCT_ERROR = 8 + INVALID_DEPENDENCY = 9 + NODE_NOT_DEPENDENT = 10 + + +ERROR_MAP = { + ErrorType.INPUT_NOT_STR: "Invalid arguements type: the content need a str", + ErrorType.FILE_TYPE_ERROR: "The type of file {filename} is not supported, only json or yaml is fine", + ErrorType.FILE_READ_ERROR: "Failed to read the file {filename}: Error message: {error}", + ErrorType.NO_DICT_ERROR: "The content cannot be converted to dict format.", + ErrorType.YAML_LOAD_ERROR: "Loading content with YAML error: {error}", + ErrorType.INVALID_TOOL_ERROR: "The tool {toolname} doesn't exist.", + ErrorType.NO_NAME_ERROR: "some nodes did not assign a value to a necessary parameter [name] and [toolname].", + ErrorType.INVALID_INPUT_PARAM: "Additional parameters {params} were input during the calling tool {tool}.", + ErrorType.INVALID_OUPUT_PARAM: "These parameters {params} do not exist in the output of the node {node}.", + ErrorType.NODE_CONFILCT_ERROR: "the Node name {name} is duplicated.", + ErrorType.INVALID_DEPENDENCY: "The current node depends on node {node} that does not exist in the plan.", + ErrorType.NODE_NOT_DEPENDENT: "The output of node {node} is being used, but it is not within the dependency" + +} + + +class PlanStrategyType(enum.Enum): + REACT = 1 + RESEARCH = 2 + COT = 3 + EMPTY = 4 + SOP = 5 + DHP = 6 \ No newline at end of file diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/mxAgent/agent_sdk/executor/recipe_executor/executor.py new file mode 100644 index 000000000..93581364d --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/executor.py @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from typing import Dict +import asyncio +import time +import re +import traceback +import json +import os +import threading +from concurrent.futures import ThreadPoolExecutor, as_completed +import yaml +from loguru import logger + +from agent_sdk.executor.recipe_executor.state import ExecutorState, WorkSpace +from agent_sdk.executor.recipe_executor.parser import Node, Parser, ActionGraph +from agent_sdk.executor.recipe_executor.sop import SopHandler +from agent_sdk.executor.common import ERROR_MAP, ErrorType, PlanStrategyType + +ALL_PLAN_STRATEGIES = { + PlanStrategyType.SOP.value: "sop" +} + + +class AgentExecutor(): + tasks_done: Dict[str, asyncio.Event] + + def __init__( + self, + tool_manager, + plan_strategy_type: PlanStrategyType = None, + ): + self.plan_strategy_type = plan_strategy_type or PlanStrategyType.SOP + self.plan_strategy = ALL_PLAN_STRATEGIES.get( + self.plan_strategy_type.value) + self.parser = Parser() + self.tool_manager = tool_manager + self.operation_handler = SopHandler(tool_manager) + self.lock = threading.Lock() + + @staticmethod + def parser_output(output, cur_operation): + content = output + if isinstance(output, str): + try: + content = json.loads(output) + except json.JSONDecodeError: + content = output + else: + content = output + cur_operation.output = content + history = { + "operation_name": cur_operation.name, + "dependecy": cur_operation.dependency, + "input": cur_operation.input, + "output": content, + "activate": cur_operation.activate, # 激活该分支的条件 + "interaction_history": "", # discuss + "variable_space": "" # discuss + } + cur_operation.history = history + return content, history + + @staticmethod + def update_history(result, executor_state): + name = result['action'] + parsed_out = result['output'] + history = result['history'] + executor_state.workspace.variable_space[name] = parsed_out + executor_state.workspace.update(history) + # 执行完毕,从set中移除 + executor_state.remaining_tasks.remove(name) + executor_state.activated_tasks.remove(name) + executor_state.done_tasks.add(name) + executor_state.workspace.update_last_operation(name) + + @staticmethod + def process_action_args(action: Node, executor_state): + # 处理参数依赖的替换 + history_names = [history["operation_name"] + for history in executor_state.workspace.operation_history] + # if any of a node's previous actions has no input + # ie. this node's previous operation name not in history, this node cannot execute, so pass + if not set(action.dependency).issubset(set(history_names)): + raise Exception("dependency action hasn't executed") + node = action + if node.activate: + # 用表达引擎,执行看是否满足准入要求 + node.activate = sub_placeholder( + node.activate, executor_state.workspace) + bool_activate = expression_engine(node.activate) + + if not bool_activate: + raise Exception("action hasn't activate") + + if action.input: + for key, value in action.input.items(): + if value is None: + logger.warning('the value of [%s] is None', key) + else: + value = sub_placeholder(value, executor_state.workspace) + action.input[key] = value + return action + + def get_executable_actions(self, executor_state): + done_actions = executor_state.done_tasks + graph = executor_state.sop_graph.actions + activated = executor_state.activated_tasks + independent_actions = [] + for task_name in executor_state.remaining_tasks: + if all(d in done_actions for d in graph[task_name].dependency): + independent_actions.append(task_name) + executable_actions = [] + pending_actions = [] + for action in independent_actions: + node = graph[action] + if node.activate: + node.activate = sub_placeholder( + node.activate, executor_state.workspace) + if not node.activate: + break + bool_activate = expression_engine(node.activate) + + if not bool_activate: + executor_state.remaining_tasks.remove(action) + executor_state.done_tasks.add(action) + continue + executable_actions.append(action) + + if action in activated: + continue + try: + node = self.process_action_args(node, executor_state,) + except Exception as e: + node = None + if node is not None: + pending_actions.append(node) + else: + continue + return pending_actions + + def run_task(self, action, executor_state, llm): + graph = executor_state.sop_graph.actions + sop_handler = self.operation_handler + mommt = time.time() + logger.debug(f'{action.name} start:{mommt}') + output = sop_handler.invoke(action, llm=llm) + + parsed_out, history = self.parser_output(output, action) + graph[action.name].output = parsed_out + + res = { + "action": action.name, + "output": parsed_out, + "history": history + } + + mommt = time.time() + logger.debug(f'step {action.step}. action: {action.name} has finished') + return res + + + + def async_run(self, content, llm): + executor_state = self.init_state(content) + executor = ThreadPoolExecutor(max_workers=5) + with ThreadPoolExecutor(max_workers=5) as executor: + while executor_state.remaining_tasks: # 活跃的 + executable_tasks = self.get_executable_actions(executor_state) + thread_list = [] + for task in executable_tasks: + th = executor.submit(self.run_task, task, executor_state, llm) + thread_list.append(th) + executor_state.activated_tasks.add(task.name) + for future in as_completed(thread_list): + with self.lock: + self.update_history(future.result(), executor_state) + return executor_state.workspace.get_last_result() + + # 而此处的写法不关注next,关注dependency + def run(self, content): + executor_state = self.init_state(content) + + sop_handler = self.operation_handler + activate_actions = self.get_executable_actions(executor_state) + executor_state.activate_actions = activate_actions + while executor_state.activate_actions: # 活跃的 + + cur_operation = executor_state.activate_actions.pop( + 0) # starts from right + mommt = time.time() + logger.error("start run step %d, action [%s]", cur_operation.step, cur_operation.name) + output = sop_handler.invoke(cur_operation) + parsed_out, history = self.parser_output(output, cur_operation) + executor_state.workspace.update(history) + # 保存结果 + executor_state.workspace.variable_space[cur_operation.name] = parsed_out + executor_state.workspace.update_last_operation( + cur_operation.name) + # 更新状态 + executor_state.remaining_tasks.remove(cur_operation.name) + executor_state.done_tasks.add(cur_operation.name) + executable_actions = self.get_executable_actions(executor_state) + executor_state.activate_actions = (executable_actions) + + return executor_state.workspace.get_last_result() + + def check_valid(self, content): + if not isinstance(content, str): + return False, ERROR_MAP[ErrorType.INPUT_NOT_STR] + absolute_path = os.getcwd() + if os.path.isfile(os.path.join(absolute_path, content)): + raw_dict, err = fetch_file_content(content) + else: + raw_dict, err = fetch_str_content(content) + if len(err) > 0: + return False, err + err = self.operation_handler.check_valid_sop(raw_dict) + return len(err) == 0, err + + def init_state(self, content): + if not isinstance(content, str): + raise TypeError("Invalid arguements type: the content need a str") + absolute_path = os.getcwd() + if os.path.isfile(os.path.join(absolute_path, content)): + raw_dict, err = fetch_file_content(content) + else: + raw_dict, err = fetch_str_content(content) + + filter_opt = [] + for operation in raw_dict: + operation_name = operation['toolname'] + api = self.tool_manager.get_api_by_name(operation_name) + if api is not None: + filter_opt.append(operation) + operations = self.parser.parse(raw_dict=filter_opt) + + execute_state = ExecutorState() + workspace = WorkSpace(operation_history=[], variable_space={}) + graph = ActionGraph(operations) + + execute_state.workspace = workspace + execute_state.sop_graph = graph + execute_state.remaining_tasks = {k for k, _ in graph.actions.items()} + return execute_state + + +def fetch_str_content(content): + start_identify = "```yaml" + end_identify = "```" + start = content.find(start_identify) + end = content.rfind(end_identify) + if start != -1 and end != -1: + content = content[start + len(start_identify): end] + try: + code_seg = content.strip("\n").split("\n") + while code_seg[0] == start_identify: + code_seg.pop(0) + while code_seg[-1] == end_identify: + code_seg.pop() + data = yaml.safe_load("\n".join(code_seg)) + except yaml.YAMLError as e: + logger.error(f"生成yaml代码块错误:{str(e)}") + return [], ERROR_MAP[ErrorType.YAML_LOAD_ERROR] + return data, '' + + +def fetch_file_content(file_path): + # 获取文件扩展名 + file_extension = os.path.splitext(file_path)[1].lower() + if file_extension == '.yaml': + try: + with open(file_path, 'r') as file: + yaml_content = yaml.safe_load(file) + return yaml_content, '' + except Exception as e: + msg = ERROR_MAP[ErrorType.FILE_READ_ERROR].format( + filename=file_path, error=e) + logger.error(msg) + return [], msg + elif file_extension == '.json': + try: + with open(file_path, 'r') as file: + json_content = json.load(file) + return json_content, '' + except Exception as e: + msg = ERROR_MAP[ErrorType.FILE_READ_ERROR].format( + filename=file_path, error=e) + logger.error(msg) + return [], msg + else: + msg = ERROR_MAP[ErrorType.FILE_TYPE_ERROR].format(filename=file_path) + logger.error(msg) + return [], msg + + +def sub_placeholder(expression, workspace, output=None): + # 考虑输入参数可能是数字 + expression = str(expression) + + def replace(match): + keys = match.group(1).split('.') + operation_name = keys[0] + key_name = keys[1] + + if output: + return str(output.get(keys, keys)) + else: + history = workspace.variable_space.get(operation_name) + if isinstance(history, dict): + val = str(history.get(key_name, '')) + return val + return "no value" + + def valid_checking(result, expression): + result = str(result) + if result == expression.replace("{", "").replace("}", "") and "{" in expression and "}" in expression: + return True + return False + + try: + # 匹配exp中占位的子串,并作为参数,传入replace,进行替换 + result = re.sub(r'\$\{([^{^}]*)\}', replace, expression) + if valid_checking(result, expression): + result += "\n [提示]:系统分析您的问题失败,原因可能是: \n①plugin调用不成功" \ + "\n②LLM参数解析失败,\n③SOP字段配置错误。\n您可以尝试再次输入或者调整您的问题" + return result + except TypeError as e: + logger.error(e) + return "" + + +def expression_engine(expression): + ''' + 输入文字表达,返回boolean表达 + ''' + expression = expression.replace("\n", '') + if " 属于 " in expression: + expression = expression.replace(" 属于 ", ".issubset(") + ')' + else: # TODO:更多规则 + pass + res = True + try: + res = bool(eval(expression)) + except Exception: + res = False + + return res + + +async def arun_and_time(func, *args, **kwargs): + """helper function to run and time a function. + Since function can error, we catch the error and return "ERROR" as the result + """ + start = time.time() + try: + result = await func(*args, **kwargs) + except Exception as e: + logger.error(e) + traceback.print_exc() + result = "ERROR" + end = time.time() + return result, end - start diff --git a/mxAgent/agent_sdk/executor/recipe_executor/parser.py b/mxAgent/agent_sdk/executor/recipe_executor/parser.py new file mode 100644 index 000000000..ee4ac0895 --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/parser.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from collections import OrderedDict +import re +from loguru import logger + + +class Node: + def __init__(self, + name, + content, + prompt, + step, + output, + activate, + strategy, + + toolname, + + dependency, + ): + self.step = step + self.name = name + self.input = content + self.prompt = prompt + self.activate = activate + self.strategy = strategy + self.dependency = dependency + self.output = output + self.toolname = toolname + + +class Parser: + def __init__(self, sop_dict=None): + self.sop_dict = sop_dict + + @staticmethod + def construct_graph(nodes): + graph = OrderedDict() + for operation in nodes: + operation = Node(name=operation['name'], + step=operation['step'], + content=operation['input'], + prompt=operation['prompt'], + toolname=operation['toolname'], + dependency=operation['dependency'], + output=operation["output"], + strategy=operation["strategy"], + activate=operation["activate"]) + graph[operation.name] = operation + return graph + + def parse(self, raw_dict): + nodes = [] + for operation in raw_dict: + node = { + "step": None, + "name": None, + "goal": None, + "input": None, # 输入 + "prompt": None, # llm的prompt + "activate": None, # 执行引擎,表达式验证规则 + "toolname": None, + "validation": None, # 校验结果 + "output": None, # 执行完成后赋值 dict类型 + "strategy": None, + "dependency": set(), # 依赖节点 + "err_msg": None, + "err_code": None + } + for key in operation: + node[key] = operation[key] + if key == 'dependency': + # 过滤none + node[key] = [item for item in operation[key] if item is not None] + if not node['prompt']: + node['prompt'] = "当前任务:[{{Operation.name}}],相关参数信息如下:{{Operation.input}}" + nodes.append(node) + + operations = self.construct_graph(nodes) # graph + return operations + + def walk_strings(self, inputs, output=None): + if output is None: + output = set() + regex = re.compile(r'\$v\{(.+?)\.output') + if isinstance(inputs, str): + match = regex.search(inputs) + if match: + output.add(match.group(1)) + elif isinstance(inputs, list): + for element in inputs: + try: + self.walk_strings(element, output) + except TypeError as e: + raise e + elif isinstance(inputs, dict): + for value in inputs.values(): + try: + self.walk_strings(value, output) + except TypeError as e: + raise e + else: + raise TypeError("Invalid inputs type") + return output + + + + +class ActionGraph: + def __init__(self, actions) -> None: + self.actions = actions + if len(actions) == 0: + self.start_node_id = 0 + else: + self.start_node_id = actions[next(iter(actions))] diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py new file mode 100644 index 000000000..436902c7b --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/sop.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import re + +from loguru import logger + +from agent_sdk.executor.common import ErrorType, ERROR_MAP +from .parser import Node + + +class SopHandler: + def __init__(self, tool_manager) -> None: + self.tool_manager = tool_manager + self.error_info = ErrorInfo() + self.opt_map = {} + + async def async_invoke(self, action: Node): + tool_name = action.toolname + args = action.input + res = self.tool_manager.executor_call(tool_name, args) + return res + + def invoke(self, action: Node, llm): + tool_name = action.toolname + args = action.input + res = self.tool_manager.executor_call(tool_name, args, llm=llm) + # 统一为api call + # res = self.tool_manager.api_call(tool_name, args) + return res + + def check_valid_sop(self, sop_dict): + res = self.check_func_param(sop_dict) + err_str = "" + cnt = 1 + for key, value in res.items(): + err_str = err_str + \ + f"{cnt}. 步骤{str(key)}的生成存在以下几个问题:\n" + "\n".join(value) + "\n" + cnt += 1 + if len(err_str) > 0: + logger.error(err_str) + return err_str + + def invalid_input(self, operation_name, key, tool_name): + api = self.tool_manager.get_api_by_name(tool_name) + if api is None: + message = ERROR_MAP[ErrorType.INVALID_TOOL_ERROR].format( + toolname=tool_name) + logger.error(message) + self.error_info.add(operation_name, message) + return False + input_parameters = self.tool_manager.get_api_by_name(tool_name)[ + 'input_parameters'] + if key not in input_parameters: + # 输入多余参数 + message = ERROR_MAP[ErrorType.INVALID_INPUT_PARAM].format( + params=key, tool=tool_name) + logger.error(message) + self.error_info.add(operation_name, message) + return True + + def invalid_output(self, operation_name, key, tool_name): + api = self.tool_manager.get_api_by_name(tool_name) + if api is None: + return False + output_parameters = self.tool_manager.get_api_by_name(tool_name)[ + 'output_parameters'] + if key not in output_parameters: + message = ERROR_MAP[ErrorType.INVALID_OUPUT_PARAM].format( + params=key, tool=tool_name) + self.error_info.add(operation_name, message) + logger.error(message) + return True + + def check_func_param(self, operations): + self.opt_map = {} + for operation in operations: + self.init_operation_dict(operation) + + for operation in operations: + self.check_operation_args(operation) + return self.error_info.store + + def check_operation_args(self, operation): + operation_name = operation['name'] + inputs = operation.get('input', {}) + tool_name = operation.get('toolname', '') + dependency = operation.get('dependency', []) + self.check_invalid_dependency(operation_name, dependency) + if isinstance(inputs, dict): + for key, param in inputs.items(): + if param is None: + continue + if not self.invalid_input(operation_name, key, tool_name): + break + holders = re.findall(r'\$\{([^}]*)\}', str(param)) + self.check_invalid_placeholder( + operation_name, holders, dependency) + else: + pass + + def init_operation_dict(self, operation): + required_keys = {'name', 'toolname'} + # 检查字典中是否包含所有必要的键, 没看懂 + necessary = True + name = operation['name'] + for key in required_keys: + if key not in operation: + necessary = False + message = ERROR_MAP[ErrorType.NO_NAME_ERROR] + self.error_info.add(operation.get('name', 'None'), message) + logger.error(message) + + if name in self.opt_map.keys(): + message = ERROR_MAP[ErrorType.NODE_CONFILCT_ERROR].format( + name=name) + self.error_info.add(name, message) + logger.error(message) + necessary = False + if not necessary: + return + self.opt_map[name] = operation + + def check_invalid_dependency(self, operation_name, dependency): + for dep in dependency: + if dep is None: + continue + if len(dep) == 0: + continue + if dep not in self.opt_map.keys(): + message = ERROR_MAP[ErrorType.INVALID_DEPENDENCY].format( + node=dep) + self.error_info.add(operation_name, message) + logger.error(message) + + def check_invalid_placeholder(self, operation_name, holders, dependency): + for _, holder in enumerate(holders): + keys = holder.split('.') + holder_name = keys[0] + key_name = keys[1] + opt = self.opt_map.get(holder_name) + if holder_name not in dependency: + message = ERROR_MAP[ErrorType.NODE_NOT_DEPENDENT].format( + node=holder_name) + self.error_info.add(operation_name, message) + logger.error(message) + self.invalid_output(operation_name, key_name, opt['toolname']) + + +class ErrorInfo: + def __init__(self) -> None: + self.store = {} + + def add(self, operation, error): + err_list = self.store.get(operation, []) + err_list.append(error) + self.store[operation] = err_list diff --git a/mxAgent/agent_sdk/executor/recipe_executor/state.py b/mxAgent/agent_sdk/executor/recipe_executor/state.py new file mode 100644 index 000000000..eeae064e7 --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/state.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from typing import List +import json + + +class Node: + def __init__( + self, + node_id: int, + in_nodes: List = None, + out_nodes: List = None, + is_terminated: bool = False, + is_finished: bool = False, + role: str = None, + thought: str = None, + function_call: str = None, + ori_query: str = None + ): + self._node_id = node_id + self._in_nodes = in_nodes if in_nodes else [] + self._out_nodes = out_nodes if out_nodes else [] + self._is_terminated = is_terminated + self._is_finished = is_finished + self._role = role + self._thought = thought + self._function_call = function_call + self._ori_query = ori_query + + +class ExecutorState: + + def __init__(self, query: str = None, functions: List = None, + restored_node_list: List = None): + self._query = query + self._functions = functions + self._max_node_id = 0 + self._node_ids = [] + self._start = Node(node_id=self._max_node_id, role="start") + self._dealing_node = self._start + self._is_finished = False + self._report_message = None + self._chains: dict = dict() + self._restored_node_list = restored_node_list if restored_node_list else [] + + self.remaining_tasks = set() + self.activated_tasks = set() + self.done_tasks = set() + self.activate_actions = [] + + # for sop planning type + self.init_query = "" + self.sop_graph = None + self.workspace = None + self.smart_selection_target_name = None + self.wait_user_sop_selection = "" + self.sop_functions = None + self.wait_user_input = False + self.wait_user_input_askuser = False + self.wait_user_feedback = False + self.wait_user_feedback_plugin = False + self.wait_user_plugin_result = False + self.stop_current_chat = False + + self.plugin_result = None + self.llm_response = None + self.validate = None + + +class WorkSpace: + def __init__(self, + operation_history, + variable_space): + self.operation_history = operation_history + self.variable_space = variable_space + self.last_operation = "" + + def update(self, history): + self.operation_history.append(history) + + def retrive_variable(self, operations, argument): + result = [ + self.variable_space[key1][argument] + for key1 in operations + if key1 in self.variable_space and argument in self.variable_space[key1] + ] + result = result[0] if result else [] + return result + + def map_keys(self, input_dict, mapping): + mapped_dict = {} + for key, value in input_dict.items(): + mapped_key = mapping.get(key, key) + if isinstance(value, dict): + mapped_dict[mapped_key] = self.map_keys(value, mapping) + else: + mapped_dict[mapped_key] = value + return mapped_dict + + def update_last_operation(self, operation): + self.last_operation = operation + + def get_last_result(self): + name = self.last_operation + result = self.variable_space.get(name, "") + ans = "" + if isinstance(result, dict): + for _, val in result.items(): + if not isinstance(val, str): + val = json.dumps(val, ensure_ascii=False) + if val: + ans += val + '\n' + return ans + + if result is None or len(result) == 0: + for key, value in self.variable_space.items(): + if value is not None and len(value) > 0: + ans += f"{key}: {str(value)}\n" + return ans diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/mxAgent/agent_sdk/executor/recipe_executor/task.py new file mode 100644 index 000000000..fae038c7f --- /dev/null +++ b/mxAgent/agent_sdk/executor/recipe_executor/task.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from dataclasses import dataclass +from typing import Any, Callable, Collection, Dict, List, Optional + +from utils.log import LOGGER + + +@dataclass +class Task: + idx: int + name: str + tool: Callable + args: Collection[Any] + dependencies: Collection[int] # 依赖的任务 idx + condition: str # 执行分支条件 + + observation: Optional[str] = None + is_join: bool = False + + async def __call__(self) -> Any: + LOGGER.info("running task") + x = await self.tool(*self.args) + LOGGER.info("done task") + return x + + def get_though_action_observation( + self, include_action=True, include_thought=True, include_action_idx=False + ) -> str: + thought_action_observation = "" + if self.thought and include_thought: + thought_action_observation = f"Thought: {self.thought}\n" + if include_action: + idx = str(self.idx) + ". " if include_action_idx else "" + if self.stringify_rule: + # If the user has specified a custom stringify rule for the + # function argument, use it + thought_action_observation += f"{idx}{self.stringify_rule(self.args)}\n" + else: + # Otherwise, we have a default stringify rule + thought_action_observation += ( + f"{idx}{self.name}" + # f"{_default_stringify_rule_for_arguments(self.args)}\n" + ) + if self.observation is not None: + thought_action_observation += f"Observation: {self.observation}\n" + return thought_action_observation diff --git a/mxAgent/agent_sdk/llms/llm.py b/mxAgent/agent_sdk/llms/llm.py new file mode 100644 index 000000000..a8d5da6e8 --- /dev/null +++ b/mxAgent/agent_sdk/llms/llm.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from .openai_compatible import OpenAICompatibleLLM + +BACKEND_OPENAI_COMPATIBLE = 1 + + +def get_llm_backend(backend, api_base, api_key, llm_name): + if backend == BACKEND_OPENAI_COMPATIBLE: + return OpenAICompatibleLLM(api_base, api_key, llm_name) + else: + raise Exception(f"not support backend: {backend}") \ No newline at end of file diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py new file mode 100644 index 000000000..d66ac952b --- /dev/null +++ b/mxAgent/agent_sdk/llms/openai_compatible.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +from typing import List, Dict, Optional +from langchain_openai import OpenAI +from loguru import logger +from openai import OpenAI + + +class OpenAICompatibleLLM: + def __init__(self, base_url, api_key, llm_name): + self.base_url = base_url + self.api_key = api_key + self.llm_name = llm_name + self.client = OpenAI(api_key=api_key, base_url=base_url) + + def run(self, prompt, temperature=0.1, stop=None, max_tokens=4096, ismessage=False, stream=False, **kwargs): + messages = prompt if ismessage else [{"role": "user", "content": prompt}] + + if stream: + res = self._chat_stream(messages, temperature, max_tokens, stop=stop, **kwargs) + else: + res = self._chat_no_stream(messages, temperature, max_tokens, stop=stop, **kwargs) + + return res + + def _chat_stream(self, + messages: List[Dict], + temperature: float, + max_tokens: int, + stop: Optional[List[str]] = None, + **kwargs): + response = self.client.chat.completions.create( + model=self.llm_name, + messages=messages, + stop=stop, + temperature=temperature, + max_tokens=max_tokens, + stream=True, + **kwargs) + for chunk in response: + if hasattr(chunk.choices[0].delta, + 'content') and chunk.choices[0].delta.content: + yield chunk.choices[0].delta.content + + def _chat_no_stream(self, + messages: List[Dict], + temperature: float, + max_tokens: int, + stop: Optional[List[str]] = None, + **kwargs): + response = self.client.chat.completions.create( + model=self.llm_name, + messages=messages, + stop=stop, + temperature=temperature, + max_tokens=max_tokens, + stream=False, + **kwargs) + return response.choices[0].message.content + + + + + -- Gitee From 1db6d44a20da9308421b4db13c3d57590ecdb225 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 17:35:44 +0800 Subject: [PATCH 12/30] Update sop.py --- mxAgent/agent_sdk/executor/recipe_executor/sop.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/mxAgent/agent_sdk/executor/recipe_executor/sop.py index 436902c7b..cdb972e1c 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/sop.py +++ b/mxAgent/agent_sdk/executor/recipe_executor/sop.py @@ -97,8 +97,6 @@ class SopHandler: holders = re.findall(r'\$\{([^}]*)\}', str(param)) self.check_invalid_placeholder( operation_name, holders, dependency) - else: - pass def init_operation_dict(self, operation): required_keys = {'name', 'toolname'} -- Gitee From db3a4a47011c4d645f14cc8b68003cd0cd118bac Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 17:41:39 +0800 Subject: [PATCH 13/30] fix llm run --- mxAgent/agent_sdk/llms/openai_compatible.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/mxAgent/agent_sdk/llms/openai_compatible.py index d66ac952b..fd51e13e6 100644 --- a/mxAgent/agent_sdk/llms/openai_compatible.py +++ b/mxAgent/agent_sdk/llms/openai_compatible.py @@ -15,9 +15,13 @@ class OpenAICompatibleLLM: self.llm_name = llm_name self.client = OpenAI(api_key=api_key, base_url=base_url) - def run(self, prompt, temperature=0.1, stop=None, max_tokens=4096, ismessage=False, stream=False, **kwargs): - messages = prompt if ismessage else [{"role": "user", "content": prompt}] + def run(self, prompt, ismessage=False, **kwargs): + temperature = kwargs.get("temperature", 0.1) + stop = kwargs.get("stop", None) + max_tokens = kwargs.get("max_tokens", 4096) + stream = kwargs.get("stream", False) + messages = prompt if ismessage else [{"role": "user", "content": prompt}] if stream: res = self._chat_stream(messages, temperature, max_tokens, stop=stop, **kwargs) else: -- Gitee From c4c6a89f3371dd8e63704ea50560256aa82ef4ab Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 19:36:25 +0800 Subject: [PATCH 14/30] add tools --- mxAgent/agent_sdk/prompts/pre_prompt.py | 192 ++++++++++++++++++ mxAgent/agent_sdk/samples/tools/__init__.py | 22 ++ .../agent_sdk/samples/tools/duck_search.py | 114 +++++++++++ .../samples/tools/tool_cost_enquiry.py | 66 ++++++ .../agent_sdk/samples/tools/tool_finish.py | 49 +++++ .../samples/tools/tool_general_query.py | 79 +++++++ .../tools/tool_query_accommodations.py | 103 ++++++++++ .../samples/tools/tool_query_attractions.py | 114 +++++++++++ .../samples/tools/tool_query_city.py | 92 +++++++++ .../tools/tool_query_distance_matrix.py | 65 ++++++ .../samples/tools/tool_query_restaurants.py | 40 ++++ .../samples/tools/tool_query_transports.py | 96 +++++++++ .../samples/tools/tool_query_weather.py | 185 +++++++++++++++++ .../agent_sdk/samples/tools/tool_summary.py | 71 +++++++ .../samples/tools/web_summary_api.py | 176 ++++++++++++++++ mxAgent/agent_sdk/toolmngt/api.py | 97 +++++++++ mxAgent/agent_sdk/toolmngt/tool_manager.py | 113 +++++++++++ 17 files changed, 1674 insertions(+) create mode 100644 mxAgent/agent_sdk/prompts/pre_prompt.py create mode 100644 mxAgent/agent_sdk/samples/tools/__init__.py create mode 100644 mxAgent/agent_sdk/samples/tools/duck_search.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_cost_enquiry.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_finish.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_general_query.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_accommodations.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_attractions.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_city.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_distance_matrix.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_restaurants.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_transports.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_query_weather.py create mode 100644 mxAgent/agent_sdk/samples/tools/tool_summary.py create mode 100644 mxAgent/agent_sdk/samples/tools/web_summary_api.py create mode 100644 mxAgent/agent_sdk/toolmngt/api.py create mode 100644 mxAgent/agent_sdk/toolmngt/tool_manager.py diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py new file mode 100644 index 000000000..51451e0f8 --- /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/samples/tools/__init__.py b/mxAgent/agent_sdk/samples/tools/__init__.py new file mode 100644 index 000000000..030d89752 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/duck_search.py b/mxAgent/agent_sdk/samples/tools/duck_search.py new file mode 100644 index 000000000..4a5782d66 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_cost_enquiry.py b/mxAgent/agent_sdk/samples/tools/tool_cost_enquiry.py new file mode 100644 index 000000000..a2ae34c5a --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_finish.py b/mxAgent/agent_sdk/samples/tools/tool_finish.py new file mode 100644 index 000000000..9ee7aafd7 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_general_query.py b/mxAgent/agent_sdk/samples/tools/tool_general_query.py new file mode 100644 index 000000000..312d6e7cf --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_query_accommodations.py b/mxAgent/agent_sdk/samples/tools/tool_query_accommodations.py new file mode 100644 index 000000000..c0bba1103 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_query_attractions.py b/mxAgent/agent_sdk/samples/tools/tool_query_attractions.py new file mode 100644 index 000000000..7bb076e80 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_query_city.py b/mxAgent/agent_sdk/samples/tools/tool_query_city.py new file mode 100644 index 000000000..4e1c90a03 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_query_distance_matrix.py b/mxAgent/agent_sdk/samples/tools/tool_query_distance_matrix.py new file mode 100644 index 000000000..f6a10c4e1 --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_query_restaurants.py b/mxAgent/agent_sdk/samples/tools/tool_query_restaurants.py new file mode 100644 index 000000000..caed5ed7a --- /dev/null +++ b/mxAgent/agent_sdk/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/agent_sdk/samples/tools/tool_query_transports.py b/mxAgent/agent_sdk/samples/tools/tool_query_transports.py new file mode 100644 index 000000000..b48f03585 --- /dev/null +++ b/mxAgent/agent_sdk/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="") diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_weather.py b/mxAgent/agent_sdk/samples/tools/tool_query_weather.py new file mode 100644 index 000000000..7fe739bc3 --- /dev/null +++ b/mxAgent/agent_sdk/samples/tools/tool_query_weather.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import datetime +import json +import json +import os +from zoneinfo import ZoneInfo + +import requests +import urllib3 +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger + +AMAP_API_KEY = "75bcb2edf5800884a31172dd0d970369" +WEEK_MAP = { + 0: "Monday", + 1: "Tuesday", + 2: "Wednesday", + 3: "Thursday", + 4: "Friday", + 5: "Saturday", + 6: "Sunday" +} +REQUEST_HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" +} + + +@ToolManager.register_tool() +class QueryWeather(API): + name = "QueryWeather" + description = "This API is used to query weather forecast from the network according to the user's input question," + weekday = WEEK_MAP.get(datetime.datetime.now(ZoneInfo("Asia/Shanghai")).weekday(), '') + input_parameters = { + + 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, + "province": {'type': 'str', 'description': 'The province corresponding to the city'}, + "date": {'type': 'str', + 'description': ("The date of the user want to query, today is" + + f"{datetime.date.today()}, and today is {weekday}, " + + "please reason the date from user's query, and format with YYYY-MM-DD,") + }, + 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, + } + output_parameters = { + "forecast": {'type': 'str', + 'description': 'the weather forecast information'}, + } + + example = ( + """ + { + "destination_city": "ShenZhen", + "province": "GuangDong", + "date": "2022-10-01" + } + """) + + def __init__(self, ): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + ex = response.exception + if ex is not None: + return False + else: + return True + + def get_forecast(self, url, param, city=""): + headers = REQUEST_HEADERS + response = requests.get(url, params=param, headers=headers, timeout=5) + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + raise Exception(f"获取网页{url}内容失败") + content = response.content + text = json.loads(content) + return text.get("data") + + def get_city2province(self, url, city): + headers = REQUEST_HEADERS + params = { + "city": city, + "source": "pc" + } + response = requests.get(url, params=params, headers=headers, timeout=5) + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + raise Exception(f"获取网页{url}内容失败") + content = response.content + text = json.loads(content) + return text.get("data") + + def format_weather(self, weekly_weather): + # 精简输入 + key_keeps = [ + 'day_weather', 'day_wind_direction', 'day_wind_power', + 'max_degree', 'min_degree', 'night_weather', 'night_wind_direction', 'night_wind_power' + ] + summary_copy = [] + for key, info in weekly_weather.items(): + time = info.get('time', key) + daily = {} + if isinstance(info, dict): + info_keeps = {k: info[k] for k in key_keeps if k in info} + daily[time] = info_keeps + summary_copy.append(daily) + return summary_copy + + def format_request_param(self, data, weather_type): + for key, value in data.items(): + city2province = value.replace(" ", "").split(",") + data[key] = city2province + # 遇到城市同名,认为是市的概率大于县 + _, max_probablity = min(data.items(), key=lambda item: len(item[1])) + if len(max_probablity) >= 2: + province = max_probablity[0] + city = max_probablity[1] + country = max_probablity[2] if len(max_probablity) >= 3 else "" + params = { + "source": "pc", # 请求来源,可以填 pc 即来自PC端 + "province": province, # 省, + "city": city, # 市, + "country": country, # 县区 + "weather_type": weather_type + } + return params + + def call(self, input_parameter, **kwargs): + des = input_parameter.get('destination_city') + departure_date = input_parameter.get("date") + weather_type = "forecast_24h" + + try: + if des is None: + return self.make_response(input_parameter, results="", success=False, exception="") + try: + data = self.get_city2province("https://wis.qq.com/city/like", des) + except Exception as e: + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + if len(data) == 0: + return self.make_response(input_parameter, + results="未能找到所查询城市所在的省份或市", success=False, exception="") + + params = self.format_request_param(data, weather_type) + try: + forecast = self.get_forecast( + "https://wis.qq.com/weather/common", params) + except Exception as e: + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + weekly_weather = forecast.get(weather_type) + summary_copy = self.format_weather(weekly_weather) + if departure_date is None: + res = { + 'forecast': summary_copy + } + return self.make_response(input_parameter, results=res, exception="") + + try: + formated_departure = datetime.datetime.strptime( + departure_date, "%Y-%m-%d").date() + except ValueError as e: + logger.warning(e) + formated_departure = datetime.date.today() + gaps = (formated_departure - datetime.date.today()).days + weather_summary = summary_copy[gaps + 1:] + + if len(weather_summary) == 0: + weather_summary = "**抱歉,我最多只能查询最近7天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ + json.dumps(summary_copy, ensure_ascii=False) + res = { + 'forecast': weather_summary + } + 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="") diff --git a/mxAgent/agent_sdk/samples/tools/tool_summary.py b/mxAgent/agent_sdk/samples/tools/tool_summary.py new file mode 100644 index 000000000..9d1b706c9 --- /dev/null +++ b/mxAgent/agent_sdk/samples/tools/tool_summary.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class PlanSummary(API): + name = "PlanSummary" + description = "this api uesed to summary all the travel plan." + input_parameters = { + 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, + 'accomadation': {'type': 'str', 'description': "the accomodation information"}, + 'transport': {'type': 'str', 'description': "the transport information"}, + 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, + 'duration': {'type': 'str', 'description': "The days of travel"}, + } + + output_parameters = { + 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, + } + + example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ + accomadation,and transport information" + example = ( + """ + { + "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ + Borough High Street in Southwark and King William Street.", + "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ + culture, shopping, and green spaces", + "transport": "10 hours from Beijing to London cost $1000.", + } + """) + + def __init__(self): + pass + + def format_tool_input_parameters(self, llm_output) -> dict: + return llm_output if llm_output else {} + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + if response['exception'] is None: + return True + else: + return False + + def call(self, input_parameters, **kwargs): + # 总的只能输入2500个字左右 + attraction = input_parameters.get('attractions') + hotel = input_parameters.get('accomadation') + transport = input_parameters.get('transport') + weather = input_parameters.get("weather") + duration = input_parameters.get("duration") + + res = "" + if duration is not None: + res += f"【用户需要旅行的天数】:{duration}天\n" + if attraction is not None: + res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" + if hotel is not None: + res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" + if transport is not None: + res = res + f"【交通安排】:\n{str(transport)[:500]}\n" + if weather is not None: + res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" + summary = { + "summary": res + } + return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/agent_sdk/samples/tools/web_summary_api.py b/mxAgent/agent_sdk/samples/tools/web_summary_api.py new file mode 100644 index 000000000..feeb4bb5b --- /dev/null +++ b/mxAgent/agent_sdk/samples/tools/web_summary_api.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import asyncio +import os +import re +import time +from concurrent.futures import ThreadPoolExecutor, wait, as_completed + +import aiohttp +import requests +import tiktoken +import urllib3 +from bs4 import BeautifulSoup +from loguru import logger +from samples.tools.google_search_api import google_search + + +def check_number_input(num, crow): + if not num.isdigit(): + return False + num = int(num) + if num > crow: + return False + return True + + +async def bai_du(url): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, + headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: + async with session.get(url) as response: + res = await response.text() + return res + + +class WebSummary: + encoder = tiktoken.get_encoding("gpt2") + + @classmethod + def get_detail_copy(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = requests.get( + url, headers=headers, timeout=(3, 3), stream=True) + mommt = time.time() + logger.info(f"finish request website: {mommt},{url}") + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + return '', f"获取网页{url}内容失败" + + content = response.content + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + except Exception as e: + logger.error(e) + return '', e + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, "" + + @classmethod + async def get_details(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' + urllib3.disable_warnings() + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = await bai_du(url) + mommt = time.time() + logger.debug(f"finish request website: {mommt},{url}") + content = response + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + if 'PleaseenableJSanddisableanyadblocker' in text: + text = "" + except Exception as e: + logger.error(e) + return '', e + if len(text) == 0: + return "", "no valid website content" + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, "" + + @classmethod + def summary_call(cls, web, max_summary_number, summary_prompt): + title = web.get("title", "") + url = web.get("url") + snippet = web.get("snippet", "") + web_summary = {} + if url is None: + return web_summary + + web_summary['title'] = title + web_summary['url'] = url + try: + content = asyncio.run(cls.get_details(url, summary_prompt)) + except Exception as e: + logger.error(e) + if not isinstance(content, str) or len(content) == 0: + web_summary['snippet'] = snippet + else: + web_summary['content'] = content + + return web_summary + + @classmethod + def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): + logger.add('app.log', level='DEBUG') + cls.llm = llm + try: + mommt = time.time() + logger.debug(f"start google search: {mommt}") + if isinstance(keys, list): + keys = ",".join(keys) + search_result = google_search(keys, search_num) + mommt = time.time() + logger.debug(f"finish google search: {mommt}") + except Exception as e: + logger.error(e) + return [] + + max_summary_number = summary_num + + webs = [] + with ThreadPoolExecutor(max_workers=3) as executor: + futures = [] + for web in search_result: + thread = executor.submit( + cls.summary_call, web, max_summary_number, summary_prompt) + futures.append(thread) + for future in as_completed(futures): + webs.append(future.result()) + wait(futures) + return webs + + @classmethod + def build_summary_prompt(cls, query, prompt): + max_input_token_num = 4096 + if len(query) == 0: + return prompt.format(text=query) + input_token_len = len(WebSummary.encoder.encode(query)) + prompt_len = len(WebSummary.encoder.encode(prompt)) + clip_text_index = int( + len(query) * (max_input_token_num - prompt_len) / input_token_len) + clip_text = query[:clip_text_index] + return prompt.format(input=clip_text) + + @classmethod + def generate_content(cls, query, prompt): + max_tokens = 1000 + try: + pmt = WebSummary.build_summary_prompt(query, prompt) + output = cls.llm(prompt=pmt, max_tokens=max_tokens) + except Exception as e: + logger.error(e) + return e + return output diff --git a/mxAgent/agent_sdk/toolmngt/api.py b/mxAgent/agent_sdk/toolmngt/api.py new file mode 100644 index 000000000..1230ecd61 --- /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 000000000..e2d1f56b7 --- /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()] -- Gitee From 2ad19c4916dec304d3cdd07dd7857b369810f0d3 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 19:39:53 +0800 Subject: [PATCH 15/30] add tools --- mxAgent/agent_sdk/prompts/pre_prompt.py | 192 ------------------ .../agent_sdk/samples/tools/tool_summary.py | 71 ------- .../samples/tools/web_summary_api.py | 176 ---------------- mxAgent/agent_sdk/toolmngt/api.py | 97 --------- mxAgent/agent_sdk/toolmngt/tool_manager.py | 113 ----------- 5 files changed, 649 deletions(-) delete mode 100644 mxAgent/agent_sdk/prompts/pre_prompt.py delete mode 100644 mxAgent/agent_sdk/samples/tools/tool_summary.py delete mode 100644 mxAgent/agent_sdk/samples/tools/web_summary_api.py delete mode 100644 mxAgent/agent_sdk/toolmngt/api.py delete mode 100644 mxAgent/agent_sdk/toolmngt/tool_manager.py diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py deleted file mode 100644 index 51451e0f8..000000000 --- a/mxAgent/agent_sdk/prompts/pre_prompt.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- 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/samples/tools/tool_summary.py b/mxAgent/agent_sdk/samples/tools/tool_summary.py deleted file mode 100644 index 9d1b706c9..000000000 --- a/mxAgent/agent_sdk/samples/tools/tool_summary.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from agent_sdk.toolmngt.api import API -from agent_sdk.toolmngt.tool_manager import ToolManager - - -@ToolManager.register_tool() -class PlanSummary(API): - name = "PlanSummary" - description = "this api uesed to summary all the travel plan." - input_parameters = { - 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, - 'accomadation': {'type': 'str', 'description': "the accomodation information"}, - 'transport': {'type': 'str', 'description': "the transport information"}, - 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, - 'duration': {'type': 'str', 'description': "The days of travel"}, - } - - output_parameters = { - 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, - } - - example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ - accomadation,and transport information" - example = ( - """ - { - "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ - Borough High Street in Southwark and King William Street.", - "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ - culture, shopping, and green spaces", - "transport": "10 hours from Beijing to London cost $1000.", - } - """) - - def __init__(self): - pass - - def format_tool_input_parameters(self, llm_output) -> dict: - return llm_output if llm_output else {} - - def check_api_call_correctness(self, response, groundtruth=None) -> bool: - if response['exception'] is None: - return True - else: - return False - - def call(self, input_parameters, **kwargs): - # 总的只能输入2500个字左右 - attraction = input_parameters.get('attractions') - hotel = input_parameters.get('accomadation') - transport = input_parameters.get('transport') - weather = input_parameters.get("weather") - duration = input_parameters.get("duration") - - res = "" - if duration is not None: - res += f"【用户需要旅行的天数】:{duration}天\n" - if attraction is not None: - res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" - if hotel is not None: - res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" - if transport is not None: - res = res + f"【交通安排】:\n{str(transport)[:500]}\n" - if weather is not None: - res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" - summary = { - "summary": res - } - return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/agent_sdk/samples/tools/web_summary_api.py b/mxAgent/agent_sdk/samples/tools/web_summary_api.py deleted file mode 100644 index feeb4bb5b..000000000 --- a/mxAgent/agent_sdk/samples/tools/web_summary_api.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - -import asyncio -import os -import re -import time -from concurrent.futures import ThreadPoolExecutor, wait, as_completed - -import aiohttp -import requests -import tiktoken -import urllib3 -from bs4 import BeautifulSoup -from loguru import logger -from samples.tools.google_search_api import google_search - - -def check_number_input(num, crow): - if not num.isdigit(): - return False - num = int(num) - if num > crow: - return False - return True - - -async def bai_du(url): - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" - } - async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, - headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: - async with session.get(url) as response: - res = await response.text() - return res - - -class WebSummary: - encoder = tiktoken.get_encoding("gpt2") - - @classmethod - def get_detail_copy(cls, url, summary_prompt): - os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 - urllib3.disable_warnings() - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" - } - try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = requests.get( - url, headers=headers, timeout=(3, 3), stream=True) - mommt = time.time() - logger.info(f"finish request website: {mommt},{url}") - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - return '', f"获取网页{url}内容失败" - - content = response.content - bsobj = BeautifulSoup(content, 'html.parser') - txt = bsobj.get_text() - text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') - text = re.sub(r'\n{2,}', '\n', text) - except Exception as e: - logger.error(e) - return '', e - res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") - return res, "" - - @classmethod - async def get_details(cls, url, summary_prompt): - os.environ['CURL_CA_BUNDLE'] = '' - urllib3.disable_warnings() - try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = await bai_du(url) - mommt = time.time() - logger.debug(f"finish request website: {mommt},{url}") - content = response - bsobj = BeautifulSoup(content, 'html.parser') - txt = bsobj.get_text() - text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') - text = re.sub(r'\n{2,}', '\n', text) - if 'PleaseenableJSanddisableanyadblocker' in text: - text = "" - except Exception as e: - logger.error(e) - return '', e - if len(text) == 0: - return "", "no valid website content" - res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") - return res, "" - - @classmethod - def summary_call(cls, web, max_summary_number, summary_prompt): - title = web.get("title", "") - url = web.get("url") - snippet = web.get("snippet", "") - web_summary = {} - if url is None: - return web_summary - - web_summary['title'] = title - web_summary['url'] = url - try: - content = asyncio.run(cls.get_details(url, summary_prompt)) - except Exception as e: - logger.error(e) - if not isinstance(content, str) or len(content) == 0: - web_summary['snippet'] = snippet - else: - web_summary['content'] = content - - return web_summary - - @classmethod - def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): - logger.add('app.log', level='DEBUG') - cls.llm = llm - try: - mommt = time.time() - logger.debug(f"start google search: {mommt}") - if isinstance(keys, list): - keys = ",".join(keys) - search_result = google_search(keys, search_num) - mommt = time.time() - logger.debug(f"finish google search: {mommt}") - except Exception as e: - logger.error(e) - return [] - - max_summary_number = summary_num - - webs = [] - with ThreadPoolExecutor(max_workers=3) as executor: - futures = [] - for web in search_result: - thread = executor.submit( - cls.summary_call, web, max_summary_number, summary_prompt) - futures.append(thread) - for future in as_completed(futures): - webs.append(future.result()) - wait(futures) - return webs - - @classmethod - def build_summary_prompt(cls, query, prompt): - max_input_token_num = 4096 - if len(query) == 0: - return prompt.format(text=query) - input_token_len = len(WebSummary.encoder.encode(query)) - prompt_len = len(WebSummary.encoder.encode(prompt)) - clip_text_index = int( - len(query) * (max_input_token_num - prompt_len) / input_token_len) - clip_text = query[:clip_text_index] - return prompt.format(input=clip_text) - - @classmethod - def generate_content(cls, query, prompt): - max_tokens = 1000 - try: - pmt = WebSummary.build_summary_prompt(query, prompt) - output = cls.llm(prompt=pmt, max_tokens=max_tokens) - except Exception as e: - logger.error(e) - return e - return output diff --git a/mxAgent/agent_sdk/toolmngt/api.py b/mxAgent/agent_sdk/toolmngt/api.py deleted file mode 100644 index 1230ecd61..000000000 --- a/mxAgent/agent_sdk/toolmngt/api.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- 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 deleted file mode 100644 index e2d1f56b7..000000000 --- a/mxAgent/agent_sdk/toolmngt/tool_manager.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- 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()] -- Gitee From 097eb342d097699bfaae0f5d9c2ccf696415ad0a Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 19:53:04 +0800 Subject: [PATCH 16/30] move tools --- mxAgent/{agent_sdk => }/requirements.txt | 0 mxAgent/{agent_sdk => }/samples/tools/__init__.py | 0 mxAgent/{agent_sdk => }/samples/tools/duck_search.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_cost_enquiry.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_finish.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_general_query.py | 0 .../{agent_sdk => }/samples/tools/tool_query_accommodations.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_query_attractions.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_query_city.py | 0 .../{agent_sdk => }/samples/tools/tool_query_distance_matrix.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_query_restaurants.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_query_transports.py | 0 mxAgent/{agent_sdk => }/samples/tools/tool_query_weather.py | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename mxAgent/{agent_sdk => }/requirements.txt (100%) rename mxAgent/{agent_sdk => }/samples/tools/__init__.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/duck_search.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_cost_enquiry.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_finish.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_general_query.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_accommodations.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_attractions.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_city.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_distance_matrix.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_restaurants.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_transports.py (100%) rename mxAgent/{agent_sdk => }/samples/tools/tool_query_weather.py (100%) 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/agent_sdk/samples/tools/__init__.py b/mxAgent/samples/tools/__init__.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/__init__.py rename to mxAgent/samples/tools/__init__.py diff --git a/mxAgent/agent_sdk/samples/tools/duck_search.py b/mxAgent/samples/tools/duck_search.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/duck_search.py rename to mxAgent/samples/tools/duck_search.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_cost_enquiry.py b/mxAgent/samples/tools/tool_cost_enquiry.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_cost_enquiry.py rename to mxAgent/samples/tools/tool_cost_enquiry.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_finish.py b/mxAgent/samples/tools/tool_finish.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_finish.py rename to mxAgent/samples/tools/tool_finish.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_general_query.py b/mxAgent/samples/tools/tool_general_query.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_general_query.py rename to mxAgent/samples/tools/tool_general_query.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_accommodations.py b/mxAgent/samples/tools/tool_query_accommodations.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_accommodations.py rename to mxAgent/samples/tools/tool_query_accommodations.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_attractions.py b/mxAgent/samples/tools/tool_query_attractions.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_attractions.py rename to mxAgent/samples/tools/tool_query_attractions.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_city.py b/mxAgent/samples/tools/tool_query_city.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_city.py rename to mxAgent/samples/tools/tool_query_city.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_distance_matrix.py rename to mxAgent/samples/tools/tool_query_distance_matrix.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_restaurants.py b/mxAgent/samples/tools/tool_query_restaurants.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_restaurants.py rename to mxAgent/samples/tools/tool_query_restaurants.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_transports.py b/mxAgent/samples/tools/tool_query_transports.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_transports.py rename to mxAgent/samples/tools/tool_query_transports.py diff --git a/mxAgent/agent_sdk/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py similarity index 100% rename from mxAgent/agent_sdk/samples/tools/tool_query_weather.py rename to mxAgent/samples/tools/tool_query_weather.py -- Gitee From dd4f1cfe09175e522d6f94c1251c439dec99b22b Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 19:54:11 +0800 Subject: [PATCH 17/30] rm requrements --- mxAgent/requirements.txt | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 mxAgent/requirements.txt diff --git a/mxAgent/requirements.txt b/mxAgent/requirements.txt deleted file mode 100644 index c96356599..000000000 --- a/mxAgent/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -requests=2.27.1 -tqdm -selenium=4.9.0 -bs4 -transformers -openai -pandas -datasets -peft -fschat -langchain -vllm -rouge -langchain_openai -colorlog -rouge-score -langchain-community -loguru -tiktoken \ No newline at end of file -- Gitee From ebe43dab205f8151d8dd891cec73ac59221a461a Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 20:01:44 +0800 Subject: [PATCH 18/30] add samoles --- mxAgent/samples/basic_demo/agent_test.py | 133 +++++++++++++ .../basic_demo/agent_traj_systhesis.py | 66 +++++++ mxAgent/samples/basic_demo/intent_router.py | 34 ++++ .../samples/basic_demo/traj_generate_test.py | 105 ++++++++++ mxAgent/samples/tools/tool_summary.py | 71 +++++++ mxAgent/samples/tools/web_summary_api.py | 176 +++++++++++++++++ .../front/chat_bot_release.py | 62 ++++++ .../samples/travel_agent_demo/travelagent.py | 184 ++++++++++++++++++ 8 files changed, 831 insertions(+) create mode 100644 mxAgent/samples/basic_demo/agent_test.py create mode 100644 mxAgent/samples/basic_demo/agent_traj_systhesis.py create mode 100644 mxAgent/samples/basic_demo/intent_router.py create mode 100644 mxAgent/samples/basic_demo/traj_generate_test.py create mode 100644 mxAgent/samples/tools/tool_summary.py create mode 100644 mxAgent/samples/tools/web_summary_api.py create mode 100644 mxAgent/samples/travel_agent_demo/front/chat_bot_release.py create mode 100644 mxAgent/samples/travel_agent_demo/travelagent.py diff --git a/mxAgent/samples/basic_demo/agent_test.py b/mxAgent/samples/basic_demo/agent_test.py new file mode 100644 index 000000000..7cc0e664d --- /dev/null +++ b/mxAgent/samples/basic_demo/agent_test.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import warnings +import os + +from loguru import logger + +from agent_sdk.agentchain.react_agent import ReactAgent, ReactReflectAgent +from agent_sdk.agentchain.tool_less_agent import ToollessAgent +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAccommodations, QueryAttractions, \ + QueryGoogleDistanceMatrix, QueryTransports, Finish + +warnings.filterwarnings('ignore') + +API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") +API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") +LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") + +MAX_CONTEXT_LEN = 4096 + + +EXAMPLE = ''' +Question: Can you help with a 5 day trip from Orlando to New York? Departure date is March 10, 2022. +Thought: To create a travel itinerary, I need to find accommodations, transportation, and attractions in New York. I will first find hotels in New York. +Action: QueryAccommodations +Action Input: {"destination_city": "New York", "position": "Central Park", "rank": "four stars"} +Observation1: [{"title": "紐約市10 大最佳四星級酒店 - Tripadvisor", "url": "https://www.tripadvisor.com.hk/Hotels-g60763-zfc4-New_York_City_New_York-Hotels.html", "snippet": "紐約市四星級酒店 · 1. Moxy NYC Times Square · 3,825 則評論 · 2. 格甚溫酒店 · 1,155 則評論 · 3. 托米哈德森廣場飯店 · 3,277 則評論 · 4. 時代廣場愛迪生酒店 · 5. Hard ..."}, {"title": "中央公園酒店| 人氣優惠及套餐", "url": "https://www.agoda.com/zh-hk/park-central-hotel/hotel/new-york-ny-us.html", "snippet": "中央公園酒店是一家位於紐約市的4.0星級酒店,提供豪華的住宿體驗。酒店於2013年進行了最後一次翻新,確保客人能夠享受現代化的設施和舒適的環境。酒店擁有761間客房,提供 ..."}, {"title": "紐約中央公園艾美酒店(Le Méridien New York, Central Park)", "url": "https://www.agoda.com/zh-hk/viceroy-central-park-new-york_2/hotel/new-york-ny-us.html", "snippet": "紐約中央公園艾美酒店位於美國紐約市,是一家四星級酒店。這家酒店提供240間客房 ... 作為一家五星級酒店,紐約中央公園艾美酒店提供優質的服務和舒適的住宿環境 ..."}] +Thought: Now that I have found some hotels in New York, I will next find transportation options from Orlando to New York for the travel dates specified. I will use the QueryTransports API for this purpose. +Action: QueryTransports +Action Input: {"departure_city": "Orlando", "destination_city": "New York", "date": "2022-03-10", "requirement": "budget-friendly"} +Observation2: [{"title": "从奥兰多出发前往纽约的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/orlb/nyca/orlando-to-new-york.html", "snippet": "查找从奥兰多飞往纽约最便宜的月份 ; 3月. ¥750 起 ; 4月. ¥927 起 ; 5月. ¥1,012 起 ; 6月. ¥1,107 起 ; 7月. ¥1,283 起."}, {"title": "从奥兰多国际出发前往纽约拉瓜迪亚的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/mco/lga/orlando-international-to-new-york-laguardia.html", "snippet": "我们始终关注票价,方便您在几秒钟内锁定好价机票。目前,从奥兰多国际飞往纽约拉瓜迪亚最便宜的月份是十一月。"}] +Thought: Now that I have found some transportation options, I will proceed to find attractions in New York. I will use the QueryAttractions API to search for tourist attractions in New York. +Action: QueryAttractions +Action Input: {"destination": "New York"} +Observation3: - snippet: 從參觀紐約市最知名觀光景點開始:時代廣場、帝國大廈、自由女神像,然後利用剩餘的時間探索周圍地區推薦去處。 探索熱門體驗. 根據評等和預訂次數,看看其他旅客喜歡從事 ... + title: 紐約市10 大最佳旅遊景點(2024) - Tripadvisor + url: https://www.tripadvisor.com.hk/Attractions-g60763-Activities-New_York_City_New_York.html +- snippet: 紐約景點推薦 · 紐約景點#1 紐約中央公園 · 紐約景點#2 范德堡一號大樓 SUMMIT · 紐約景點#3 第五大道(Fifth Avenue) + · 紐約景點#4 大都會藝術博物館The ... + title: 【2024紐約景點】漫遊曼哈頓!26個必去行程&免費景點整理 + url: https://www.klook.com/zh-TW/blog/new-york-must-go/ +- snippet: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 · 紐約NewYork景點推薦-10大必去景點 · 1.中央公園(Central + Park) · 2.第五大道(Fifth Avenue) · 3.大都會 ... + title: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 + url: https://schoolaplus.com/articles-detail.asp?seq=35 +Thought: Now that I have found some attractions in New York, I will summarize the information and create a travel itinerary for the 5-day trip. I will use the Finish tool to provide the final answer. +Action: Finish +Action Input: {"plan details": "Day 1: Depart from Orlando to New York on March 10, 2022. Stay at the Park Central Hotel in Central Park. Visit the Empire State Building and Times Square. Have dinner at Lombardi's Pizza. +Day 2: Visit Central Park, the Metropolitan Museum of Art, and the American Museum of Natural History. Have lunch at Shake Shack and dinner at Le Pain Quotidien. +Day 3: Explore the Brooklyn Bridge, Brooklyn Heights, and DUMBO. Have lunch at Di Fara Pizza and dinner at Peter Luger Steak House. +Day 4: Visit the Statue of Liberty and Ellis Island. Have lunch at The Boil and dinner at Xi'an Famous Foods. +Day 5: Spend the day shopping on Fifth Avenue and visiting the Rockefeller Center. Have lunch at Shake Shack and dinner at Katz's Delicatessen."} +''' + + +def get_default_react_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] + + agent = ReactAgent(llm=llm, tool_list=tool_list, max_context_len=max_context_len) + return agent + + +def get_default_react_agent_fewshot(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] + + agent = ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) + return agent + + +def get_default_toolless_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + agent = ToollessAgent(llm=llm, max_context_len=max_context_len) + return agent + + +def get_default_react_reflect_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + + tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] + agent = ReactReflectAgent(reflect_llm=llm, react_llm=llm, example=EXAMPLE, + tool_list=tool_list, max_context_len=max_context_len) + return agent + + +def test_react_agent(): + a = get_default_react_agent_fewshot(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +def test_toolless_agent(): + a = get_default_toolless_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", + text="given information") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +def test_react_reflect_agent(): + a = get_default_react_reflect_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", + text="given information") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +if __name__ == '__main__': + logger.info("react agent test begin") + test_react_agent() + logger.info("react agent test end") + + logger.info("toolless agent test begin") + test_toolless_agent() + logger.info("toolless agent test end") + + logger.info("react reflect agent test begin") + test_react_reflect_agent() + logger.info("react reflect agent test end") + + + + + + + diff --git a/mxAgent/samples/basic_demo/agent_traj_systhesis.py b/mxAgent/samples/basic_demo/agent_traj_systhesis.py new file mode 100644 index 000000000..ced5ad1ae --- /dev/null +++ b/mxAgent/samples/basic_demo/agent_traj_systhesis.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import os +import warnings + +from langchain._api import LangChainDeprecationWarning +from loguru import logger +from tqdm import tqdm + +from agent_sdk.agentchain.react_agent import ReactAgent +from agent_sdk.common.enum_type import AgentRunStatus +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAttractions, QueryTransports, QueryAccommodations, \ + QueryRestaurants, QueryGoogleDistanceMatrix +from mxAgent.samples.basic_demo.agent_test import EXAMPLE + + +warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore', category=DeprecationWarning) +warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) + +os.environ["WORKNING_DIR"] = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") +API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") +LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") + +MAX_CONTEXT_LEN = 4096 + + +def get_default_react_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + tool_list = [QueryAttractions, QueryTransports, QueryAccommodations, QueryRestaurants, QueryGoogleDistanceMatrix] + return ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) + + +if __name__ == '__main__': + agent = get_default_react_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + + queries = [ + "Book a rental car for two people in Salt Lake City from April 15 to April 18, 2022.", + "Research and list down outdoor activities suitable for adrenaline junkies in Moab \ +between April 12 and 14, 2022.", + "Write a short itinerary for a weekend trip to Nashville, starting on April 15, including live music venues." + ] + + s = AgentRunStatus() + + for query in tqdm(queries): + result = agent.run(query) + s.total_cnt += 1 + if agent.finished: + s.success_cnt += 1 + agent.save_agent_status("./save_instructions.jsonl") + agent.reset() + logger.info("\n") + logger.info("*" * 150) + logger.info(f"Question: {query}") + logger.info("*" * 150) + logger.info(f"Final answer: {result.answer}") + logger.info("*" * 150) + logger.info(f"Trajectory Path: {result.scratchpad}") + logger.info("*" * 150) + + logger.info(f"success rates: {s}") + logger.info(f"Total success rates: {s}") diff --git a/mxAgent/samples/basic_demo/intent_router.py b/mxAgent/samples/basic_demo/intent_router.py new file mode 100644 index 000000000..3826da5bd --- /dev/null +++ b/mxAgent/samples/basic_demo/intent_router.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from loguru import logger + +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.router_agent import RouterAgent + +llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run + +INTENT = { + "query_flight": "用户期望查询航班信息", + "query_attraction": "用户期望查询旅游景点信息", + "query_hotel": "用户期望查询酒店和住宿信息", + "plan_attraction": "用户期望给出旅行规划建议", + "whimsical": "异想天开", + "other": "其他不符合上述意图的描述" +} + +querys = [ + "帮我查一下从北京去深圳的机票", + "帮我查一下北京的旅游景点", + "我想去北京旅游", + "去北京旅游可以住在哪里呢,推荐一下", + "帮我去书城买本书", "我想上天" +] + +agent = RouterAgent(llm=llm, intents=INTENT) + +for query in querys: + response = agent.run(query) + agent.reset() + logger.info(f"query: {query}, intent: {response.answer}") diff --git a/mxAgent/samples/basic_demo/traj_generate_test.py b/mxAgent/samples/basic_demo/traj_generate_test.py new file mode 100644 index 000000000..bd4b73048 --- /dev/null +++ b/mxAgent/samples/basic_demo/traj_generate_test.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json +import os +import warnings +from typing import Callable, List +from tqdm import tqdm +from loguru import logger +from langchain._api import LangChainDeprecationWarning + +from agent_sdk.agentchain.base_agent import BaseAgent +from agent_sdk.agentchain.single_action_agent import SingleActionAgent +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAccommodations, QueryAttractions, QueryRestaurants, \ + QueryTransports, QueryGoogleDistanceMatrix + + +API_BASE = "http://10.44.115.98:8006/v1" +API_KEY = "EMPTY" +MODEL_NAME = "Qwen2-7b-Instruct" + +os.environ["OPENAI_API_BASE"] = API_BASE +os.environ["OPENAI_API_KEY"] = API_KEY +os.environ["MODEL_NAME"] = MODEL_NAME +os.environ["WORKING_DIR"] = os.path.dirname( + os.path.dirname(os.path.realpath(__file__))) + +warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore', category=DeprecationWarning) +warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) + + +class TrajectoryGenerator: + + @staticmethod + def generate(output_path: str, agent: BaseAgent, load_dataset: Callable[[], List[str]], **kwargs): + questions = load_dataset() + for q in tqdm(questions): + try: + agent.run(q, **kwargs) + agent.save_agent_status(output_path) + agent.reset() + + except Exception as err: + logger.warning(f"generate traj failed, query: {q}, agent: {agent.name}, err: {err}") + continue + + @staticmethod + def _check_data_format(data): + if not isinstance(data, list): + raise ValueError("Data should be a list of dict") + + if len(data) == 0: + raise ValueError("Data should not be empty") + + if not isinstance(data[0], dict): + raise ValueError("Data item should be a dict") + + alpaca_format_keys = ["instruction", "input", "output", "status"] + data_keys_set = set(data[0].keys()) + + if not all([key in data_keys_set for key in alpaca_format_keys]): + raise ValueError("need alpaca data format") + + def _load_data_from_file(self, data_path): + if not os.path.exists(data_path): + raise FileNotFoundError(f"File not found: {data_path}") + + if data_path.endswith(".jsonl"): + traj_data = [json.loads(line) for line in open(data_path, "r")] + else: + raise ValueError("Unknown file format") + + self._check_data_format(traj_data) + return traj_data + + +def get_single_action_agent(api_base, api_key, llm_name): + tool_list = [ + QueryAccommodations, QueryAttractions, QueryRestaurants, + QueryTransports, QueryGoogleDistanceMatrix + ] + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, + api_base, api_key, llm_name).run + return SingleActionAgent(llm=llm, tool_list=tool_list, max_steps=5) + + +if __name__ == '__main__': + single_agent = get_single_action_agent(API_BASE, API_KEY, MODEL_NAME) + queries = [ + "Write a review of the hotel \"The Beach House\" in Charlotte Amalie.", + "Book a flight from Evansville to Sacramento for April 10th.", + "Create a list of top 5 attractions in Hilo for a solo traveler.", + "Compare the prices of hotels in Newark for a 3-night stay.", + "Book a hotel room in Paducah for April 12th.", + "Write a travel blog post about visiting the Golden Gate Bridge in San Francisco.", + "Recommend the best mode of transportation from Flagstaff to Phoenix.", + "Determine the best time to visit the Statue of Liberty.", + "Compare the prices of car rentals in Seattle.", + "What are the top - rated museums in Harrisburg?" + ] + generator = TrajectoryGenerator() + generator.generate(output_path="./save_instructions.jsonl", agent=single_agent, + load_dataset=lambda: queries) diff --git a/mxAgent/samples/tools/tool_summary.py b/mxAgent/samples/tools/tool_summary.py new file mode 100644 index 000000000..9d1b706c9 --- /dev/null +++ b/mxAgent/samples/tools/tool_summary.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class PlanSummary(API): + name = "PlanSummary" + description = "this api uesed to summary all the travel plan." + input_parameters = { + 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, + 'accomadation': {'type': 'str', 'description': "the accomodation information"}, + 'transport': {'type': 'str', 'description': "the transport information"}, + 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, + 'duration': {'type': 'str', 'description': "The days of travel"}, + } + + output_parameters = { + 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, + } + + example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ + accomadation,and transport information" + example = ( + """ + { + "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ + Borough High Street in Southwark and King William Street.", + "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ + culture, shopping, and green spaces", + "transport": "10 hours from Beijing to London cost $1000.", + } + """) + + def __init__(self): + pass + + def format_tool_input_parameters(self, llm_output) -> dict: + return llm_output if llm_output else {} + + def check_api_call_correctness(self, response, groundtruth=None) -> bool: + if response['exception'] is None: + return True + else: + return False + + def call(self, input_parameters, **kwargs): + # 总的只能输入2500个字左右 + attraction = input_parameters.get('attractions') + hotel = input_parameters.get('accomadation') + transport = input_parameters.get('transport') + weather = input_parameters.get("weather") + duration = input_parameters.get("duration") + + res = "" + if duration is not None: + res += f"【用户需要旅行的天数】:{duration}天\n" + if attraction is not None: + res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" + if hotel is not None: + res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" + if transport is not None: + res = res + f"【交通安排】:\n{str(transport)[:500]}\n" + if weather is not None: + res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" + summary = { + "summary": res + } + return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/samples/tools/web_summary_api.py b/mxAgent/samples/tools/web_summary_api.py new file mode 100644 index 000000000..feeb4bb5b --- /dev/null +++ b/mxAgent/samples/tools/web_summary_api.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import asyncio +import os +import re +import time +from concurrent.futures import ThreadPoolExecutor, wait, as_completed + +import aiohttp +import requests +import tiktoken +import urllib3 +from bs4 import BeautifulSoup +from loguru import logger +from samples.tools.google_search_api import google_search + + +def check_number_input(num, crow): + if not num.isdigit(): + return False + num = int(num) + if num > crow: + return False + return True + + +async def bai_du(url): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, + headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: + async with session.get(url) as response: + res = await response.text() + return res + + +class WebSummary: + encoder = tiktoken.get_encoding("gpt2") + + @classmethod + def get_detail_copy(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = requests.get( + url, headers=headers, timeout=(3, 3), stream=True) + mommt = time.time() + logger.info(f"finish request website: {mommt},{url}") + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + return '', f"获取网页{url}内容失败" + + content = response.content + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + except Exception as e: + logger.error(e) + return '', e + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, "" + + @classmethod + async def get_details(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' + urllib3.disable_warnings() + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = await bai_du(url) + mommt = time.time() + logger.debug(f"finish request website: {mommt},{url}") + content = response + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + if 'PleaseenableJSanddisableanyadblocker' in text: + text = "" + except Exception as e: + logger.error(e) + return '', e + if len(text) == 0: + return "", "no valid website content" + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, "" + + @classmethod + def summary_call(cls, web, max_summary_number, summary_prompt): + title = web.get("title", "") + url = web.get("url") + snippet = web.get("snippet", "") + web_summary = {} + if url is None: + return web_summary + + web_summary['title'] = title + web_summary['url'] = url + try: + content = asyncio.run(cls.get_details(url, summary_prompt)) + except Exception as e: + logger.error(e) + if not isinstance(content, str) or len(content) == 0: + web_summary['snippet'] = snippet + else: + web_summary['content'] = content + + return web_summary + + @classmethod + def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): + logger.add('app.log', level='DEBUG') + cls.llm = llm + try: + mommt = time.time() + logger.debug(f"start google search: {mommt}") + if isinstance(keys, list): + keys = ",".join(keys) + search_result = google_search(keys, search_num) + mommt = time.time() + logger.debug(f"finish google search: {mommt}") + except Exception as e: + logger.error(e) + return [] + + max_summary_number = summary_num + + webs = [] + with ThreadPoolExecutor(max_workers=3) as executor: + futures = [] + for web in search_result: + thread = executor.submit( + cls.summary_call, web, max_summary_number, summary_prompt) + futures.append(thread) + for future in as_completed(futures): + webs.append(future.result()) + wait(futures) + return webs + + @classmethod + def build_summary_prompt(cls, query, prompt): + max_input_token_num = 4096 + if len(query) == 0: + return prompt.format(text=query) + input_token_len = len(WebSummary.encoder.encode(query)) + prompt_len = len(WebSummary.encoder.encode(prompt)) + clip_text_index = int( + len(query) * (max_input_token_num - prompt_len) / input_token_len) + clip_text = query[:clip_text_index] + return prompt.format(input=clip_text) + + @classmethod + def generate_content(cls, query, prompt): + max_tokens = 1000 + try: + pmt = WebSummary.build_summary_prompt(query, prompt) + output = cls.llm(prompt=pmt, max_tokens=max_tokens) + except Exception as e: + logger.error(e) + return e + return output diff --git a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py new file mode 100644 index 000000000..d74e03696 --- /dev/null +++ b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py @@ -0,0 +1,62 @@ +import streamlit as st +from samples.travel_agent.travelagent import TravelAgent + +if __name__ == "__main__": + st.set_page_config( + page_title="旅游规划agent", + page_icon="./logo.jpg" + ) + st.logo("logo.jpg") + st.markdown('

旅游规划Agent

', unsafe_allow_html=True) + + + placeholder1 = st.empty() + placeholder2 = st.empty() + placeholder3 = st.empty() + + if "messages" not in st.session_state: + st.session_state.messages = [] + + if "aagent" not in st.session_state: + st.session_state.agent = TravelAgent() + + with placeholder1: + container = st.container(height=300, border=False) + with placeholder2: + _, col1, _ = st.columns([10, 2, 10]) + with col1: + st.image("logo.jpg", use_column_width=True) + with placeholder3: + _, col2, _ = st.columns([1, 20, 1]) + helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

+

例如:从北京到西安旅游规划

+

例如:西安有哪些免费的博物馆景点

+

例如:查一下西安的酒店

""" + with col2: + st.markdown(helloinfo, unsafe_allow_html=True) + + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.empty() + st.markdown(message["content"]) + + if prompt = st.chat_input("send message"): + st.session_state.messages.append({"role":"user", "content":prompt}) + placeholder1.empty() + placeholder2.empty() + placeholder3.empty() + + agent = st.session_state["agent"] + + with st.chat_message("user"): + st.markdown(prompt) + + with st.chat_message("assistant"): + with st.spinner("thinking..."): + response = agent.run(query=prompt, stream=True) + if isinstance(response, str): + st.markdown(response) + else: + response = st.write_stream(response) + + st.session_state.messages.append({"role":"assistant", "content":response}) diff --git a/mxAgent/samples/travel_agent_demo/travelagent.py b/mxAgent/samples/travel_agent_demo/travelagent.py new file mode 100644 index 000000000..247402940 --- /dev/null +++ b/mxAgent/samples/travel_agent_demo/travelagent.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import enum +from abc import ABC +from loguru import logger + +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.recipe_agent import RecipeAgent +from agent_sdk.agentchain.router_agent import RouterAgent +from agent_sdk.agentchain.tool_less_agent import ToollessAgent +from agent_sdk.agentchain.base_agent import AgentRunResult + +from samples.tools.tool_query_accommodations import QueryAccommodations +from samples.tools.tool_query_transports import QueryTransports +from samples.tools.tool_query_attractions import QueryAttractions +from samples.tools.tool_summary import PlanSummary +from samples.tools.tool_general_query import GeneralQuery +from samples.tools.tool_query_weather import QueryWeather + + +PESUEDE_CODE = """步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息 +步骤2:根据用户的问题,从网络中查询相关的出行交通信息。 +步骤3:根据用户的问题,从网络中搜索相关的住宿和酒店信息; +步骤4:根据用户的问题,查询用户需要的城市天气情况; +步骤5:总结以上的出行信息、景点游玩、住宿信息等。""" +TRANSPORT_INST = """步骤1:根据用户的输入问题,从网络中查询相关的出行交通信息""" +ATTRACTION_INST = "步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息" +HOTEL_INST = """步骤1:根据用户的问题,从网络中搜索相关的住宿和酒店信息,""" +WEATHER_INST = "步骤一:根据用户的问题,查询用户需要的城市天气情况" +OTHER_INST = """步骤1:根据用户的输入,从互联网中查询相关的解答""" + + +GENERAL_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页摘要信息进行总结,并以markdown的格式进行返回, +请添加适当的词语,使得语句内容连贯,通顺 +请将content和snippet的信息进行综合处理,进行总结,生成一个段落。 +涉及到url字段时,使用超链接的格式将网页url链接到网页title上。 +参数介绍】: +title:网页标题 +url:网页链接 +snippet:网页摘要信息 +content:网页的内容总结 +下面是JSON格式的输入: +{text} +请生成markdown段落:""" + +WEATHER_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手, +当前的工作场景是:天气出行建议;输入的内容是JSON格式的用户所查询城市未来的天气预报,请将这些信息总结为的自然语言展示天气预报的信息,并对用户的出游给除建议, +根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 +请添加适当的词语,使得语句内容连贯,通顺,并尽可能保留输入的信息和数据,但不要自行杜撰信息。 +提供的信息以JSON的格式进行展示 +【参数介绍】: +date:日期 +day_weather:白天的天气情况 +day_wind_direction:白天风向 +day_wind_power: 白天风力 +night_weather:夜晚的天气情况 +night_wind_direction:夜晚风向 +night_wind_power: 夜晚风力 +max_degree: 最高温 +min_degree:最低温 +下面是JSON格式的输入: +{text} +请生成markdown段落: +""" +PLANNER_FINAL_PROMPT = """你是一个擅长规划和文字处理的智能助手,你需要将提供的信息按照下面的步骤撰写一份旅游攻略,输出markdown格式的段落, +你可以添加适当的语句,使得段落通顺,但不要自己杜撰信息。 +步骤】 +1. 根据【用户需要旅行的天数】,将输入的景点分配到每一天的行程中,每天2-3个景点,并介绍景点的详细情况 +2. 叙述输入中推荐的住宿情况,详细介绍酒店的详细情况,和预定链接 +3. 叙述输入中查询的交通安排,详细介绍每个出行方案的价格、时间、时长等详细情况,和预定链接 +4. 介绍输入中天气预报的情况,根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 +【参数介绍】: +title:网页标题 +url:网页链接,满足用户需求的酒店筛选结果 +content:网页主要内容提取物 +snippet:网页摘要信息 +输入的信息以JSON格式,下面是的输入: +{text} +请生成markdown段落:""" + + + +TRAVEL_PLAN = "TRAVEL_PLAN" +QUERY_ATTRACTION = "QUERY_ATTRACTION" +QUERY_HOTEL = "QUERY_HOTEL" +QUERY_TRANSPORT = "QUERY_TRANSPORT" +QUERY_WEATHER = "QUERY_WEATHER" +OTHERS = "OTHERS" + +classifer = [TRAVEL_PLAN, QUERY_ATTRACTION, QUERY_HOTEL, QUERY_TRANSPORT, QUERY_WEATHER, OTHERS] + +INST_MAP = { + TRAVEL_PLAN :PESUEDE_CODE, + QUERY_ATTRACTION :ATTRACTION_INST, + QUERY_HOTEL:HOTEL_INST, + QUERY_TRANSPORT :TRANSPORT_INST, + QUERY_WEATHER :WEATHER_INST, + OTHERS:OTHER_INST +} + +FINAL_PMT_MAP = { + TRAVEL_PLAN :PLANNER_FINAL_PROMPT, + QUERY_ATTRACTION :GENERAL_FINAL_PROMPT, + QUERY_HOTEL:GENERAL_FINAL_PROMPT, + QUERY_TRANSPORT :GENERAL_FINAL_PROMPT, + QUERY_WEATHER :WEATHER_FINAL_PROMPT, + OTHERS: GENERAL_FINAL_PROMPT + +} + +TOOL_LIST_MAP = { + TRAVEL_PLAN :[QueryAccommodations, QueryAttractions, QueryTransports, PlanSummary, QueryWeather], + QUERY_ATTRACTION :[QueryAttractions], + QUERY_HOTEL:[QueryAccommodations], + QUERY_TRANSPORT :[QueryTransports], + QUERY_WEATHER : [QueryWeather], + OTHERS:[] +} + +intents = { + TRAVEL_PLAN :"询问旅行规划,问题中要求旅游项目日程安排、交通查询、查询当地住宿等方面的能力", + QUERY_ATTRACTION :"查询旅游项目、景区、旅游活动", + QUERY_HOTEL: "仅查询酒店和住宿信息", + QUERY_TRANSPORT : "与现实中出行、乘坐交通、如高铁、动车、飞机、火车等相关的意图", + QUERY_WEATHER :"包括气温、湿度、降水等与天气、天气预报相关的意图", + OTHERS :"与旅游场景不相干的查询" +} +LLM_MODEL = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run + + +class TalkShowAgent(ToollessAgent, ABC): + def __init__(self, llm, prompt="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:'\ + 目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐,请利用你的知识回答问题,这是用户的问题:{query}", + **kwargs): + super().__init__(llm, prompt, **kwargs) + self.query = "" + + def _build_agent_prompt(self, **kwargs): + return self.prompt.format( + query=self.query + ) + + +class TravelAgent: + @classmethod + def route_query(cls, query): + router_agent = RouterAgent(llm=LLM_MODEL, intents=intents) + classify = router_agent.run(query).answer + if classify not in classifer or classify == OTHERS: + return TalkShowAgent(llm=LLM_MODEL) + return RecipeAgent(name=classify, + description="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐", + llm=LLM_MODEL, + tool_list=TOOL_LIST_MAP[classify], + recipe=INST_MAP[classify], + max_steps=3, + max_token_number=4096, + final_prompt=FINAL_PMT_MAP[classify]) + + def run(self, query, stream): + agent = self.route_query(query) + return agent.run(query, stream=stream) + +if __name__ == "__main__": + # request = "去北京的旅游规划" + # request = "从北京到西安的机票" + # request = "查询北京王府井附近的高档酒店" + # request = "泰国有哪些值得推荐的景点" + # request = "帮我查一下北京最近的天气" + # request = "上海酒店查询" + # request = "北京到上海的高铁" + # request = "上海天气怎么样" + request = "帮我制定一份从北京到上海6天的旅游计划" + + travel_agent = TravelAgent() + res = travel_agent.run(request, stream=True) + if isinstance(res, AgentRunResult): + logger.info("-----------run agent success-------------") + logger.info(res.answer) + else: + for char in res: + logger.info(char) -- Gitee From 47bc5eff1964c3dc2f7342adf7189008c1e66ee7 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 20:03:18 +0800 Subject: [PATCH 19/30] add tool manager --- mxAgent/agent_sdk/prompts/pre_prompt.py | 192 +++++++++++++++++++++ mxAgent/agent_sdk/toolmngt/api.py | 97 +++++++++++ mxAgent/agent_sdk/toolmngt/tool_manager.py | 113 ++++++++++++ mxAgent/requirements.txt | 19 ++ 4 files changed, 421 insertions(+) create mode 100644 mxAgent/agent_sdk/prompts/pre_prompt.py create mode 100644 mxAgent/agent_sdk/toolmngt/api.py create mode 100644 mxAgent/agent_sdk/toolmngt/tool_manager.py create mode 100644 mxAgent/requirements.txt diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py new file mode 100644 index 000000000..51451e0f8 --- /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 000000000..1230ecd61 --- /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 000000000..e2d1f56b7 --- /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/requirements.txt b/mxAgent/requirements.txt new file mode 100644 index 000000000..c96356599 --- /dev/null +++ b/mxAgent/requirements.txt @@ -0,0 +1,19 @@ +requests=2.27.1 +tqdm +selenium=4.9.0 +bs4 +transformers +openai +pandas +datasets +peft +fschat +langchain +vllm +rouge +langchain_openai +colorlog +rouge-score +langchain-community +loguru +tiktoken \ No newline at end of file -- Gitee From 22fee6296349e2052adfb13dc5d279edf5f5375a Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 20:05:34 +0800 Subject: [PATCH 20/30] fix code --- mxAgent/agent_sdk/prompts/pre_prompt.py | 192 ------------------ mxAgent/agent_sdk/toolmngt/api.py | 97 --------- mxAgent/agent_sdk/toolmngt/tool_manager.py | 113 ----------- mxAgent/requirements.txt | 19 -- mxAgent/samples/basic_demo/agent_test.py | 133 ------------ .../basic_demo/agent_traj_systhesis.py | 66 ------ mxAgent/samples/basic_demo/intent_router.py | 34 ---- .../samples/basic_demo/traj_generate_test.py | 105 ---------- .../front/chat_bot_release.py | 62 ------ .../samples/travel_agent_demo/travelagent.py | 184 ----------------- 10 files changed, 1005 deletions(-) delete mode 100644 mxAgent/agent_sdk/prompts/pre_prompt.py delete mode 100644 mxAgent/agent_sdk/toolmngt/api.py delete mode 100644 mxAgent/agent_sdk/toolmngt/tool_manager.py delete mode 100644 mxAgent/requirements.txt delete mode 100644 mxAgent/samples/basic_demo/agent_test.py delete mode 100644 mxAgent/samples/basic_demo/agent_traj_systhesis.py delete mode 100644 mxAgent/samples/basic_demo/intent_router.py delete mode 100644 mxAgent/samples/basic_demo/traj_generate_test.py delete mode 100644 mxAgent/samples/travel_agent_demo/front/chat_bot_release.py delete mode 100644 mxAgent/samples/travel_agent_demo/travelagent.py diff --git a/mxAgent/agent_sdk/prompts/pre_prompt.py b/mxAgent/agent_sdk/prompts/pre_prompt.py deleted file mode 100644 index 51451e0f8..000000000 --- a/mxAgent/agent_sdk/prompts/pre_prompt.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- 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 deleted file mode 100644 index 1230ecd61..000000000 --- a/mxAgent/agent_sdk/toolmngt/api.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- 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 deleted file mode 100644 index e2d1f56b7..000000000 --- a/mxAgent/agent_sdk/toolmngt/tool_manager.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- 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/requirements.txt b/mxAgent/requirements.txt deleted file mode 100644 index c96356599..000000000 --- a/mxAgent/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -requests=2.27.1 -tqdm -selenium=4.9.0 -bs4 -transformers -openai -pandas -datasets -peft -fschat -langchain -vllm -rouge -langchain_openai -colorlog -rouge-score -langchain-community -loguru -tiktoken \ No newline at end of file diff --git a/mxAgent/samples/basic_demo/agent_test.py b/mxAgent/samples/basic_demo/agent_test.py deleted file mode 100644 index 7cc0e664d..000000000 --- a/mxAgent/samples/basic_demo/agent_test.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import warnings -import os - -from loguru import logger - -from agent_sdk.agentchain.react_agent import ReactAgent, ReactReflectAgent -from agent_sdk.agentchain.tool_less_agent import ToollessAgent -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from samples.tools import QueryAccommodations, QueryAttractions, \ - QueryGoogleDistanceMatrix, QueryTransports, Finish - -warnings.filterwarnings('ignore') - -API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") -API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") -LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") - -MAX_CONTEXT_LEN = 4096 - - -EXAMPLE = ''' -Question: Can you help with a 5 day trip from Orlando to New York? Departure date is March 10, 2022. -Thought: To create a travel itinerary, I need to find accommodations, transportation, and attractions in New York. I will first find hotels in New York. -Action: QueryAccommodations -Action Input: {"destination_city": "New York", "position": "Central Park", "rank": "four stars"} -Observation1: [{"title": "紐約市10 大最佳四星級酒店 - Tripadvisor", "url": "https://www.tripadvisor.com.hk/Hotels-g60763-zfc4-New_York_City_New_York-Hotels.html", "snippet": "紐約市四星級酒店 · 1. Moxy NYC Times Square · 3,825 則評論 · 2. 格甚溫酒店 · 1,155 則評論 · 3. 托米哈德森廣場飯店 · 3,277 則評論 · 4. 時代廣場愛迪生酒店 · 5. Hard ..."}, {"title": "中央公園酒店| 人氣優惠及套餐", "url": "https://www.agoda.com/zh-hk/park-central-hotel/hotel/new-york-ny-us.html", "snippet": "中央公園酒店是一家位於紐約市的4.0星級酒店,提供豪華的住宿體驗。酒店於2013年進行了最後一次翻新,確保客人能夠享受現代化的設施和舒適的環境。酒店擁有761間客房,提供 ..."}, {"title": "紐約中央公園艾美酒店(Le Méridien New York, Central Park)", "url": "https://www.agoda.com/zh-hk/viceroy-central-park-new-york_2/hotel/new-york-ny-us.html", "snippet": "紐約中央公園艾美酒店位於美國紐約市,是一家四星級酒店。這家酒店提供240間客房 ... 作為一家五星級酒店,紐約中央公園艾美酒店提供優質的服務和舒適的住宿環境 ..."}] -Thought: Now that I have found some hotels in New York, I will next find transportation options from Orlando to New York for the travel dates specified. I will use the QueryTransports API for this purpose. -Action: QueryTransports -Action Input: {"departure_city": "Orlando", "destination_city": "New York", "date": "2022-03-10", "requirement": "budget-friendly"} -Observation2: [{"title": "从奥兰多出发前往纽约的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/orlb/nyca/orlando-to-new-york.html", "snippet": "查找从奥兰多飞往纽约最便宜的月份 ; 3月. ¥750 起 ; 4月. ¥927 起 ; 5月. ¥1,012 起 ; 6月. ¥1,107 起 ; 7月. ¥1,283 起."}, {"title": "从奥兰多国际出发前往纽约拉瓜迪亚的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/mco/lga/orlando-international-to-new-york-laguardia.html", "snippet": "我们始终关注票价,方便您在几秒钟内锁定好价机票。目前,从奥兰多国际飞往纽约拉瓜迪亚最便宜的月份是十一月。"}] -Thought: Now that I have found some transportation options, I will proceed to find attractions in New York. I will use the QueryAttractions API to search for tourist attractions in New York. -Action: QueryAttractions -Action Input: {"destination": "New York"} -Observation3: - snippet: 從參觀紐約市最知名觀光景點開始:時代廣場、帝國大廈、自由女神像,然後利用剩餘的時間探索周圍地區推薦去處。 探索熱門體驗. 根據評等和預訂次數,看看其他旅客喜歡從事 ... - title: 紐約市10 大最佳旅遊景點(2024) - Tripadvisor - url: https://www.tripadvisor.com.hk/Attractions-g60763-Activities-New_York_City_New_York.html -- snippet: 紐約景點推薦 · 紐約景點#1 紐約中央公園 · 紐約景點#2 范德堡一號大樓 SUMMIT · 紐約景點#3 第五大道(Fifth Avenue) - · 紐約景點#4 大都會藝術博物館The ... - title: 【2024紐約景點】漫遊曼哈頓!26個必去行程&免費景點整理 - url: https://www.klook.com/zh-TW/blog/new-york-must-go/ -- snippet: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 · 紐約NewYork景點推薦-10大必去景點 · 1.中央公園(Central - Park) · 2.第五大道(Fifth Avenue) · 3.大都會 ... - title: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 - url: https://schoolaplus.com/articles-detail.asp?seq=35 -Thought: Now that I have found some attractions in New York, I will summarize the information and create a travel itinerary for the 5-day trip. I will use the Finish tool to provide the final answer. -Action: Finish -Action Input: {"plan details": "Day 1: Depart from Orlando to New York on March 10, 2022. Stay at the Park Central Hotel in Central Park. Visit the Empire State Building and Times Square. Have dinner at Lombardi's Pizza. -Day 2: Visit Central Park, the Metropolitan Museum of Art, and the American Museum of Natural History. Have lunch at Shake Shack and dinner at Le Pain Quotidien. -Day 3: Explore the Brooklyn Bridge, Brooklyn Heights, and DUMBO. Have lunch at Di Fara Pizza and dinner at Peter Luger Steak House. -Day 4: Visit the Statue of Liberty and Ellis Island. Have lunch at The Boil and dinner at Xi'an Famous Foods. -Day 5: Spend the day shopping on Fifth Avenue and visiting the Rockefeller Center. Have lunch at Shake Shack and dinner at Katz's Delicatessen."} -''' - - -def get_default_react_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] - - agent = ReactAgent(llm=llm, tool_list=tool_list, max_context_len=max_context_len) - return agent - - -def get_default_react_agent_fewshot(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] - - agent = ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) - return agent - - -def get_default_toolless_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - agent = ToollessAgent(llm=llm, max_context_len=max_context_len) - return agent - - -def get_default_react_reflect_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - - tool_list = [QueryAccommodations, QueryTransports, QueryGoogleDistanceMatrix, QueryAttractions, Finish] - agent = ReactReflectAgent(reflect_llm=llm, react_llm=llm, example=EXAMPLE, - tool_list=tool_list, max_context_len=max_context_len) - return agent - - -def test_react_agent(): - a = get_default_react_agent_fewshot(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.") - - logger.info(f"5 day trip from Orlando to Paris:{response.answer}") - - -def test_toolless_agent(): - a = get_default_toolless_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", - text="given information") - - logger.info(f"5 day trip from Orlando to Paris:{response.answer}") - - -def test_react_reflect_agent(): - a = get_default_react_reflect_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - response = a.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", - text="given information") - - logger.info(f"5 day trip from Orlando to Paris:{response.answer}") - - -if __name__ == '__main__': - logger.info("react agent test begin") - test_react_agent() - logger.info("react agent test end") - - logger.info("toolless agent test begin") - test_toolless_agent() - logger.info("toolless agent test end") - - logger.info("react reflect agent test begin") - test_react_reflect_agent() - logger.info("react reflect agent test end") - - - - - - - diff --git a/mxAgent/samples/basic_demo/agent_traj_systhesis.py b/mxAgent/samples/basic_demo/agent_traj_systhesis.py deleted file mode 100644 index ced5ad1ae..000000000 --- a/mxAgent/samples/basic_demo/agent_traj_systhesis.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import os -import warnings - -from langchain._api import LangChainDeprecationWarning -from loguru import logger -from tqdm import tqdm - -from agent_sdk.agentchain.react_agent import ReactAgent -from agent_sdk.common.enum_type import AgentRunStatus -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from samples.tools import QueryAttractions, QueryTransports, QueryAccommodations, \ - QueryRestaurants, QueryGoogleDistanceMatrix -from mxAgent.samples.basic_demo.agent_test import EXAMPLE - - -warnings.filterwarnings('ignore') -warnings.filterwarnings('ignore', category=DeprecationWarning) -warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) - -os.environ["WORKNING_DIR"] = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -API_BASE = os.environ.get("OPENAI_API_BASE", "http://10.44.115.98:8006/v1") -API_KEY = os.environ.get("OPENAI_API_KEY", "EMPTY") -LLM_NAME = os.environ.get("MODEL_NAME", "Qwen2-7b-Instruct") - -MAX_CONTEXT_LEN = 4096 - - -def get_default_react_agent(api_base, api_key, llm_name, max_context_len): - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run - tool_list = [QueryAttractions, QueryTransports, QueryAccommodations, QueryRestaurants, QueryGoogleDistanceMatrix] - return ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) - - -if __name__ == '__main__': - agent = get_default_react_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) - - queries = [ - "Book a rental car for two people in Salt Lake City from April 15 to April 18, 2022.", - "Research and list down outdoor activities suitable for adrenaline junkies in Moab \ -between April 12 and 14, 2022.", - "Write a short itinerary for a weekend trip to Nashville, starting on April 15, including live music venues." - ] - - s = AgentRunStatus() - - for query in tqdm(queries): - result = agent.run(query) - s.total_cnt += 1 - if agent.finished: - s.success_cnt += 1 - agent.save_agent_status("./save_instructions.jsonl") - agent.reset() - logger.info("\n") - logger.info("*" * 150) - logger.info(f"Question: {query}") - logger.info("*" * 150) - logger.info(f"Final answer: {result.answer}") - logger.info("*" * 150) - logger.info(f"Trajectory Path: {result.scratchpad}") - logger.info("*" * 150) - - logger.info(f"success rates: {s}") - logger.info(f"Total success rates: {s}") diff --git a/mxAgent/samples/basic_demo/intent_router.py b/mxAgent/samples/basic_demo/intent_router.py deleted file mode 100644 index 3826da5bd..000000000 --- a/mxAgent/samples/basic_demo/intent_router.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from loguru import logger - -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from agent_sdk.agentchain.router_agent import RouterAgent - -llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, - api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run - -INTENT = { - "query_flight": "用户期望查询航班信息", - "query_attraction": "用户期望查询旅游景点信息", - "query_hotel": "用户期望查询酒店和住宿信息", - "plan_attraction": "用户期望给出旅行规划建议", - "whimsical": "异想天开", - "other": "其他不符合上述意图的描述" -} - -querys = [ - "帮我查一下从北京去深圳的机票", - "帮我查一下北京的旅游景点", - "我想去北京旅游", - "去北京旅游可以住在哪里呢,推荐一下", - "帮我去书城买本书", "我想上天" -] - -agent = RouterAgent(llm=llm, intents=INTENT) - -for query in querys: - response = agent.run(query) - agent.reset() - logger.info(f"query: {query}, intent: {response.answer}") diff --git a/mxAgent/samples/basic_demo/traj_generate_test.py b/mxAgent/samples/basic_demo/traj_generate_test.py deleted file mode 100644 index bd4b73048..000000000 --- a/mxAgent/samples/basic_demo/traj_generate_test.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import json -import os -import warnings -from typing import Callable, List -from tqdm import tqdm -from loguru import logger -from langchain._api import LangChainDeprecationWarning - -from agent_sdk.agentchain.base_agent import BaseAgent -from agent_sdk.agentchain.single_action_agent import SingleActionAgent -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from samples.tools import QueryAccommodations, QueryAttractions, QueryRestaurants, \ - QueryTransports, QueryGoogleDistanceMatrix - - -API_BASE = "http://10.44.115.98:8006/v1" -API_KEY = "EMPTY" -MODEL_NAME = "Qwen2-7b-Instruct" - -os.environ["OPENAI_API_BASE"] = API_BASE -os.environ["OPENAI_API_KEY"] = API_KEY -os.environ["MODEL_NAME"] = MODEL_NAME -os.environ["WORKING_DIR"] = os.path.dirname( - os.path.dirname(os.path.realpath(__file__))) - -warnings.filterwarnings('ignore') -warnings.filterwarnings('ignore', category=DeprecationWarning) -warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) - - -class TrajectoryGenerator: - - @staticmethod - def generate(output_path: str, agent: BaseAgent, load_dataset: Callable[[], List[str]], **kwargs): - questions = load_dataset() - for q in tqdm(questions): - try: - agent.run(q, **kwargs) - agent.save_agent_status(output_path) - agent.reset() - - except Exception as err: - logger.warning(f"generate traj failed, query: {q}, agent: {agent.name}, err: {err}") - continue - - @staticmethod - def _check_data_format(data): - if not isinstance(data, list): - raise ValueError("Data should be a list of dict") - - if len(data) == 0: - raise ValueError("Data should not be empty") - - if not isinstance(data[0], dict): - raise ValueError("Data item should be a dict") - - alpaca_format_keys = ["instruction", "input", "output", "status"] - data_keys_set = set(data[0].keys()) - - if not all([key in data_keys_set for key in alpaca_format_keys]): - raise ValueError("need alpaca data format") - - def _load_data_from_file(self, data_path): - if not os.path.exists(data_path): - raise FileNotFoundError(f"File not found: {data_path}") - - if data_path.endswith(".jsonl"): - traj_data = [json.loads(line) for line in open(data_path, "r")] - else: - raise ValueError("Unknown file format") - - self._check_data_format(traj_data) - return traj_data - - -def get_single_action_agent(api_base, api_key, llm_name): - tool_list = [ - QueryAccommodations, QueryAttractions, QueryRestaurants, - QueryTransports, QueryGoogleDistanceMatrix - ] - llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, - api_base, api_key, llm_name).run - return SingleActionAgent(llm=llm, tool_list=tool_list, max_steps=5) - - -if __name__ == '__main__': - single_agent = get_single_action_agent(API_BASE, API_KEY, MODEL_NAME) - queries = [ - "Write a review of the hotel \"The Beach House\" in Charlotte Amalie.", - "Book a flight from Evansville to Sacramento for April 10th.", - "Create a list of top 5 attractions in Hilo for a solo traveler.", - "Compare the prices of hotels in Newark for a 3-night stay.", - "Book a hotel room in Paducah for April 12th.", - "Write a travel blog post about visiting the Golden Gate Bridge in San Francisco.", - "Recommend the best mode of transportation from Flagstaff to Phoenix.", - "Determine the best time to visit the Statue of Liberty.", - "Compare the prices of car rentals in Seattle.", - "What are the top - rated museums in Harrisburg?" - ] - generator = TrajectoryGenerator() - generator.generate(output_path="./save_instructions.jsonl", agent=single_agent, - load_dataset=lambda: queries) diff --git a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py b/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py deleted file mode 100644 index d74e03696..000000000 --- a/mxAgent/samples/travel_agent_demo/front/chat_bot_release.py +++ /dev/null @@ -1,62 +0,0 @@ -import streamlit as st -from samples.travel_agent.travelagent import TravelAgent - -if __name__ == "__main__": - st.set_page_config( - page_title="旅游规划agent", - page_icon="./logo.jpg" - ) - st.logo("logo.jpg") - st.markdown('

旅游规划Agent

', unsafe_allow_html=True) - - - placeholder1 = st.empty() - placeholder2 = st.empty() - placeholder3 = st.empty() - - if "messages" not in st.session_state: - st.session_state.messages = [] - - if "aagent" not in st.session_state: - st.session_state.agent = TravelAgent() - - with placeholder1: - container = st.container(height=300, border=False) - with placeholder2: - _, col1, _ = st.columns([10, 2, 10]) - with col1: - st.image("logo.jpg", use_column_width=True) - with placeholder3: - _, col2, _ = st.columns([1, 20, 1]) - helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

-

例如:从北京到西安旅游规划

-

例如:西安有哪些免费的博物馆景点

-

例如:查一下西安的酒店

""" - with col2: - st.markdown(helloinfo, unsafe_allow_html=True) - - for message in st.session_state.messages: - with st.chat_message(message["role"]): - st.empty() - st.markdown(message["content"]) - - if prompt = st.chat_input("send message"): - st.session_state.messages.append({"role":"user", "content":prompt}) - placeholder1.empty() - placeholder2.empty() - placeholder3.empty() - - agent = st.session_state["agent"] - - with st.chat_message("user"): - st.markdown(prompt) - - with st.chat_message("assistant"): - with st.spinner("thinking..."): - response = agent.run(query=prompt, stream=True) - if isinstance(response, str): - st.markdown(response) - else: - response = st.write_stream(response) - - st.session_state.messages.append({"role":"assistant", "content":response}) diff --git a/mxAgent/samples/travel_agent_demo/travelagent.py b/mxAgent/samples/travel_agent_demo/travelagent.py deleted file mode 100644 index 247402940..000000000 --- a/mxAgent/samples/travel_agent_demo/travelagent.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import enum -from abc import ABC -from loguru import logger - -from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE -from agent_sdk.agentchain.recipe_agent import RecipeAgent -from agent_sdk.agentchain.router_agent import RouterAgent -from agent_sdk.agentchain.tool_less_agent import ToollessAgent -from agent_sdk.agentchain.base_agent import AgentRunResult - -from samples.tools.tool_query_accommodations import QueryAccommodations -from samples.tools.tool_query_transports import QueryTransports -from samples.tools.tool_query_attractions import QueryAttractions -from samples.tools.tool_summary import PlanSummary -from samples.tools.tool_general_query import GeneralQuery -from samples.tools.tool_query_weather import QueryWeather - - -PESUEDE_CODE = """步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息 -步骤2:根据用户的问题,从网络中查询相关的出行交通信息。 -步骤3:根据用户的问题,从网络中搜索相关的住宿和酒店信息; -步骤4:根据用户的问题,查询用户需要的城市天气情况; -步骤5:总结以上的出行信息、景点游玩、住宿信息等。""" -TRANSPORT_INST = """步骤1:根据用户的输入问题,从网络中查询相关的出行交通信息""" -ATTRACTION_INST = "步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息" -HOTEL_INST = """步骤1:根据用户的问题,从网络中搜索相关的住宿和酒店信息,""" -WEATHER_INST = "步骤一:根据用户的问题,查询用户需要的城市天气情况" -OTHER_INST = """步骤1:根据用户的输入,从互联网中查询相关的解答""" - - -GENERAL_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页摘要信息进行总结,并以markdown的格式进行返回, -请添加适当的词语,使得语句内容连贯,通顺 -请将content和snippet的信息进行综合处理,进行总结,生成一个段落。 -涉及到url字段时,使用超链接的格式将网页url链接到网页title上。 -参数介绍】: -title:网页标题 -url:网页链接 -snippet:网页摘要信息 -content:网页的内容总结 -下面是JSON格式的输入: -{text} -请生成markdown段落:""" - -WEATHER_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手, -当前的工作场景是:天气出行建议;输入的内容是JSON格式的用户所查询城市未来的天气预报,请将这些信息总结为的自然语言展示天气预报的信息,并对用户的出游给除建议, -根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 -请添加适当的词语,使得语句内容连贯,通顺,并尽可能保留输入的信息和数据,但不要自行杜撰信息。 -提供的信息以JSON的格式进行展示 -【参数介绍】: -date:日期 -day_weather:白天的天气情况 -day_wind_direction:白天风向 -day_wind_power: 白天风力 -night_weather:夜晚的天气情况 -night_wind_direction:夜晚风向 -night_wind_power: 夜晚风力 -max_degree: 最高温 -min_degree:最低温 -下面是JSON格式的输入: -{text} -请生成markdown段落: -""" -PLANNER_FINAL_PROMPT = """你是一个擅长规划和文字处理的智能助手,你需要将提供的信息按照下面的步骤撰写一份旅游攻略,输出markdown格式的段落, -你可以添加适当的语句,使得段落通顺,但不要自己杜撰信息。 -步骤】 -1. 根据【用户需要旅行的天数】,将输入的景点分配到每一天的行程中,每天2-3个景点,并介绍景点的详细情况 -2. 叙述输入中推荐的住宿情况,详细介绍酒店的详细情况,和预定链接 -3. 叙述输入中查询的交通安排,详细介绍每个出行方案的价格、时间、时长等详细情况,和预定链接 -4. 介绍输入中天气预报的情况,根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 -【参数介绍】: -title:网页标题 -url:网页链接,满足用户需求的酒店筛选结果 -content:网页主要内容提取物 -snippet:网页摘要信息 -输入的信息以JSON格式,下面是的输入: -{text} -请生成markdown段落:""" - - - -TRAVEL_PLAN = "TRAVEL_PLAN" -QUERY_ATTRACTION = "QUERY_ATTRACTION" -QUERY_HOTEL = "QUERY_HOTEL" -QUERY_TRANSPORT = "QUERY_TRANSPORT" -QUERY_WEATHER = "QUERY_WEATHER" -OTHERS = "OTHERS" - -classifer = [TRAVEL_PLAN, QUERY_ATTRACTION, QUERY_HOTEL, QUERY_TRANSPORT, QUERY_WEATHER, OTHERS] - -INST_MAP = { - TRAVEL_PLAN :PESUEDE_CODE, - QUERY_ATTRACTION :ATTRACTION_INST, - QUERY_HOTEL:HOTEL_INST, - QUERY_TRANSPORT :TRANSPORT_INST, - QUERY_WEATHER :WEATHER_INST, - OTHERS:OTHER_INST -} - -FINAL_PMT_MAP = { - TRAVEL_PLAN :PLANNER_FINAL_PROMPT, - QUERY_ATTRACTION :GENERAL_FINAL_PROMPT, - QUERY_HOTEL:GENERAL_FINAL_PROMPT, - QUERY_TRANSPORT :GENERAL_FINAL_PROMPT, - QUERY_WEATHER :WEATHER_FINAL_PROMPT, - OTHERS: GENERAL_FINAL_PROMPT - -} - -TOOL_LIST_MAP = { - TRAVEL_PLAN :[QueryAccommodations, QueryAttractions, QueryTransports, PlanSummary, QueryWeather], - QUERY_ATTRACTION :[QueryAttractions], - QUERY_HOTEL:[QueryAccommodations], - QUERY_TRANSPORT :[QueryTransports], - QUERY_WEATHER : [QueryWeather], - OTHERS:[] -} - -intents = { - TRAVEL_PLAN :"询问旅行规划,问题中要求旅游项目日程安排、交通查询、查询当地住宿等方面的能力", - QUERY_ATTRACTION :"查询旅游项目、景区、旅游活动", - QUERY_HOTEL: "仅查询酒店和住宿信息", - QUERY_TRANSPORT : "与现实中出行、乘坐交通、如高铁、动车、飞机、火车等相关的意图", - QUERY_WEATHER :"包括气温、湿度、降水等与天气、天气预报相关的意图", - OTHERS :"与旅游场景不相干的查询" -} -LLM_MODEL = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, - api_base="http://10.44.115.108:1055/v1", api_key="EMPTY", llm_name="Qwen1.5-32B-Chat").run - - -class TalkShowAgent(ToollessAgent, ABC): - def __init__(self, llm, prompt="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:'\ - 目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐,请利用你的知识回答问题,这是用户的问题:{query}", - **kwargs): - super().__init__(llm, prompt, **kwargs) - self.query = "" - - def _build_agent_prompt(self, **kwargs): - return self.prompt.format( - query=self.query - ) - - -class TravelAgent: - @classmethod - def route_query(cls, query): - router_agent = RouterAgent(llm=LLM_MODEL, intents=intents) - classify = router_agent.run(query).answer - if classify not in classifer or classify == OTHERS: - return TalkShowAgent(llm=LLM_MODEL) - return RecipeAgent(name=classify, - description="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐", - llm=LLM_MODEL, - tool_list=TOOL_LIST_MAP[classify], - recipe=INST_MAP[classify], - max_steps=3, - max_token_number=4096, - final_prompt=FINAL_PMT_MAP[classify]) - - def run(self, query, stream): - agent = self.route_query(query) - return agent.run(query, stream=stream) - -if __name__ == "__main__": - # request = "去北京的旅游规划" - # request = "从北京到西安的机票" - # request = "查询北京王府井附近的高档酒店" - # request = "泰国有哪些值得推荐的景点" - # request = "帮我查一下北京最近的天气" - # request = "上海酒店查询" - # request = "北京到上海的高铁" - # request = "上海天气怎么样" - request = "帮我制定一份从北京到上海6天的旅游计划" - - travel_agent = TravelAgent() - res = travel_agent.run(request, stream=True) - if isinstance(res, AgentRunResult): - logger.info("-----------run agent success-------------") - logger.info(res.answer) - else: - for char in res: - logger.info(char) -- Gitee From 9a3287937c8b943fbd2032bb022eabde7f768f6d Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 20:06:36 +0800 Subject: [PATCH 21/30] master clean code --- mxAgent/samples/tools/tool_summary.py | 71 --------- mxAgent/samples/tools/web_summary_api.py | 176 ----------------------- 2 files changed, 247 deletions(-) delete mode 100644 mxAgent/samples/tools/tool_summary.py delete mode 100644 mxAgent/samples/tools/web_summary_api.py diff --git a/mxAgent/samples/tools/tool_summary.py b/mxAgent/samples/tools/tool_summary.py deleted file mode 100644 index 9d1b706c9..000000000 --- a/mxAgent/samples/tools/tool_summary.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -from agent_sdk.toolmngt.api import API -from agent_sdk.toolmngt.tool_manager import ToolManager - - -@ToolManager.register_tool() -class PlanSummary(API): - name = "PlanSummary" - description = "this api uesed to summary all the travel plan." - input_parameters = { - 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, - 'accomadation': {'type': 'str', 'description': "the accomodation information"}, - 'transport': {'type': 'str', 'description': "the transport information"}, - 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, - 'duration': {'type': 'str', 'description': "The days of travel"}, - } - - output_parameters = { - 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, - } - - example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ - accomadation,and transport information" - example = ( - """ - { - "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ - Borough High Street in Southwark and King William Street.", - "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ - culture, shopping, and green spaces", - "transport": "10 hours from Beijing to London cost $1000.", - } - """) - - def __init__(self): - pass - - def format_tool_input_parameters(self, llm_output) -> dict: - return llm_output if llm_output else {} - - def check_api_call_correctness(self, response, groundtruth=None) -> bool: - if response['exception'] is None: - return True - else: - return False - - def call(self, input_parameters, **kwargs): - # 总的只能输入2500个字左右 - attraction = input_parameters.get('attractions') - hotel = input_parameters.get('accomadation') - transport = input_parameters.get('transport') - weather = input_parameters.get("weather") - duration = input_parameters.get("duration") - - res = "" - if duration is not None: - res += f"【用户需要旅行的天数】:{duration}天\n" - if attraction is not None: - res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" - if hotel is not None: - res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" - if transport is not None: - res = res + f"【交通安排】:\n{str(transport)[:500]}\n" - if weather is not None: - res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" - summary = { - "summary": res - } - return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/samples/tools/web_summary_api.py b/mxAgent/samples/tools/web_summary_api.py deleted file mode 100644 index feeb4bb5b..000000000 --- a/mxAgent/samples/tools/web_summary_api.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - -import asyncio -import os -import re -import time -from concurrent.futures import ThreadPoolExecutor, wait, as_completed - -import aiohttp -import requests -import tiktoken -import urllib3 -from bs4 import BeautifulSoup -from loguru import logger -from samples.tools.google_search_api import google_search - - -def check_number_input(num, crow): - if not num.isdigit(): - return False - num = int(num) - if num > crow: - return False - return True - - -async def bai_du(url): - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" - } - async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, - headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: - async with session.get(url) as response: - res = await response.text() - return res - - -class WebSummary: - encoder = tiktoken.get_encoding("gpt2") - - @classmethod - def get_detail_copy(cls, url, summary_prompt): - os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 - urllib3.disable_warnings() - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" - } - try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = requests.get( - url, headers=headers, timeout=(3, 3), stream=True) - mommt = time.time() - logger.info(f"finish request website: {mommt},{url}") - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - return '', f"获取网页{url}内容失败" - - content = response.content - bsobj = BeautifulSoup(content, 'html.parser') - txt = bsobj.get_text() - text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') - text = re.sub(r'\n{2,}', '\n', text) - except Exception as e: - logger.error(e) - return '', e - res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") - return res, "" - - @classmethod - async def get_details(cls, url, summary_prompt): - os.environ['CURL_CA_BUNDLE'] = '' - urllib3.disable_warnings() - try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = await bai_du(url) - mommt = time.time() - logger.debug(f"finish request website: {mommt},{url}") - content = response - bsobj = BeautifulSoup(content, 'html.parser') - txt = bsobj.get_text() - text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') - text = re.sub(r'\n{2,}', '\n', text) - if 'PleaseenableJSanddisableanyadblocker' in text: - text = "" - except Exception as e: - logger.error(e) - return '', e - if len(text) == 0: - return "", "no valid website content" - res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") - return res, "" - - @classmethod - def summary_call(cls, web, max_summary_number, summary_prompt): - title = web.get("title", "") - url = web.get("url") - snippet = web.get("snippet", "") - web_summary = {} - if url is None: - return web_summary - - web_summary['title'] = title - web_summary['url'] = url - try: - content = asyncio.run(cls.get_details(url, summary_prompt)) - except Exception as e: - logger.error(e) - if not isinstance(content, str) or len(content) == 0: - web_summary['snippet'] = snippet - else: - web_summary['content'] = content - - return web_summary - - @classmethod - def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): - logger.add('app.log', level='DEBUG') - cls.llm = llm - try: - mommt = time.time() - logger.debug(f"start google search: {mommt}") - if isinstance(keys, list): - keys = ",".join(keys) - search_result = google_search(keys, search_num) - mommt = time.time() - logger.debug(f"finish google search: {mommt}") - except Exception as e: - logger.error(e) - return [] - - max_summary_number = summary_num - - webs = [] - with ThreadPoolExecutor(max_workers=3) as executor: - futures = [] - for web in search_result: - thread = executor.submit( - cls.summary_call, web, max_summary_number, summary_prompt) - futures.append(thread) - for future in as_completed(futures): - webs.append(future.result()) - wait(futures) - return webs - - @classmethod - def build_summary_prompt(cls, query, prompt): - max_input_token_num = 4096 - if len(query) == 0: - return prompt.format(text=query) - input_token_len = len(WebSummary.encoder.encode(query)) - prompt_len = len(WebSummary.encoder.encode(prompt)) - clip_text_index = int( - len(query) * (max_input_token_num - prompt_len) / input_token_len) - clip_text = query[:clip_text_index] - return prompt.format(input=clip_text) - - @classmethod - def generate_content(cls, query, prompt): - max_tokens = 1000 - try: - pmt = WebSummary.build_summary_prompt(query, prompt) - output = cls.llm(prompt=pmt, max_tokens=max_tokens) - except Exception as e: - logger.error(e) - return e - return output -- Gitee From cd3200bdb4804f8c5a43b0f98bc77a76b7aa0669 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 20:09:24 +0800 Subject: [PATCH 22/30] Delete tool_query_weather.py --- mxAgent/samples/tools/tool_query_weather.py | 185 -------------------- 1 file changed, 185 deletions(-) delete mode 100644 mxAgent/samples/tools/tool_query_weather.py diff --git a/mxAgent/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py deleted file mode 100644 index 7fe739bc3..000000000 --- a/mxAgent/samples/tools/tool_query_weather.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - -import datetime -import json -import json -import os -from zoneinfo import ZoneInfo - -import requests -import urllib3 -from agent_sdk.toolmngt.api import API -from agent_sdk.toolmngt.tool_manager import ToolManager -from loguru import logger - -AMAP_API_KEY = "75bcb2edf5800884a31172dd0d970369" -WEEK_MAP = { - 0: "Monday", - 1: "Tuesday", - 2: "Wednesday", - 3: "Thursday", - 4: "Friday", - 5: "Saturday", - 6: "Sunday" -} -REQUEST_HEADERS = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ - Chrome/126.0.0.0 Safari/537.36" -} - - -@ToolManager.register_tool() -class QueryWeather(API): - name = "QueryWeather" - description = "This API is used to query weather forecast from the network according to the user's input question," - weekday = WEEK_MAP.get(datetime.datetime.now(ZoneInfo("Asia/Shanghai")).weekday(), '') - input_parameters = { - - 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, - "province": {'type': 'str', 'description': 'The province corresponding to the city'}, - "date": {'type': 'str', - 'description': ("The date of the user want to query, today is" + - f"{datetime.date.today()}, and today is {weekday}, " + - "please reason the date from user's query, and format with YYYY-MM-DD,") - }, - 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, - } - output_parameters = { - "forecast": {'type': 'str', - 'description': 'the weather forecast information'}, - } - - example = ( - """ - { - "destination_city": "ShenZhen", - "province": "GuangDong", - "date": "2022-10-01" - } - """) - - def __init__(self, ): - os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 - urllib3.disable_warnings() - - def check_api_call_correctness(self, response, groundtruth=None) -> bool: - ex = response.exception - if ex is not None: - return False - else: - return True - - def get_forecast(self, url, param, city=""): - headers = REQUEST_HEADERS - response = requests.get(url, params=param, headers=headers, timeout=5) - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - raise Exception(f"获取网页{url}内容失败") - content = response.content - text = json.loads(content) - return text.get("data") - - def get_city2province(self, url, city): - headers = REQUEST_HEADERS - params = { - "city": city, - "source": "pc" - } - response = requests.get(url, params=params, headers=headers, timeout=5) - if response.status_code != 200: - logger.error(f"获取网页{url}内容失败") - raise Exception(f"获取网页{url}内容失败") - content = response.content - text = json.loads(content) - return text.get("data") - - def format_weather(self, weekly_weather): - # 精简输入 - key_keeps = [ - 'day_weather', 'day_wind_direction', 'day_wind_power', - 'max_degree', 'min_degree', 'night_weather', 'night_wind_direction', 'night_wind_power' - ] - summary_copy = [] - for key, info in weekly_weather.items(): - time = info.get('time', key) - daily = {} - if isinstance(info, dict): - info_keeps = {k: info[k] for k in key_keeps if k in info} - daily[time] = info_keeps - summary_copy.append(daily) - return summary_copy - - def format_request_param(self, data, weather_type): - for key, value in data.items(): - city2province = value.replace(" ", "").split(",") - data[key] = city2province - # 遇到城市同名,认为是市的概率大于县 - _, max_probablity = min(data.items(), key=lambda item: len(item[1])) - if len(max_probablity) >= 2: - province = max_probablity[0] - city = max_probablity[1] - country = max_probablity[2] if len(max_probablity) >= 3 else "" - params = { - "source": "pc", # 请求来源,可以填 pc 即来自PC端 - "province": province, # 省, - "city": city, # 市, - "country": country, # 县区 - "weather_type": weather_type - } - return params - - def call(self, input_parameter, **kwargs): - des = input_parameter.get('destination_city') - departure_date = input_parameter.get("date") - weather_type = "forecast_24h" - - try: - if des is None: - return self.make_response(input_parameter, results="", success=False, exception="") - try: - data = self.get_city2province("https://wis.qq.com/city/like", des) - except Exception as e: - e = str(e) - return self.make_response(input_parameter, results=e, success=False, exception=e) - if len(data) == 0: - return self.make_response(input_parameter, - results="未能找到所查询城市所在的省份或市", success=False, exception="") - - params = self.format_request_param(data, weather_type) - try: - forecast = self.get_forecast( - "https://wis.qq.com/weather/common", params) - except Exception as e: - e = str(e) - return self.make_response(input_parameter, results=e, success=False, exception=e) - weekly_weather = forecast.get(weather_type) - summary_copy = self.format_weather(weekly_weather) - if departure_date is None: - res = { - 'forecast': summary_copy - } - return self.make_response(input_parameter, results=res, exception="") - - try: - formated_departure = datetime.datetime.strptime( - departure_date, "%Y-%m-%d").date() - except ValueError as e: - logger.warning(e) - formated_departure = datetime.date.today() - gaps = (formated_departure - datetime.date.today()).days - weather_summary = summary_copy[gaps + 1:] - - if len(weather_summary) == 0: - weather_summary = "**抱歉,我最多只能查询最近7天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ - json.dumps(summary_copy, ensure_ascii=False) - res = { - 'forecast': weather_summary - } - 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="") -- Gitee From e2e418c2e4cce2ffe036f482f872bf1a2ac100ba Mon Sep 17 00:00:00 2001 From: huxinjia Date: Thu, 21 Nov 2024 20:12:37 +0800 Subject: [PATCH 23/30] delete readme --- mxAgent/README.md | 38 ----------------------------------- mxAgent/agent_sdk/llms/llm.py | 4 ++-- 2 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 mxAgent/README.md diff --git a/mxAgent/README.md b/mxAgent/README.md deleted file mode 100644 index a5f081ebe..000000000 --- a/mxAgent/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# mxAgent: 基于工具调用的多模式LLM Agent框架 -**mxAgent**是一个基于LLMs的通用Agent框架,应用多种框架解决不同场景和复杂度的问题,并通过工具调用的方式允许LLMs与外部源进行交互来获取信息,使LLMs生成更加可靠和实际。mxAgent通过构建DAG(Directed Acyclic Graph)的方式建立工具之间的依赖关系,通过并行执行的方式,提高多工具执行的效率,缩短Agent在复杂场景的执行时间。mxAgent框架还在框架级别支持流式输出 -提供一套Agent实现框架,让用户可以通过框架搭建自己的Agent应用 -## Router Agent -提供意图识别的能力,用户可预设意图的分类,通过Router Agent给出具体问题的分类结果,用于设别不同的问题场景。 -## Recipe Agent -设置复杂问题执行的workflow,在解决具体问题时,将workflow翻译成有向无环图的的节点编排,通过并行的方式执行节点。 -适用于有相对固定workflow的复杂问题场景。 -1)通过自然语言描述复杂问题的workflow, -2)workflow中每一个步骤对应一次工具使用,并描述步骤间关系 -3)recipe Agent将按照workflow的指导完成工具调用 -4)使用模型总结工作流结果,解决复杂问题 -Recipe Agent利用用户所提供的流程指导和工具,使用LLMs生成SOP,并构建DAG图描述Steps之间的依赖关系。agent识别那些可并行的step,通过并行执行提高agent的执行效率。 -使用Recipe Agent,仅需要提供一段解决问题的SOP指导、用于提示最终答案生成的final prompt、解决问题可能使用的工具。 -示例见[travelagent.py](./travel_agent/travelagent.py)运行方式如下: -``` -cd mxAgent -export PYTHONPATH=. -python samples/travel_agent/travelagent.py -``` - -## ReAct Agent -使用Thought、Action、Action Input、Observation的循环流程,解决复杂问题: -1)ReAct通过大模型思考并给出下一步的工具调用, -2)执行工具调用,得到工具执行结果 -3)将工具执行结果应用于下一次的模型思考 -4)循环上述过程,直到模型认为问题得到解决 -## Single Action Agent - -通过模型反思、调用工具执行,总结工具结果的执行轨迹,完成一次复杂问题的处理。Single Action Agent使用一次工具调用帮助完成复杂问题解决 -使用示例: -``` -cd mxAgent -export PYTHONPATH=. -python samples/traj_generate_test.py -``` - -## \ No newline at end of file diff --git a/mxAgent/agent_sdk/llms/llm.py b/mxAgent/agent_sdk/llms/llm.py index a8d5da6e8..953f1c47e 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 -- Gitee From d1eccd61a17ab6d0b9518b3d5c7b27a09aaac575 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Mon, 25 Nov 2024 18:53:38 +0800 Subject: [PATCH 24/30] fix code review --- mxAgent/samples/tools/common.py | 30 ++++++++++ mxAgent/samples/tools/duck_search.py | 55 ++++------------- mxAgent/samples/tools/tool_cost_enquiry.py | 11 +--- mxAgent/samples/tools/tool_finish.py | 8 --- mxAgent/samples/tools/tool_general_query.py | 26 +------- .../tools/tool_query_accommodations.py | 54 +++-------------- .../samples/tools/tool_query_attractions.py | 60 ++++--------------- mxAgent/samples/tools/tool_query_city.py | 6 -- .../tools/tool_query_distance_matrix.py | 6 -- .../samples/tools/tool_query_restaurants.py | 3 - .../samples/tools/tool_query_transports.py | 55 +++++------------ 11 files changed, 80 insertions(+), 234 deletions(-) create mode 100644 mxAgent/samples/tools/common.py diff --git a/mxAgent/samples/tools/common.py b/mxAgent/samples/tools/common.py new file mode 100644 index 000000000..63c90e010 --- /dev/null +++ b/mxAgent/samples/tools/common.py @@ -0,0 +1,30 @@ +import json +from samples.tools.web_summary_api import WebSummary + +def get_website_summary(keys, prompt, llm): + 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 isinstance(val, list): + it = flatten(val) + filtered.append(it) + filtered.append(val) + + if len(filtered) == 0: + raise Exception("keywords has no been found") + + webs = WebSummary.web_summary( + filtered, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) + return json.dumps(webs, ensure_ascii=False) + +def flatten(nested_list): + """递归地扁平化列表""" + for item in nested_list: + if isinstance(item, list): + return flatten(item) + else: + return item \ No newline at end of file diff --git a/mxAgent/samples/tools/duck_search.py b/mxAgent/samples/tools/duck_search.py index 4a5782d66..3f5337074 100644 --- a/mxAgent/samples/tools/duck_search.py +++ b/mxAgent/samples/tools/duck_search.py @@ -1,5 +1,6 @@ import json from typing import List +import re from langchain_community.tools import DuckDuckGoSearchResults from langchain_community.utilities import DuckDuckGoSearchAPIWrapper @@ -30,40 +31,9 @@ class DuckDuckGoSearch(API): 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', "") + def call(self, input_parameter: dict, **kwargs) -> dict: + logger.debug(f"{input_parameter}") + query = input_parameter.get('query', "") try: responses = self.call_duck_duck_go_search(query=query, count=4) @@ -73,15 +43,12 @@ class DuckDuckGoSearch(API): for r in responses: output += self.format_step(r) else: - output = "Bing search error" + output = "duck duck search error" + return self.make_response(input_parameter, results=responses, exception=output) + return self.make_response(input_parameter, results=responses, exception="") 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} + return self.make_response(input_parameter, results="", exception=exception) def format_result(self, res): snippet_idx = res.find("snippet:") @@ -101,8 +68,10 @@ class DuckDuckGoSearch(API): temp = search.run(query) logger.debug(temp) - for x in temp.split("["): - snippet = x.split("]")[0].strip() + snippets = re.findall(r'\[(.*?)\]', temp) + snippets = [snippet.strip() for snippet in snippets] + + for snippet in snippets: if len(snippet) == 0: continue logger.debug(f"snippet is {snippet}") diff --git a/mxAgent/samples/tools/tool_cost_enquiry.py b/mxAgent/samples/tools/tool_cost_enquiry.py index a2ae34c5a..ebe9dbe1c 100644 --- a/mxAgent/samples/tools/tool_cost_enquiry.py +++ b/mxAgent/samples/tools/tool_cost_enquiry.py @@ -3,6 +3,7 @@ from typing import Union +import json from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager @@ -33,14 +34,6 @@ class CostEnquiry(API): 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") @@ -49,7 +42,7 @@ class CostEnquiry(API): raise Exception("react_env is missing") try: - input_arg = eval(action_arg) + input_arg = json.loads(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 ' diff --git a/mxAgent/samples/tools/tool_finish.py b/mxAgent/samples/tools/tool_finish.py index 9ee7aafd7..7b00fae6a 100644 --- a/mxAgent/samples/tools/tool_finish.py +++ b/mxAgent/samples/tools/tool_finish.py @@ -36,14 +36,6 @@ class Finish(API): 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 index 312d6e7cf..ceffdde3f 100644 --- a/mxAgent/samples/tools/tool_general_query.py +++ b/mxAgent/samples/tools/tool_general_query.py @@ -33,13 +33,6 @@ class GeneralQuery(API): 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: @@ -53,27 +46,12 @@ class GeneralQuery(API): """ 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) + content = json.dumps(webs, ensure_ascii=False) res = { 'reply': content } - + return self.make_response(input_parameter, results=res, exception=None) 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 index c0bba1103..3c80febeb 100644 --- a/mxAgent/samples/tools/tool_query_accommodations.py +++ b/mxAgent/samples/tools/tool_query_accommodations.py @@ -4,10 +4,11 @@ import json import tiktoken +from loguru import logger + 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 +from samples.tools.common import get_website_summary @ToolManager.register_tool() @@ -38,33 +39,14 @@ class QueryAccommodations(API): 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("住宿") + keys = [destination, position, rank, "住宿"] + logger.debug(f"search accommodation key words: {','.join(keys)}") + prompt = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页信息进行总结,并以精简的文本的形式进行返回, 请添加适当的词语,使得语句内容连贯,通顺。提供的信息是为用户推荐的酒店的网页数据, 请总结网页信息,要求从以下几个方面考虑: @@ -77,27 +59,9 @@ class QueryAccommodations(API): 请生成总结: """ try: - webs = WebSummary.web_summary( - keys=keyword, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) + content = get_website_summary(keys, prompt, llm) + res = {"accommodation": content} + return self.make_response(input_parameter, results=res, exception="") 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 index 7bb076e80..de83491c9 100644 --- a/mxAgent/samples/tools/tool_query_attractions.py +++ b/mxAgent/samples/tools/tool_query_attractions.py @@ -3,10 +3,11 @@ import tiktoken import yaml +from loguru import logger + 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 +from samples.tools.common import get_website_summary @ToolManager.register_tool() @@ -43,37 +44,14 @@ class QueryAttractions(API): 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)}") + keys = [destination, scene, scene_type, requirement, "景点"] summary_prompt = """你是一个擅长于网页信息总结的智能助手,提供的网页是关于旅游规划的信息,现在已经从网页中获取到了相关的文字内容信息,你需要从网页中找到与**景区**介绍相关的内容,并进行提取, 你务必保证提取的内容都来自所提供的文本,保证结果的客观性,真实性。 网页中可能包含多个景点的介绍,你需要以YAML文件的格式返回,每个景点的返回的参数和格式如下: @@ -89,26 +67,10 @@ class QueryAttractions(API): {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 + try: + content = get_website_summary(keys, summary_prompt, llm) + res = {'attractions': content} + return self.make_response(input_parameter, results=res, exception="") + except Exception as e: + logger.error(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) diff --git a/mxAgent/samples/tools/tool_query_city.py b/mxAgent/samples/tools/tool_query_city.py index 4e1c90a03..bc20932dd 100644 --- a/mxAgent/samples/tools/tool_query_city.py +++ b/mxAgent/samples/tools/tool_query_city.py @@ -69,12 +69,6 @@ class CitySearch(API): 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', '') diff --git a/mxAgent/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py index f6a10c4e1..c2ca449b1 100644 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -52,12 +52,6 @@ class QueryGoogleDistanceMatrix(API): 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', "") diff --git a/mxAgent/samples/tools/tool_query_restaurants.py b/mxAgent/samples/tools/tool_query_restaurants.py index caed5ed7a..66f83c96c 100644 --- a/mxAgent/samples/tools/tool_query_restaurants.py +++ b/mxAgent/samples/tools/tool_query_restaurants.py @@ -35,6 +35,3 @@ class QueryRestaurants(API): 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 index b48f03585..74bb5cc12 100644 --- a/mxAgent/samples/tools/tool_query_transports.py +++ b/mxAgent/samples/tools/tool_query_transports.py @@ -3,12 +3,12 @@ import json - +from loguru import logger 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 +from samples.tools.common import get_website_summary @ToolManager.register_tool() @@ -41,13 +41,6 @@ class QueryTransports(API): 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') @@ -57,40 +50,20 @@ class QueryTransports(API): 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)}") + keys = [prefix, req, travel_mode, "购票"] 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 - } + 请添加适当的词语,使得语句内容连贯,通顺。输入是为用户查询的航班、高铁等交通数据,请将这些信息总结 + 请总结网页信息,要求从以下几个方面考虑: + 总结出航班或者高铁的价格区间、需要时长区间、并给出2-3例子,介绍车次、时间、时长、价格等 + 下面是网页的输入: + {input} + 请生成总结: + """ + content = get_website_summary(keys, prompt, llm) + res = {'transport': content} + return self.make_response(input_parameter, results=res, exception="") 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="") -- Gitee From aa90a75353e702ef2efa639e7261be20844b9311 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Tue, 26 Nov 2024 16:31:39 +0800 Subject: [PATCH 25/30] add tools --- mxAgent/samples/tools/duck_search.py | 56 +++--- mxAgent/samples/tools/tool_query_weather.py | 178 ++++++++++++++++++++ mxAgent/samples/tools/tool_summary.py | 66 ++++++++ mxAgent/samples/tools/web_summary_api.py | 176 +++++++++++++++++++ 4 files changed, 448 insertions(+), 28 deletions(-) create mode 100644 mxAgent/samples/tools/tool_query_weather.py create mode 100644 mxAgent/samples/tools/tool_summary.py create mode 100644 mxAgent/samples/tools/web_summary_api.py diff --git a/mxAgent/samples/tools/duck_search.py b/mxAgent/samples/tools/duck_search.py index 3f5337074..6e5f422c3 100644 --- a/mxAgent/samples/tools/duck_search.py +++ b/mxAgent/samples/tools/duck_search.py @@ -36,7 +36,7 @@ class DuckDuckGoSearch(API): query = input_parameter.get('query', "") try: - responses = self.call_duck_duck_go_search(query=query, count=4) + responses = call_duck_duck_go_search(query=query, count=4) logger.debug(f"responses is {responses}") output = "" if len(responses) > 0: @@ -50,34 +50,34 @@ class DuckDuckGoSearch(API): exception = str(e) return self.make_response(input_parameter, results="", exception=exception) - 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 format_result(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) +def call_duck_duck_go_search(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) + bingsearch_results = [] + temp = search.run(query) + logger.debug(temp) - snippets = re.findall(r'\[(.*?)\]', temp) - snippets = [snippet.strip() for snippet in snippets] + snippets = re.findall(r'\[(.*?)\]', temp) + snippets = [snippet.strip() for snippet in snippets] - for snippet in snippets: - 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' + for snippet in snippets: + if len(snippet) == 0: + continue + logger.debug(f"snippet is {snippet}") + bingsearch_results.append(format_result(snippet)) + logger.success(f"{json.dumps(bingsearch_results, indent=4)}") + except Exception as e: + scratchpad += f'Search error {str(e)}, please try again' - return [x['snippet'] for x in self.bingsearch_results] + return [x['snippet'] for x in bingsearch_results] diff --git a/mxAgent/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py new file mode 100644 index 000000000..fc6d37f4e --- /dev/null +++ b/mxAgent/samples/tools/tool_query_weather.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import datetime +import json +import json +import os +from zoneinfo import ZoneInfo + +import requests +import urllib3 +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager +from loguru import logger + +AMAP_API_KEY = "75bcb2edf5800884a31172dd0d970369" +WEEK_MAP = { + 0: "Monday", + 1: "Tuesday", + 2: "Wednesday", + 3: "Thursday", + 4: "Friday", + 5: "Saturday", + 6: "Sunday" +} +REQUEST_HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" +} + + +@ToolManager.register_tool() +class QueryWeather(API): + name = "QueryWeather" + description = "This API is used to query weather forecast from the network according to the user's input question," + weekday = WEEK_MAP.get(datetime.datetime.now(ZoneInfo("Asia/Shanghai")).weekday(), '') + input_parameters = { + + 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, + "province": {'type': 'str', 'description': 'The province corresponding to the city'}, + "date": {'type': 'str', + 'description':("The date of the user want to query, today is"+ + f"{datetime.date.today()}, and today is {weekday}, "+ + "please reason the date from user's query, and format with YYYY-MM-DD,") + }, + 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, + } + output_parameters = { + "forecast": {'type': 'str', + 'description': 'the weather forecast information'}, + } + + example = ( + """ + { + "destination_city": "ShenZhen", + "province": "GuangDong", + "date": "2022-10-01" + } + """) + + def __init__(self, ): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + + def get_forecast(self, url, param, city=""): + headers = REQUEST_HEADERS + response = requests.get(url, params=param, headers=headers, timeout=5) + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + raise Exception(f"获取网页{url}内容失败") + content = response.content + text = json.loads(content) + return text.get("data") + + def get_city2province(self, url, city): + headers = REQUEST_HEADERS + params = { + "city": city, + "source": "pc" + } + response = requests.get(url, params=params, headers=headers, timeout=5) + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + raise Exception(f"获取网页{url}内容失败") + content = response.content + text = json.loads(content) + return text.get("data") + + def format_weather(self, weekly_weather): + # 精简输入 + key_keeps = [ + 'day_weather', 'day_wind_direction', 'day_wind_power', + 'max_degree', 'min_degree', 'night_weather', 'night_wind_direction', 'night_wind_power' + ] + summary_copy = [] + for key, info in weekly_weather.items(): + time = info.get('time', key) + daily = {} + if isinstance(info, dict): + info_keeps = {k: info[k] for k in key_keeps if k in info} + daily[time] = info_keeps + summary_copy.append(daily) + return summary_copy + + def format_request_param(self, data, weather_type): + for key, value in data.items(): + city2province = value.replace(" ", "").split(",") + data[key] = city2province + # 遇到城市同名,认为是市的概率大于县 + _, max_probablity = min(data.items(), key=lambda item: len(item[1])) + if len(max_probablity) >= 2: + province = max_probablity[0] + city = max_probablity[1] + country = max_probablity[2] if len(max_probablity) >= 3 else "" + params = { + "source": "pc", # 请求来源,可以填 pc 即来自PC端 + "province": province, # 省, + "city": city, # 市, + "country": country, # 县区 + "weather_type": weather_type + } + return params + + def call(self, input_parameter, **kwargs): + des = input_parameter.get('destination_city') + departure_date = input_parameter.get("date") + weather_type = "forecast_24h" + + try: + if des is None: + return self.make_response(input_parameter, results="", success=False, exception="") + try: + data = self.get_city2province("https://wis.qq.com/city/like", des) + except Exception as e: + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + if len(data) == 0: + return self.make_response(input_parameter, + results="未能找到所查询城市所在的省份或市", success=False, exception="") + + params = self.format_request_param(data, weather_type) + try: + forecast = self.get_forecast( + "https://wis.qq.com/weather/common", params) + except Exception as e: + e = str(e) + return self.make_response(input_parameter, results=e, success=False, exception=e) + weekly_weather = forecast.get(weather_type) + summary_copy = self.format_weather(weekly_weather) + if departure_date is None: + res = { + 'forecast': summary_copy + } + return self.make_response(input_parameter, results=res, exception="") + + try: + formated_departure = datetime.datetime.strptime( + departure_date, "%Y-%m-%d").date() + except ValueError as e: + logger.warning(e) + formated_departure = datetime.date.today() + gaps = (formated_departure - datetime.date.today()).days + weather_summary = summary_copy[gaps + 1:] + + if len(weather_summary) == 0: + weather_summary = "**抱歉,我最多只能查询最近7天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ + json.dumps(summary_copy, ensure_ascii=False) + res = { + 'forecast': weather_summary + } + 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="") diff --git a/mxAgent/samples/tools/tool_summary.py b/mxAgent/samples/tools/tool_summary.py new file mode 100644 index 000000000..6a6469fdd --- /dev/null +++ b/mxAgent/samples/tools/tool_summary.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager + + +@ToolManager.register_tool() +class PlanSummary(API): + name = "PlanSummary" + description = "this api uesed to summary all the travel plan." + input_parameters = { + 'attractions': {'type': 'str', 'description': "the planned arrangement of attraction."}, + 'accomadation': {'type': 'str', 'description': "the accomodation information"}, + 'transport': {'type': 'str', 'description': "the transport information"}, + 'weather': {'type': 'str', 'description': "Weather information for the next few days"}, + 'duration': {'type': 'str', 'description': "The days of travel"}, + } + + output_parameters = { + 'summary': {'type': 'str', 'description': 'Summary all the plan of this travel'}, + } + + example = "PlanSummary[attractions,hotel,flight] will summary all the plan of travel inculed attractions,'\ + accomadation,and transport information" + example = ( + """ + { + "attractions": "London Bridge, any of several successive structures spanning the River Thames between '\ + Borough High Street in Southwark and King William Street.", + "accomadation": "Park Plaza London Riverbank In the heart of London, with great transport connections, '\ + culture, shopping, and green spaces", + "transport": "10 hours from Beijing to London cost $1000.", + } + """) + + def __init__(self): + pass + + def format_tool_input_parameters(self, llm_output) -> dict: + return llm_output if llm_output else {} + + + def call(self, input_parameters, **kwargs): + # 总的只能输入2500个字左右 + attraction = input_parameters.get('attractions') + hotel = input_parameters.get('accomadation') + transport = input_parameters.get('transport') + weather = input_parameters.get("weather") + duration = input_parameters.get("duration") + + res = "" + if duration is not None: + res += f"【用户需要旅行的天数】:{duration}天\n" + if attraction is not None: + res = res + f"【景点汇总】:\n{str(attraction)[:1000]}\n" + if hotel is not None: + res = res + f"【住宿安排】:\n{str(hotel)[:500]}\n" + if transport is not None: + res = res + f"【交通安排】:\n{str(transport)[:500]}\n" + if weather is not None: + res = res + f"【未来几天的天气情况】:\n{str(weather)[:500]}\n" + summary = { + "summary": res + } + return self.make_response(input_parameters, results=summary, exception="") \ No newline at end of file diff --git a/mxAgent/samples/tools/web_summary_api.py b/mxAgent/samples/tools/web_summary_api.py new file mode 100644 index 000000000..dfb38dd37 --- /dev/null +++ b/mxAgent/samples/tools/web_summary_api.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import asyncio +import os +import re +import time +from concurrent.futures import ThreadPoolExecutor, wait, as_completed + +import aiohttp +import requests +import tiktoken +import urllib3 +from bs4 import BeautifulSoup +from loguru import logger +from samples.tools.duck_search import call_duck_duck_go_search + + +def check_number_input(num, crow): + if not num.isdigit(): + return False + num = int(num) + if num > crow: + return False + return True + + +async def bai_du(url): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False, limit=25), trust_env=True, + headers=headers, timeout=aiohttp.ClientTimeout(total=5)) as session: + async with session.get(url) as response: + res = await response.text() + return res + + +class WebSummary: + encoder = tiktoken.get_encoding("gpt2") + + @classmethod + def get_detail_copy(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 + urllib3.disable_warnings() + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ + Chrome/126.0.0.0 Safari/537.36" + } + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = requests.get( + url, headers=headers, timeout=(3, 3), stream=True) + mommt = time.time() + logger.info(f"finish request website: {mommt},{url}") + if response.status_code != 200: + logger.error(f"获取网页{url}内容失败") + return '', f"获取网页{url}内容失败" + + content = response.content + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + except Exception as e: + logger.error(e) + return '', e + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, None + + @classmethod + async def get_details(cls, url, summary_prompt): + os.environ['CURL_CA_BUNDLE'] = '' + urllib3.disable_warnings() + try: + mommt = time.time() + logger.info(f"start request website: {mommt},{url}") + response = await bai_du(url) + mommt = time.time() + logger.debug(f"finish request website: {mommt},{url}") + content = response + bsobj = BeautifulSoup(content, 'html.parser') + txt = bsobj.get_text() + text = re.sub(r'\n{2,}', '\n', txt).replace(' ', '') + text = re.sub(r'\n{2,}', '\n', text) + if 'PleaseenableJSanddisableanyadblocker' in text: + text = "" + except Exception as e: + logger.error(e) + return '', e + if len(text) == 0: + return "", "no valid website content" + res = cls.generate_content(text, summary_prompt) + mommt = time.time() + logger.info(f"finish summary website: {mommt},{url}") + return res, None + + @classmethod + def summary_call(cls, web, max_summary_number, summary_prompt): + title = web.get("title", "") + url = web.get("url") + snippet = web.get("snippet", "") + web_summary = {} + if url is None: + return web_summary + + web_summary['title'] = title + web_summary['url'] = url + try: + content = asyncio.run(cls.get_details(url, summary_prompt)) + except Exception as e: + logger.error(e) + if not isinstance(content, str) or len(content) == 0: + web_summary['snippet'] = snippet + else: + web_summary['content'] = content + + return web_summary + + @classmethod + def web_summary(cls, keys, search_num, summary_num, summary_prompt, llm): + logger.add('app.log', level='DEBUG') + cls.llm = llm + try: + mommt = time.time() + logger.debug(f"start google search: {mommt}") + if isinstance(keys, list): + keys = ",".join(keys) + search_result = call_duck_duck_go_search(keys, search_num) + mommt = time.time() + logger.debug(f"finish google search: {mommt}") + except Exception as e: + logger.error(e) + return [] + + max_summary_number = summary_num + + webs = [] + with ThreadPoolExecutor(max_workers=3) as executor: + futures = [] + for web in search_result: + thread = executor.submit( + cls.summary_call, web, max_summary_number, summary_prompt) + futures.append(thread) + for future in as_completed(futures): + webs.append(future.result()) + wait(futures) + return webs + + @classmethod + def build_summary_prompt(cls, query, prompt): + max_input_token_num = 4096 + if len(query) == 0: + return prompt.format(text=query) + input_token_len = len(WebSummary.encoder.encode(query)) + prompt_len = len(WebSummary.encoder.encode(prompt)) + clip_text_index = int( + len(query) * (max_input_token_num - prompt_len) / input_token_len) + clip_text = query[:clip_text_index] + return prompt.format(input=clip_text) + + @classmethod + def generate_content(cls, query, prompt): + max_tokens = 1000 + try: + pmt = WebSummary.build_summary_prompt(query, prompt) + output = cls.llm(prompt=pmt, max_tokens=max_tokens) + except Exception as e: + logger.error(e) + return e + return output -- Gitee From 2194fd2af7dd7cb4306be9f0e5fb8cbfbb46f62b Mon Sep 17 00:00:00 2001 From: huxinjia Date: Tue, 26 Nov 2024 16:53:57 +0800 Subject: [PATCH 26/30] cleancode --- mxAgent/samples/tools/common.py | 2 + mxAgent/samples/tools/duck_search.py | 10 +++-- .../tools/tool_query_distance_matrix.py | 37 ++++--------------- mxAgent/samples/tools/tool_query_weather.py | 4 +- 4 files changed, 18 insertions(+), 35 deletions(-) diff --git a/mxAgent/samples/tools/common.py b/mxAgent/samples/tools/common.py index 63c90e010..8fea8f41b 100644 --- a/mxAgent/samples/tools/common.py +++ b/mxAgent/samples/tools/common.py @@ -1,6 +1,7 @@ import json from samples.tools.web_summary_api import WebSummary + def get_website_summary(keys, prompt, llm): filtered = [] for val in keys: @@ -21,6 +22,7 @@ def get_website_summary(keys, prompt, llm): filtered, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) return json.dumps(webs, ensure_ascii=False) + def flatten(nested_list): """递归地扁平化列表""" for item in nested_list: diff --git a/mxAgent/samples/tools/duck_search.py b/mxAgent/samples/tools/duck_search.py index 6e5f422c3..eb0b7291d 100644 --- a/mxAgent/samples/tools/duck_search.py +++ b/mxAgent/samples/tools/duck_search.py @@ -50,6 +50,7 @@ class DuckDuckGoSearch(API): exception = str(e) return self.make_response(input_parameter, results="", exception=exception) + def format_result(res): snippet_idx = res.find("snippet:") title_idx = res.find("title:") @@ -57,7 +58,8 @@ def format_result(res): 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} + return {"snippet": snippet.replace("", "").replace("", ""), "title": title, "url": link} + def call_duck_duck_go_search(query: str, count: int) -> List[str]: try: @@ -78,6 +80,6 @@ def call_duck_duck_go_search(query: str, count: int) -> List[str]: bingsearch_results.append(format_result(snippet)) logger.success(f"{json.dumps(bingsearch_results, indent=4)}") except Exception as e: - scratchpad += f'Search error {str(e)}, please try again' - - return [x['snippet'] for x in bingsearch_results] + logger.error(e) + return [] + return bingsearch_results diff --git a/mxAgent/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py index c2ca449b1..92726c470 100644 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -1,19 +1,7 @@ -# -*- 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 - +from agent_sdk.toolmngt.api import API +from agent_sdk.toolmngt.tool_manager import ToolManager @ToolManager.register_tool() class QueryGoogleDistanceMatrix(API): name = "QueryGoogleDistanceMatrix" @@ -21,26 +9,17 @@ class QueryGoogleDistanceMatrix(API): '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'."} + 'description': "The method of transportation. Choices include 'self-driving', 'flight' 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.'}, + 'origin': {'type': 'str', 'description': 'The origin city of the journey.'}, + 'destination': {'type': 'str', 'description': 'The destination city of your journey.'}, + 'cost': {'type': 'str', 'description': 'The cost of the journey.'}, + 'duration': {'type': 'str', 'description': 'The duration of the journey. Format: X hours Y minutes.'}, + 'distance': {'type': 'str', 'description': 'The distance of the journey. 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 = ( """ { diff --git a/mxAgent/samples/tools/tool_query_weather.py b/mxAgent/samples/tools/tool_query_weather.py index fc6d37f4e..8c7cb2de1 100644 --- a/mxAgent/samples/tools/tool_query_weather.py +++ b/mxAgent/samples/tools/tool_query_weather.py @@ -39,8 +39,8 @@ class QueryWeather(API): 'destination_city': {'type': 'str', 'description': 'the destination city user aim to query weather.'}, "province": {'type': 'str', 'description': 'The province corresponding to the city'}, "date": {'type': 'str', - 'description':("The date of the user want to query, today is"+ - f"{datetime.date.today()}, and today is {weekday}, "+ + 'description': ("The date of the user want to query, today is" + + f"{datetime.date.today()}, and today is {weekday}, " + "please reason the date from user's query, and format with YYYY-MM-DD,") }, 'requirement': {'type': 'str', 'description': 'The more requirement of weather mentioned by the user'}, -- Gitee From 42959d7f82a6065be1019c7d6f301261750a55b8 Mon Sep 17 00:00:00 2001 From: huxinjia Date: Tue, 26 Nov 2024 17:10:29 +0800 Subject: [PATCH 27/30] cleancode --- mxAgent/samples/tools/tool_query_distance_matrix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mxAgent/samples/tools/tool_query_distance_matrix.py b/mxAgent/samples/tools/tool_query_distance_matrix.py index 92726c470..937a11af6 100644 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ b/mxAgent/samples/tools/tool_query_distance_matrix.py @@ -2,6 +2,8 @@ from loguru import logger from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager + + @ToolManager.register_tool() class QueryGoogleDistanceMatrix(API): name = "QueryGoogleDistanceMatrix" -- Gitee From f2d1d9f3aeb372c6d338e61ef162b658907fe6b7 Mon Sep 17 00:00:00 2001 From: hu-xinjia Date: Fri, 3 Jan 2025 17:50:21 +0800 Subject: [PATCH 28/30] add agent SDK repo --- AgentSDK/READM.md | 102 +++++++++ .../agent_sdk/agentchain/base_agent.py | 0 .../agent_sdk/agentchain/react_agent.py | 31 +-- .../agent_sdk/agentchain/recipe_agent.py | 2 +- .../agent_sdk/agentchain/router_agent.py | 0 .../agentchain/single_action_agent.py | 28 +-- .../agent_sdk/agentchain/tool_less_agent.py | 0 .../agent_sdk/common/__init__.py | 0 .../agent_sdk/common/constant.py | 25 ++- .../agent_sdk/executor/common.py | 0 .../executor/recipe_executor/executor.py | 52 +++-- .../executor/recipe_executor/parser.py | 20 +- .../agent_sdk/executor/recipe_executor/sop.py | 0 .../executor/recipe_executor/state.py | 4 +- .../executor/recipe_executor/task.py | 0 {mxAgent => AgentSDK}/agent_sdk/llms/llm.py | 0 .../agent_sdk/llms/openai_compatible.py | 14 +- AgentSDK/agent_sdk/prompts/pre_prompt.py | 191 +++++++++++++++++ AgentSDK/agent_sdk/toolmngt/api.py | 94 +++++++++ AgentSDK/agent_sdk/toolmngt/tool_manager.py | 109 ++++++++++ AgentSDK/requirements.txt | 16 ++ .../samples/basic_demo/test_intent_router.py | 48 +++++ .../samples/basic_demo/test_react_agent.py | 73 +++++++ .../samples/basic_demo/test_react_reflect.py | 81 ++++++++ .../basic_demo/test_single_action_agent.py | 107 ++++++++++ .../samples/basic_demo/test_toolless_agent.py | 35 ++++ .../samples/tools/__init__.py | 10 +- {mxAgent => AgentSDK}/samples/tools/common.py | 11 +- .../samples/tools/duck_search.py | 55 ++--- .../samples/tools/tool_finish.py | 0 .../samples/tools/tool_general_query.py | 0 .../tools/tool_query_accommodations.py | 36 ++-- .../samples/tools/tool_query_attractions.py | 47 +++-- .../samples/tools/tool_query_restaurants.py | 0 .../samples/tools/tool_query_transports.py | 34 +-- .../samples/tools/tool_query_weather.py | 20 +- .../samples/tools/tool_summary.py | 0 .../samples/tools/web_summary_api.py | 50 ++--- .../front/chat_bot_release.py | 58 ++++++ .../samples/travel_agent_demo/travelagent.py | 193 ++++++++++++++++++ mxAgent/samples/tools/tool_cost_enquiry.py | 59 ------ mxAgent/samples/tools/tool_query_city.py | 86 -------- .../tools/tool_query_distance_matrix.py | 40 ---- 43 files changed, 1317 insertions(+), 414 deletions(-) create mode 100644 AgentSDK/READM.md rename {mxAgent => AgentSDK}/agent_sdk/agentchain/base_agent.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/agentchain/react_agent.py (89%) rename {mxAgent => AgentSDK}/agent_sdk/agentchain/recipe_agent.py (98%) rename {mxAgent => AgentSDK}/agent_sdk/agentchain/router_agent.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/agentchain/single_action_agent.py (88%) rename {mxAgent => AgentSDK}/agent_sdk/agentchain/tool_less_agent.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/common/__init__.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/common/constant.py (38%) rename {mxAgent => AgentSDK}/agent_sdk/executor/common.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/executor/recipe_executor/executor.py (89%) rename {mxAgent => AgentSDK}/agent_sdk/executor/recipe_executor/parser.py (90%) rename {mxAgent => AgentSDK}/agent_sdk/executor/recipe_executor/sop.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/executor/recipe_executor/state.py (93%) rename {mxAgent => AgentSDK}/agent_sdk/executor/recipe_executor/task.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/llms/llm.py (100%) rename {mxAgent => AgentSDK}/agent_sdk/llms/openai_compatible.py (84%) create mode 100644 AgentSDK/agent_sdk/prompts/pre_prompt.py create mode 100644 AgentSDK/agent_sdk/toolmngt/api.py create mode 100644 AgentSDK/agent_sdk/toolmngt/tool_manager.py create mode 100644 AgentSDK/requirements.txt create mode 100644 AgentSDK/samples/basic_demo/test_intent_router.py create mode 100644 AgentSDK/samples/basic_demo/test_react_agent.py create mode 100644 AgentSDK/samples/basic_demo/test_react_reflect.py create mode 100644 AgentSDK/samples/basic_demo/test_single_action_agent.py create mode 100644 AgentSDK/samples/basic_demo/test_toolless_agent.py rename {mxAgent => AgentSDK}/samples/tools/__init__.py (59%) rename {mxAgent => AgentSDK}/samples/tools/common.py (64%) rename {mxAgent => AgentSDK}/samples/tools/duck_search.py (60%) rename {mxAgent => AgentSDK}/samples/tools/tool_finish.py (100%) rename {mxAgent => AgentSDK}/samples/tools/tool_general_query.py (100%) rename {mxAgent => AgentSDK}/samples/tools/tool_query_accommodations.py (62%) rename {mxAgent => AgentSDK}/samples/tools/tool_query_attractions.py (58%) rename {mxAgent => AgentSDK}/samples/tools/tool_query_restaurants.py (100%) rename {mxAgent => AgentSDK}/samples/tools/tool_query_transports.py (61%) rename {mxAgent => AgentSDK}/samples/tools/tool_query_weather.py (94%) rename {mxAgent => AgentSDK}/samples/tools/tool_summary.py (100%) rename {mxAgent => AgentSDK}/samples/tools/web_summary_api.py (75%) create mode 100644 AgentSDK/samples/travel_agent_demo/front/chat_bot_release.py create mode 100644 AgentSDK/samples/travel_agent_demo/travelagent.py delete mode 100644 mxAgent/samples/tools/tool_cost_enquiry.py delete mode 100644 mxAgent/samples/tools/tool_query_city.py delete mode 100644 mxAgent/samples/tools/tool_query_distance_matrix.py diff --git a/AgentSDK/READM.md b/AgentSDK/READM.md new file mode 100644 index 000000000..fe38627db --- /dev/null +++ b/AgentSDK/READM.md @@ -0,0 +1,102 @@ +# Agent SDK 基于工具调用的多模式LLM Agent框架 +## 一、功能介绍 +Agent SDK是一个基于LLMs的通用Agent框架,应用多种框架解决不同场景和复杂度的问题,并通过工具调用的方式允许LLMs与外部源进行交互来获取信息,使LLMs生成更加可靠和实际。Agent SDK通过构建DAG(Directed Acyclic Graph)的方式建立工具之间的依赖关系,通过并行执行的方式,提高多工具执行的效率,缩短Agent在复杂场景的执行时间。Agent SDK框架还在框架级别支持流式输出 +提供一套Agent实现框架,让用户可以通过框架搭建自己的Agent应用 +### 1.Router Agent +提供意图识别的能力,用户可预设意图的分类,通过Router Agent给出具体问题的分类结果,用于设别不同的问题场景。 +使用示例: +``` +cd AgentSDK +export PYTHONPATH=. +python samples/basic_demo/intent_router.py --model_name xxx --base_url xxx --api_key xxxx +``` +### 2.Recipe Agent +设置复杂问题执行的workflow,在解决具体问题时,将workflow翻译成有向无环图的的节点编排,通过并行的方式执行节点。 +适用于有相对固定workflow的复杂问题场景。 +1)通过自然语言描述复杂问题的workflow, +2)workflow中每一个步骤对应一次工具使用,并描述步骤间关系 +3)recipe Agent将按照workflow的指导完成工具调用 +4)使用模型总结工作流结果,解决复杂问题 +Recipe Agent利用用户所提供的流程指导和工具,使用LLMs生成SOP,并构建DAG图描述Steps之间的依赖关系。agent识别那些可并行的step,通过并行执行提高agent的执行效率。 +使用Recipe Agent,仅需要提供一段解决问题的SOP指导、用于提示最终答案生成的final prompt、解决问题可能使用的工具。 +示例见[travelagent.py](.travel_agenttravelagent.py)运行方式如下: +``` +cd AgentSDK +export PYTHONPATH=. +python samples/travel_agent_demo/travelagent.py --model_name xxx --base_url xxx --api_key xxxx +``` + +### 3.ReAct Agent +使用Thought、Action、Action Input、Observation的循环流程,解决复杂问题: +1)ReAct通过大模型思考并给出下一步的工具调用, +2)执行工具调用,得到工具执行结果 +3)将工具执行结果应用于下一次的模型思考 +4)循环上述过程,直到模型认为问题得到解决 +使用样例: +``` +cd AgentSDK +export PYTHONPATH=. +python samples/basic_demo/test_react_agent.py --model_name xxx --base_url xxx --api_key xxxx +``` +样例执行过程的轨迹见目录`samples/basic_demo/trajs`下对应的文件 +### 4.Single Action Agent + +通过模型反思、调用工具执行,总结工具结果的执行轨迹,完成一次复杂问题的处理。Single Action Agent使用一次工具调用帮助完成复杂问题解决 +使用示例: +``` +cd AgentSDK +export PYTHONPATH=. +python samples/basic_demo/test_single_action_agent.py --model_name xxx --base_url xxx --api_key xxxx +``` +样例执行过程的轨迹见目录`samples/basic_demo/trajs`下对应的文件 + +### 5. Tooless Agent + +未使用外部工具和知识库,通过prompt工程等技术指导大模型完成复杂问题规划,通过提供示例和指导等内容调优规划效果。 +使用示例: +``` +cd AgentSDK +export PYTHONPATH=. +python samples/basic_demo/test_toolless_agent.py --model_name xxx --base_url xxx --api_key xxxx +``` +## + +## 二、接口使用方法 +### 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/agentchain/base_agent.py b/AgentSDK/agent_sdk/agentchain/base_agent.py similarity index 100% rename from mxAgent/agent_sdk/agentchain/base_agent.py rename to AgentSDK/agent_sdk/agentchain/base_agent.py diff --git a/mxAgent/agent_sdk/agentchain/react_agent.py b/AgentSDK/agent_sdk/agentchain/react_agent.py similarity index 89% rename from mxAgent/agent_sdk/agentchain/react_agent.py rename to AgentSDK/agent_sdk/agentchain/react_agent.py index 7afbb8f5a..f05f44b15 100644 --- a/mxAgent/agent_sdk/agentchain/react_agent.py +++ b/AgentSDK/agent_sdk/agentchain/react_agent.py @@ -1,10 +1,5 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - import copy import json -import os -import stat import re from abc import ABC from dataclasses import dataclass @@ -18,6 +13,7 @@ from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult from agent_sdk.prompts.pre_prompt import travel_agent_prompt, reflect_prompt_value, \ react_reflect_planner_agent_prompt, REFLECTION_HEADER from agent_sdk.toolmngt.api import APIResponse +from agent_sdk.common.constant import save_traj_local class ReflexionStrategy(Enum): @@ -112,28 +108,7 @@ class ReactAgent(BaseAgent, ABC): def save_agent_status(self, file_path: str): try: - instruction = self.prompt.format( - tools=self.tools, - times=self.max_steps - 1, - tools_name=self.tool_names, - query=self.query, - example=self.example, - scratchpad="") - traj = self.scratchpad.strip() - - save_dict = { - "instruction": instruction, "input": "", "output": traj, - "status": self.finished, "created_at": str(datetime.now(tz=timezone.utc)), - "task": self.query - } - - parent_folder_path = os.path.dirname(file_path) - os.makedirs(parent_folder_path, exist_ok=True) - flag = os.O_WRONLY | os.O_CREAT - mode = stat.S_IWUSR | stat.S_IRUSR - with os.fdopen(os.open(file_path, flags=flag, mode=mode), "w") as fout: - json.dump(save_dict, fout, ensure_ascii=False) - fout.write("\n") + save_traj_local(self.query, self.scratchpad, file_path) logger.success(f"save {self.__class__.__name__} status done") except Exception as e: logger.error(f"prompt = {self.prompt}") @@ -168,7 +143,7 @@ class ReactAgent(BaseAgent, ABC): if self.is_valid_tool(action_rst.action): resp = self.tool_manager.api_call(action_rst.action, action_rst.action_input, llm=self.llm) self.api_response_cache.add(resp, action=action_rst.action, action_input=action_rst.action_input) - output_str = json.dumps(resp.output, ensure_ascii=False) + output_str = json.dumps(resp.output, ensure_ascii=False, indent=4) result = output_str if resp.finished: self.answer = output_str diff --git a/mxAgent/agent_sdk/agentchain/recipe_agent.py b/AgentSDK/agent_sdk/agentchain/recipe_agent.py similarity index 98% rename from mxAgent/agent_sdk/agentchain/recipe_agent.py rename to AgentSDK/agent_sdk/agentchain/recipe_agent.py index ec20af472..c12a1d6f0 100644 --- a/mxAgent/agent_sdk/agentchain/recipe_agent.py +++ b/AgentSDK/agent_sdk/agentchain/recipe_agent.py @@ -200,7 +200,7 @@ SUGGESTION = """1. 翻译结果请严格按照YAML格式输出,不要添加任 5. 每个节点的dependency字段必须准确,能匹配伪代码中的依赖关系逻辑,dependency的节点必须是存在的节点 6. 每个节点的input字段必须有参数,每个节点的input字段名务必准确,必须是工具有的参数名,input字段中的每个参数输入值必须且只能是具体值或者依赖节点的工具输出参数, 不要使用python代码或者其他表达式, -7. 每个节点的input字段的每个参数值,优先使用依赖节点的工具输出参数,若无法通过依赖得到可以问题中提取,若存在多个答案,请使用加号+隔开, +7. 每个节点的input字段的每个参数值,优先使用依赖节点的工具输出参数,若无法通过依赖得到可以从问题中提取,若存在多个答案,请使用加号+隔开,若仍无法得到参数,统一使用【无】 8. 【伪代码】的步骤:一个步骤只能翻译成一个对应的节点 9. 生成的内容请严格遵循YAML的语法和格式 """ diff --git a/mxAgent/agent_sdk/agentchain/router_agent.py b/AgentSDK/agent_sdk/agentchain/router_agent.py similarity index 100% rename from mxAgent/agent_sdk/agentchain/router_agent.py rename to AgentSDK/agent_sdk/agentchain/router_agent.py diff --git a/mxAgent/agent_sdk/agentchain/single_action_agent.py b/AgentSDK/agent_sdk/agentchain/single_action_agent.py similarity index 88% rename from mxAgent/agent_sdk/agentchain/single_action_agent.py rename to AgentSDK/agent_sdk/agentchain/single_action_agent.py index c5b4b55d3..b1959c546 100644 --- a/mxAgent/agent_sdk/agentchain/single_action_agent.py +++ b/AgentSDK/agent_sdk/agentchain/single_action_agent.py @@ -2,17 +2,16 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. import json -import os -import stat import re import time -from copy import deepcopy +from datetime import datetime, timezone from loguru import logger from agent_sdk.agentchain.base_agent import BaseAgent, AgentRunResult from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager from agent_sdk.prompts.pre_prompt import single_action_agent_prompt, single_action_final_prompt +from agent_sdk.common.constant import save_traj_local class SingleActionAgent(BaseAgent): @@ -75,7 +74,7 @@ class SingleActionAgent(BaseAgent): else: if self.is_valid_tool(action_type): tool_response = self.tool_manager.api_call(action_type, argument, llm=self.llm) - output_str = json.dumps(tool_response.output, ensure_ascii=False) + output_str = json.dumps(tool_response.output, ensure_ascii=False, indent=4) self.tool_output = output_str if tool_response.success: self.finished = True @@ -88,26 +87,7 @@ class SingleActionAgent(BaseAgent): def save_agent_status(self, file_path): try: - instruction = self.prompt.format( - tools=self.tools, - tools_name=self.tool_names, - query=self.query, - scratchpad="" - ) - - traj = self.scratchpad.strip() - save_dict = { - "instruction": instruction, - "input": "", - "output": traj, - "final answer": self.answer, - "status": self.finished - } - flag = os.O_WRONLY | os.O_CREAT - mode = stat.S_IWUSR | stat.S_IRUSR - with os.fdopen(os.open(file_path, flags=flag, mode=mode), "w") as fout: - json.dump(save_dict, fout, ensure_ascii=False) - fout.write("\n") + save_traj_local(self.query, self.scratchpad, file_path) except Exception as e: logger.error(f"agent_prompt = {self.prompt}") logger.error(e) diff --git a/mxAgent/agent_sdk/agentchain/tool_less_agent.py b/AgentSDK/agent_sdk/agentchain/tool_less_agent.py similarity index 100% rename from mxAgent/agent_sdk/agentchain/tool_less_agent.py rename to AgentSDK/agent_sdk/agentchain/tool_less_agent.py diff --git a/mxAgent/agent_sdk/common/__init__.py b/AgentSDK/agent_sdk/common/__init__.py similarity index 100% rename from mxAgent/agent_sdk/common/__init__.py rename to AgentSDK/agent_sdk/common/__init__.py diff --git a/mxAgent/agent_sdk/common/constant.py b/AgentSDK/agent_sdk/common/constant.py similarity index 38% rename from mxAgent/agent_sdk/common/constant.py rename to AgentSDK/agent_sdk/common/constant.py index 6ae285e4c..19993ab0e 100644 --- a/mxAgent/agent_sdk/common/constant.py +++ b/AgentSDK/agent_sdk/common/constant.py @@ -2,6 +2,10 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. import enum +import os +import stat +from datetime import datetime, timezone +from loguru import logger from pydantic import BaseModel @@ -27,4 +31,23 @@ class AgentRunStatus(BaseModel): THOUGHT = "Thought" ACTION = "Action" ACTION_INPUT = "Action Input" -OBSERVATION = "Observation" \ No newline at end of file +OBSERVATION = "Observation" + + +def save_traj_local(query, traj, path): + directory = os.path.dirname(path) + if not os.path.exists(directory): + try: + os.makedirs(directory) + new_permissions = 0o755 + os.chmod(directory, new_permissions) + except Exception as e: + logger.error(f"make dir error, {e}") + flag = os.O_WRONLY | os.O_CREAT | os.O_APPEND + mode = stat.S_IWUSR | stat.S_IRUSR + with os.fdopen(os.open(path, flags=flag, mode=mode), "a") as fout: + fout.write("****************TASK START*******************\n") + fout.write(f"task: {query}\n") + fout.write(f"trajectory:\n{traj}\n") + fout.write(f"created_at {str(datetime.now(tz=timezone.utc))}\n") + fout.write("*****************TASK END*******************\n\n\n") \ No newline at end of file diff --git a/mxAgent/agent_sdk/executor/common.py b/AgentSDK/agent_sdk/executor/common.py similarity index 100% rename from mxAgent/agent_sdk/executor/common.py rename to AgentSDK/agent_sdk/executor/common.py diff --git a/mxAgent/agent_sdk/executor/recipe_executor/executor.py b/AgentSDK/agent_sdk/executor/recipe_executor/executor.py similarity index 89% rename from mxAgent/agent_sdk/executor/recipe_executor/executor.py rename to AgentSDK/agent_sdk/executor/recipe_executor/executor.py index 93581364d..ee8ca765b 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/executor.py +++ b/AgentSDK/agent_sdk/executor/recipe_executor/executor.py @@ -4,6 +4,7 @@ from typing import Dict import asyncio import time +from datetime import datetime, timezone import re import traceback import json @@ -14,7 +15,7 @@ import yaml from loguru import logger from agent_sdk.executor.recipe_executor.state import ExecutorState, WorkSpace -from agent_sdk.executor.recipe_executor.parser import Node, Parser, ActionGraph +from agent_sdk.executor.recipe_executor.parser import Node, Parser from agent_sdk.executor.recipe_executor.sop import SopHandler from agent_sdk.executor.common import ERROR_MAP, ErrorType, PlanStrategyType @@ -103,9 +104,32 @@ class AgentExecutor(): action.input[key] = value return action + @staticmethod + def get_leaves_result(state): + summary = "" + for name in state.leaves_tasks: + content = state.sop_graph[name].description + res = state.workspace.variable_space.get(name, "") + summary += f"content: {content}\n" + summary += f"result: {json.dumps(res, ensure_ascii=False)}\n" + return summary + + @staticmethod + def get_leaves_node(nodes): + leaves = [] + for key, _ in nodes.items(): + is_leaf = True + for _, other_node in nodes.items(): + if key in other_node.dependency: + is_leaf = False + break + if is_leaf: + leaves.append(key) + return leaves + def get_executable_actions(self, executor_state): done_actions = executor_state.done_tasks - graph = executor_state.sop_graph.actions + graph = executor_state.sop_graph activated = executor_state.activated_tasks independent_actions = [] for task_name in executor_state.remaining_tasks: @@ -141,10 +165,10 @@ class AgentExecutor(): return pending_actions def run_task(self, action, executor_state, llm): - graph = executor_state.sop_graph.actions + graph = executor_state.sop_graph sop_handler = self.operation_handler - mommt = time.time() - logger.debug(f'{action.name} start:{mommt}') + mommt = datetime.now(tz=timezone.utc) + logger.debug(f'{action.name} start: {mommt.strftime("%Y-%m-%d %H:%M:%S")}') output = sop_handler.invoke(action, llm=llm) parsed_out, history = self.parser_output(output, action) @@ -156,8 +180,8 @@ class AgentExecutor(): "history": history } - mommt = time.time() - logger.debug(f'step {action.step}. action: {action.name} has finished') + mommt = datetime.now(tz=timezone.utc) + logger.debug(f'step {action.step}. action: {action.name} has finished. {mommt.strftime("%Y-%m-%d %H:%M:%S")}') return res @@ -176,7 +200,7 @@ class AgentExecutor(): for future in as_completed(thread_list): with self.lock: self.update_history(future.result(), executor_state) - return executor_state.workspace.get_last_result() + return self.get_leaves_result(executor_state) # 而此处的写法不关注next,关注dependency def run(self, content): @@ -187,9 +211,8 @@ class AgentExecutor(): executor_state.activate_actions = activate_actions while executor_state.activate_actions: # 活跃的 - cur_operation = executor_state.activate_actions.pop( - 0) # starts from right - mommt = time.time() + cur_operation = executor_state.activate_actions.pop(0) + # starts from right logger.error("start run step %d, action [%s]", cur_operation.step, cur_operation.name) output = sop_handler.invoke(cur_operation) parsed_out, history = self.parser_output(output, cur_operation) @@ -238,11 +261,12 @@ class AgentExecutor(): execute_state = ExecutorState() workspace = WorkSpace(operation_history=[], variable_space={}) - graph = ActionGraph(operations) execute_state.workspace = workspace - execute_state.sop_graph = graph - execute_state.remaining_tasks = {k for k, _ in graph.actions.items()} + execute_state.sop_graph = operations + execute_state.start_node_id = operations[next(iter(operations))] + execute_state.remaining_tasks = {k for k, _ in operations.items()} + execute_state.leaves_tasks = self.get_leaves_node(operations) return execute_state diff --git a/mxAgent/agent_sdk/executor/recipe_executor/parser.py b/AgentSDK/agent_sdk/executor/recipe_executor/parser.py similarity index 90% rename from mxAgent/agent_sdk/executor/recipe_executor/parser.py rename to AgentSDK/agent_sdk/executor/recipe_executor/parser.py index ee4ac0895..40a19999c 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/parser.py +++ b/AgentSDK/agent_sdk/executor/recipe_executor/parser.py @@ -9,6 +9,7 @@ from loguru import logger class Node: def __init__(self, name, + description, content, prompt, step, @@ -22,6 +23,7 @@ class Node: ): self.step = step self.name = name + self.description = description self.input = content self.prompt = prompt self.activate = activate @@ -39,7 +41,8 @@ class Parser: def construct_graph(nodes): graph = OrderedDict() for operation in nodes: - operation = Node(name=operation['name'], + node = Node(name=operation['name'], + description=operation["description"], step=operation['step'], content=operation['input'], prompt=operation['prompt'], @@ -48,7 +51,7 @@ class Parser: output=operation["output"], strategy=operation["strategy"], activate=operation["activate"]) - graph[operation.name] = operation + graph[node.name] = node return graph def parse(self, raw_dict): @@ -57,7 +60,7 @@ class Parser: node = { "step": None, "name": None, - "goal": None, + "description": None, "input": None, # 输入 "prompt": None, # llm的prompt "activate": None, # 执行引擎,表达式验证规则 @@ -104,14 +107,3 @@ class Parser: else: raise TypeError("Invalid inputs type") return output - - - - -class ActionGraph: - def __init__(self, actions) -> None: - self.actions = actions - if len(actions) == 0: - self.start_node_id = 0 - else: - self.start_node_id = actions[next(iter(actions))] diff --git a/mxAgent/agent_sdk/executor/recipe_executor/sop.py b/AgentSDK/agent_sdk/executor/recipe_executor/sop.py similarity index 100% rename from mxAgent/agent_sdk/executor/recipe_executor/sop.py rename to AgentSDK/agent_sdk/executor/recipe_executor/sop.py diff --git a/mxAgent/agent_sdk/executor/recipe_executor/state.py b/AgentSDK/agent_sdk/executor/recipe_executor/state.py similarity index 93% rename from mxAgent/agent_sdk/executor/recipe_executor/state.py rename to AgentSDK/agent_sdk/executor/recipe_executor/state.py index eeae064e7..d0e916507 100644 --- a/mxAgent/agent_sdk/executor/recipe_executor/state.py +++ b/AgentSDK/agent_sdk/executor/recipe_executor/state.py @@ -48,6 +48,8 @@ class ExecutorState: self.activated_tasks = set() self.done_tasks = set() self.activate_actions = [] + self.leaves_tasks = [] # 图中所有的叶子节点 + self.start_node_id = None # 开始节点 # for sop planning type self.init_query = "" @@ -73,7 +75,7 @@ class WorkSpace: operation_history, variable_space): self.operation_history = operation_history - self.variable_space = variable_space + self.variable_space = variable_space # 记录执行结果,workspace[node_name]的值正常是一个json,由[out_param]指定某一个具体的参数值 self.last_operation = "" def update(self, history): diff --git a/mxAgent/agent_sdk/executor/recipe_executor/task.py b/AgentSDK/agent_sdk/executor/recipe_executor/task.py similarity index 100% rename from mxAgent/agent_sdk/executor/recipe_executor/task.py rename to AgentSDK/agent_sdk/executor/recipe_executor/task.py diff --git a/mxAgent/agent_sdk/llms/llm.py b/AgentSDK/agent_sdk/llms/llm.py similarity index 100% rename from mxAgent/agent_sdk/llms/llm.py rename to AgentSDK/agent_sdk/llms/llm.py diff --git a/mxAgent/agent_sdk/llms/openai_compatible.py b/AgentSDK/agent_sdk/llms/openai_compatible.py similarity index 84% rename from mxAgent/agent_sdk/llms/openai_compatible.py rename to AgentSDK/agent_sdk/llms/openai_compatible.py index fd51e13e6..3ab79cea9 100644 --- a/mxAgent/agent_sdk/llms/openai_compatible.py +++ b/AgentSDK/agent_sdk/llms/openai_compatible.py @@ -1,10 +1,4 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - from typing import List, Dict, Optional -from langchain_openai import OpenAI -from loguru import logger from openai import OpenAI @@ -16,10 +10,10 @@ class OpenAICompatibleLLM: self.client = OpenAI(api_key=api_key, base_url=base_url) def run(self, prompt, ismessage=False, **kwargs): - temperature = kwargs.get("temperature", 0.1) - stop = kwargs.get("stop", None) - max_tokens = kwargs.get("max_tokens", 4096) - stream = kwargs.get("stream", False) + temperature = kwargs.pop("temperature", 0.1) + stop = kwargs.pop("stop", None) + max_tokens = kwargs.pop("max_tokens", 4096) + stream = kwargs.pop("stream", False) messages = prompt if ismessage else [{"role": "user", "content": prompt}] if stream: diff --git a/AgentSDK/agent_sdk/prompts/pre_prompt.py b/AgentSDK/agent_sdk/prompts/pre_prompt.py new file mode 100644 index 000000000..b7a24589a --- /dev/null +++ b/AgentSDK/agent_sdk/prompts/pre_prompt.py @@ -0,0 +1,191 @@ +from datetime import date +from langchain.prompts import PromptTemplate + + +react_agent_instruction = (""" +"You are a world expert at making 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 cannot provide a 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.\n""" ++ f"Please be aware that default date is today on {date.today().strftime('%Y-%m-%d')}.\n" ++ """ +Some rules you should follow: +1. The parameters of tools are alternative, if the user haven't provided, you just keep it empty. +Begin! + +Question: {query}. +{scratchpad}""") + + +FINAL_PROMPT = """Please write a clear and targeted survey 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. + +1. Understand the user's query, based on the user's question, combine `answer` into a coherent text. +2. Try to retain the more information in `answer` +3. Be careful not to fabricate data by oneself and ensure that all data come from the `answer`. +4. When the provided information can't meet the question, you can make a explanation. + +Begin! + +question: {query} +answer: {answer} +""" + +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/AgentSDK/agent_sdk/toolmngt/api.py b/AgentSDK/agent_sdk/toolmngt/api.py new file mode 100644 index 000000000..ea0c624fd --- /dev/null +++ b/AgentSDK/agent_sdk/toolmngt/api.py @@ -0,0 +1,94 @@ +import json +from abc import ABC +from dataclasses import dataclass, field +from typing import Union, Tuple + +from agent_sdk.common.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: + return response.exception is not None + + 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/AgentSDK/agent_sdk/toolmngt/tool_manager.py b/AgentSDK/agent_sdk/toolmngt/tool_manager.py new file mode 100644 index 000000000..2d1f5ecbc --- /dev/null +++ b/AgentSDK/agent_sdk/toolmngt/tool_manager.py @@ -0,0 +1,109 @@ +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}") + return None + + 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/AgentSDK/requirements.txt b/AgentSDK/requirements.txt new file mode 100644 index 000000000..f0cb04bf4 --- /dev/null +++ b/AgentSDK/requirements.txt @@ -0,0 +1,16 @@ +requests==2.27.1 +tqdm +bs4 +transformers +openai +pandas +datasets +peft +fschat +langchain +rouge +rouge-score +loguru +tiktoken +duckduckgo-search==5.3.1b1 +lxml \ No newline at end of file diff --git a/AgentSDK/samples/basic_demo/test_intent_router.py b/AgentSDK/samples/basic_demo/test_intent_router.py new file mode 100644 index 000000000..c9aaf3edc --- /dev/null +++ b/AgentSDK/samples/basic_demo/test_intent_router.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + + +import argparse +from loguru import logger +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.router_agent import RouterAgent + +INTENT = { + "query_flight": "用户期望查询航班信息", + "query_attraction": "用户期望查询旅游景点信息", + "query_hotel": "用户期望查询酒店和住宿信息", + "plan_attraction": "用户期望给出旅行规划建议", + "whimsical": "异想天开", + "other": "其他不符合上述意图的描述" +} + +querys = [ + "帮我查一下从北京去深圳的机票", + "帮我查一下北京的旅游景点", + "我想去北京旅游", + "去北京旅游可以住在哪里呢,推荐一下", + "帮我去书城买本书", "我想上天" +] + + + + +def get_args(): + parse = argparse.ArgumentParser() + parse.add_argument("--model_name", type=str, default="Qwen1.5-32B-Chat", help="OpenAI客户端模型名") + parse.add_argument("--base_url", type=str, default="http://10.44.115.108:1055/v1", help="OpenAI客户端模型地址") + parse.add_argument("--api_key", type=str, default="EMPTY", help="OpenAI客户端api key") + return parse.parse_args().__dict__ + +if __name__ == "__main__": + args = get_args() + API_BASE = args.pop("base_url") + API_KEY = args.pop("api_key") + LLM_NAME = args.pop("model_name") + llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + base_url=API_BASE, api_key=API_KEY, llm_name=LLM_NAME).run + agent = RouterAgent(llm=llm, intents=INTENT) + for query in querys: + response = agent.run(query) + agent.reset() + logger.info(f"query: {query}, intent: {response.answer}") diff --git a/AgentSDK/samples/basic_demo/test_react_agent.py b/AgentSDK/samples/basic_demo/test_react_agent.py new file mode 100644 index 000000000..569da3a92 --- /dev/null +++ b/AgentSDK/samples/basic_demo/test_react_agent.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import os +import warnings +import argparse + +from langchain._api import LangChainDeprecationWarning +from loguru import logger +from tqdm import tqdm + +from agent_sdk.agentchain.react_agent import ReactAgent +from agent_sdk.common.constant import AgentRunStatus +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAttractions, QueryTransports, QueryAccommodations, \ + QueryRestaurants +from samples.basic_demo.test_react_reflect import EXAMPLE + + +warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore', category=DeprecationWarning) +warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) + +os.environ["WORKNING_DIR"] = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + +MAX_CONTEXT_LEN = 4096 + + +def get_default_react_agent(api_base, api_key, llm_name, max_context_len): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, api_base, api_key, llm_name).run + tool_list = [QueryAttractions, QueryTransports, QueryAccommodations, QueryRestaurants] + return ReactAgent(llm=llm, example=EXAMPLE, tool_list=tool_list, max_context_len=max_context_len) + + +def get_args(): + parse = argparse.ArgumentParser() + parse.add_argument("--model_name", type=str, default="Qwen1.5-32B-Chat", help="OpenAI客户端模型名") + parse.add_argument("--base_url", type=str, default="http://10.44.115.108:1055/v1", help="OpenAI客户端模型地址") + parse.add_argument("--api_key", type=str, default="EMPTY", help="OpenAI客户端api key") + return parse.parse_args().__dict__ + + +if __name__ == "__main__": + args = get_args() + API_BASE = args.pop("base_url") + API_KEY = args.pop("api_key") + LLM_NAME = args.pop("model_name") + agent = get_default_react_agent(API_BASE, API_KEY, LLM_NAME, MAX_CONTEXT_LEN) + + queries = [ + "Book a rental car for two people in Salt Lake City from April 15 to April 18, 2022.", + "Research and list down outdoor activities suitable for adrenaline junkies in Moab \ +between April 12 and 14, 2022.", + "Write a short itinerary for a weekend trip to Nashville, starting on April 15, including live music venues." + ] + + staus = AgentRunStatus() + + for query in tqdm(queries): + result = agent.run(query) + staus.total_cnt += 1 + if agent.finished: + staus.success_cnt += 1 + current_path = os.path.dirname(os.path.realpath(__file__)) + agent.save_agent_status(f"{current_path}/trajs/react_execution_log.txt") + agent.reset() + logger.info("\n") + logger.info("*" * 150) + logger.info(f"Question: {query}") + logger.info("*" * 150) + logger.info(f"Final answer: {result.answer}\n") + + logger.info(f"success rates: {staus}") diff --git a/AgentSDK/samples/basic_demo/test_react_reflect.py b/AgentSDK/samples/basic_demo/test_react_reflect.py new file mode 100644 index 000000000..e04d74acd --- /dev/null +++ b/AgentSDK/samples/basic_demo/test_react_reflect.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import warnings +import argparse +import os + +from loguru import logger + +from agent_sdk.agentchain.react_agent import ReactReflectAgent +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAccommodations, QueryAttractions, QueryTransports, Finish + +warnings.filterwarnings('ignore') + +MAX_CONTEXT_LEN = 4096 + + +EXAMPLE = ''' +Question: Can you help with a 5 day trip from Orlando to New York? Departure date is March 10, 2022. +Thought: To create a travel itinerary, I need to find accommodations, transportation, and attractions in New York. I will first find hotels in New York. +Action: QueryAccommodations +Action Input: {"destination_city": "New York", "position": "Central Park", "rank": "four stars"} +Observation1: [{"title": "紐約市10 大最佳四星級酒店 - Tripadvisor", "url": "https://www.tripadvisor.com.hk/Hotels-g60763-zfc4-New_York_City_New_York-Hotels.html", "snippet": "紐約市四星級酒店 · 1. Moxy NYC Times Square · 3,825 則評論 · 2. 格甚溫酒店 · 1,155 則評論 · 3. 托米哈德森廣場飯店 · 3,277 則評論 · 4. 時代廣場愛迪生酒店 · 5. Hard ..."}, {"title": "中央公園酒店| 人氣優惠及套餐", "url": "https://www.agoda.com/zh-hk/park-central-hotel/hotel/new-york-ny-us.html", "snippet": "中央公園酒店是一家位於紐約市的4.0星級酒店,提供豪華的住宿體驗。酒店於2013年進行了最後一次翻新,確保客人能夠享受現代化的設施和舒適的環境。酒店擁有761間客房,提供 ..."}, {"title": "紐約中央公園艾美酒店(Le Méridien New York, Central Park)", "url": "https://www.agoda.com/zh-hk/viceroy-central-park-new-york_2/hotel/new-york-ny-us.html", "snippet": "紐約中央公園艾美酒店位於美國紐約市,是一家四星級酒店。這家酒店提供240間客房 ... 作為一家五星級酒店,紐約中央公園艾美酒店提供優質的服務和舒適的住宿環境 ..."}] +Thought: Now that I have found some hotels in New York, I will next find transportation options from Orlando to New York for the travel dates specified. I will use the QueryTransports API for this purpose. +Action: QueryTransports +Action Input: {"departure_city": "Orlando", "destination_city": "New York", "date": "2022-03-10", "requirement": "budget-friendly"} +Observation2: [{"title": "从奥兰多出发前往纽约的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/orlb/nyca/orlando-to-new-york.html", "snippet": "查找从奥兰多飞往纽约最便宜的月份 ; 3月. ¥750 起 ; 4月. ¥927 起 ; 5月. ¥1,012 起 ; 6月. ¥1,107 起 ; 7月. ¥1,283 起."}, {"title": "从奥兰多国际出发前往纽约拉瓜迪亚的特价机票,往返 ...", "url": "https://www.tianxun.com/routes/mco/lga/orlando-international-to-new-york-laguardia.html", "snippet": "我们始终关注票价,方便您在几秒钟内锁定好价机票。目前,从奥兰多国际飞往纽约拉瓜迪亚最便宜的月份是十一月。"}] +Thought: Now that I have found some transportation options, I will proceed to find attractions in New York. I will use the QueryAttractions API to search for tourist attractions in New York. +Action: QueryAttractions +Action Input: {"destination": "New York"} +Observation3: - snippet: 從參觀紐約市最知名觀光景點開始:時代廣場、帝國大廈、自由女神像,然後利用剩餘的時間探索周圍地區推薦去處。 探索熱門體驗. 根據評等和預訂次數,看看其他旅客喜歡從事 ... + title: 紐約市10 大最佳旅遊景點(2024) - Tripadvisor + url: https://www.tripadvisor.com.hk/Attractions-g60763-Activities-New_York_City_New_York.html +- snippet: 紐約景點推薦 · 紐約景點#1 紐約中央公園 · 紐約景點#2 范德堡一號大樓 SUMMIT · 紐約景點#3 第五大道(Fifth Avenue) + · 紐約景點#4 大都會藝術博物館The ... + title: 【2024紐約景點】漫遊曼哈頓!26個必去行程&免費景點整理 + url: https://www.klook.com/zh-TW/blog/new-york-must-go/ +- snippet: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 · 紐約NewYork景點推薦-10大必去景點 · 1.中央公園(Central + Park) · 2.第五大道(Fifth Avenue) · 3.大都會 ... + title: 【紐約NewYork景點推薦】紐約「10大必去」打卡景點整理懶人包 + url: https://schoolaplus.com/articles-detail.asp?seq=35 +Thought: Now that I have found some attractions in New York, I will summarize the information and create a travel itinerary for the 5-day trip. I will use the Finish tool to provide the final answer. +Action: Finish +Action Input: {"plan details": "Day 1: Depart from Orlando to New York on March 10, 2022. Stay at the Park Central Hotel in Central Park. Visit the Empire State Building and Times Square. Have dinner at Lombardi's Pizza. +Day 2: Visit Central Park, the Metropolitan Museum of Art, and the American Museum of Natural History. Have lunch at Shake Shack and dinner at Le Pain Quotidien. +Day 3: Explore the Brooklyn Bridge, Brooklyn Heights, and DUMBO. Have lunch at Di Fara Pizza and dinner at Peter Luger Steak House. +Day 4: Visit the Statue of Liberty and Ellis Island. Have lunch at The Boil and dinner at Xi'an Famous Foods. +Day 5: Spend the day shopping on Fifth Avenue and visiting the Rockefeller Center. Have lunch at Shake Shack and dinner at Katz's Delicatessen."} +''' + + +def test_react_reflect_agent(): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, API_BASE, API_KEY, LLM_NAME).run + tool_list = [QueryAccommodations, QueryTransports, QueryAttractions, Finish] + agent = ReactReflectAgent(reflect_llm=llm, react_llm=llm, example=EXAMPLE, + tool_list=tool_list, max_context_len=MAX_CONTEXT_LEN) + response = agent.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", + text="given information") + current_path = os.path.dirname(os.path.realpath(__file__)) + agent.save_agent_status(f"{current_path}/trajs/react_reflect_execution_log.txt") + + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +def get_args(): + parse = argparse.ArgumentParser() + parse.add_argument("--model_name", type=str, default="Qwen1.5-32B-Chat", help="OpenAI客户端模型名") + parse.add_argument("--base_url", type=str, default="http://10.44.115.108:1055/v1", help="OpenAI客户端模型地址") + parse.add_argument("--api_key", type=str, default="EMPTY", help="OpenAI客户端api key") + return parse.parse_args().__dict__ + + +if __name__ == "__main__": + args = get_args() + API_BASE = args.pop("base_url") + API_KEY = args.pop("api_key") + LLM_NAME = args.pop("model_name") + logger.info("react reflect agent test begin") + test_react_reflect_agent() + logger.info("react reflect agent test end") diff --git a/AgentSDK/samples/basic_demo/test_single_action_agent.py b/AgentSDK/samples/basic_demo/test_single_action_agent.py new file mode 100644 index 000000000..42af64911 --- /dev/null +++ b/AgentSDK/samples/basic_demo/test_single_action_agent.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +import json +import os +import warnings +import argparse +from typing import Callable, List +from tqdm import tqdm +from loguru import logger +from langchain._api import LangChainDeprecationWarning + +from agent_sdk.agentchain.base_agent import BaseAgent +from agent_sdk.agentchain.single_action_agent import SingleActionAgent +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from samples.tools import QueryAccommodations, QueryAttractions, QueryRestaurants, QueryTransports + +os.environ["WORKING_DIR"] = os.path.dirname( + os.path.dirname(os.path.realpath(__file__))) + +warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore', category=DeprecationWarning) +warnings.filterwarnings('ignore', category=LangChainDeprecationWarning) + + +class TrajectoryGenerator: + + @staticmethod + def generate(output_path: str, agent: BaseAgent, load_dataset: Callable[[], List[str]], **kwargs): + questions = load_dataset() + for q in tqdm(questions): + try: + agent.run(q, **kwargs) + agent.save_agent_status(output_path) + agent.reset() + + except Exception as err: + logger.warning(f"generate traj failed, query: {q}, agent: {agent.name}, err: {err}") + continue + + @staticmethod + def _check_data_format(data): + if not isinstance(data, list): + raise ValueError("Data should be a list of dict") + + if len(data) == 0: + raise ValueError("Data should not be empty") + + if not isinstance(data[0], dict): + raise ValueError("Data item should be a dict") + + alpaca_format_keys = ["instruction", "input", "output", "status"] + data_keys_set = set(data[0].keys()) + + if not all([key in data_keys_set for key in alpaca_format_keys]): + raise ValueError("need alpaca data format") + + def _load_data_from_file(self, data_path): + if not os.path.exists(data_path): + raise FileNotFoundError(f"File not found: {data_path}") + + if data_path.endswith(".jsonl"): + data = [json.loads(line) for line in open(data_path, "r")] + else: + raise ValueError("Unknown file format") + + self._check_data_format(data) + return data + + +def get_single_action_agent(api_base, api_key, llm_name): + tool_list = [QueryAccommodations, QueryAttractions, QueryRestaurants, QueryTransports] + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, + api_base, api_key, llm_name).run + return SingleActionAgent(llm=llm, tool_list=tool_list, max_steps=5) + + +def get_args(): + parse = argparse.ArgumentParser() + parse.add_argument("--model_name", type=str, default="Qwen1.5-32B-Chat", help="OpenAI客户端模型名") + parse.add_argument("--base_url", type=str, default="http://10.44.115.108:1055/v1", help="OpenAI客户端模型地址") + parse.add_argument("--api_key", type=str, default="EMPTY", help="OpenAI客户端api key") + return parse.parse_args().__dict__ + + +if __name__ == "__main__": + args = get_args() + API_BASE = args.pop("base_url") + API_KEY = args.pop("api_key") + LLM_NAME = args.pop("model_name") + single_agent = get_single_action_agent(API_BASE, API_KEY, LLM_NAME) + queries = [ + "Write a review of the hotel \"The Beach House\" in Charlotte Amalie.", + "Book a flight from Evansville to Sacramento for April 10th.", + "Create a list of top 5 attractions in Hilo for a solo traveler.", + "Compare the prices of hotels in Newark for a 3-night stay.", + "Book a hotel room in Paducah for April 12th.", + "Write a travel blog post about visiting the Golden Gate Bridge in San Francisco.", + "Recommend the best mode of transportation from Flagstaff to Phoenix.", + "Determine the best time to visit the Statue of Liberty.", + "Compare the prices of car rentals in Seattle.", + "What are the top - rated museums in Harrisburg?" + ] + generator = TrajectoryGenerator() + current_path = os.path.dirname(os.path.realpath(__file__)) + generator.generate(output_path=f"{current_path}/trajs/single_action_execution.txt", agent=single_agent, + load_dataset=lambda: queries) diff --git a/AgentSDK/samples/basic_demo/test_toolless_agent.py b/AgentSDK/samples/basic_demo/test_toolless_agent.py new file mode 100644 index 000000000..c782af63d --- /dev/null +++ b/AgentSDK/samples/basic_demo/test_toolless_agent.py @@ -0,0 +1,35 @@ +import warnings +import argparse + +from loguru import logger +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.tool_less_agent import ToollessAgent + +warnings.filterwarnings('ignore') +MAX_CONTEXT_LEN = 4096 + + +def test_toolless_agent(): + llm = get_llm_backend(BACKEND_OPENAI_COMPATIBLE, API_BASE, API_KEY, LLM_NAME).run + agent = ToollessAgent(llm=llm, max_context_len=MAX_CONTEXT_LEN) + response = agent.run("Can you help with a 5 day trip from Orlando to Paris? Departure date is April 10, 2022.", + text="given information") + logger.info(f"5 day trip from Orlando to Paris:{response.answer}") + + +def get_args(): + parse = argparse.ArgumentParser() + parse.add_argument("--model_name", type=str, default="Qwen1.5-32B-Chat", help="OpenAI客户端模型名") + parse.add_argument("--base_url", type=str, default="http://10.44.115.108:1055/v1", help="OpenAI客户端模型地址") + parse.add_argument("--api_key", type=str, default="EMPTY", help="OpenAI客户端api key") + return parse.parse_args().__dict__ + + +if __name__ == "__main__": + args = get_args() + API_BASE = args.pop("base_url") + API_KEY = args.pop("api_key") + LLM_NAME = args.pop("model_name") + logger.info("toolless agent test begin") + test_toolless_agent() + logger.info("toolless agent test end") \ No newline at end of file diff --git a/mxAgent/samples/tools/__init__.py b/AgentSDK/samples/tools/__init__.py similarity index 59% rename from mxAgent/samples/tools/__init__.py rename to AgentSDK/samples/tools/__init__.py index 030d89752..c5bb0a43e 100644 --- a/mxAgent/samples/tools/__init__.py +++ b/AgentSDK/samples/tools/__init__.py @@ -2,21 +2,15 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. __all__ = [ - 'CostEnquiry', 'Finish', 'QueryAccommodations', 'QueryAttractions', 'CitySearch', - 'QueryGoogleDistanceMatrix', 'QueryTransports', 'QueryWeather', "QueryRestaurants", - 'PlanSummary', 'WebSummary' + 'Finish', 'QueryAccommodations', 'QueryAttractions', '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/common.py b/AgentSDK/samples/tools/common.py similarity index 64% rename from mxAgent/samples/tools/common.py rename to AgentSDK/samples/tools/common.py index 8fea8f41b..98fac08fd 100644 --- a/mxAgent/samples/tools/common.py +++ b/AgentSDK/samples/tools/common.py @@ -1,8 +1,5 @@ -import json -from samples.tools.web_summary_api import WebSummary - -def get_website_summary(keys, prompt, llm): +def filter_website_keywords(keys): filtered = [] for val in keys: if val is None or len(val) == 0: @@ -13,14 +10,10 @@ def get_website_summary(keys, prompt, llm): if isinstance(val, list): it = flatten(val) filtered.append(it) - filtered.append(val) if len(filtered) == 0: raise Exception("keywords has no been found") - - webs = WebSummary.web_summary( - filtered, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) - return json.dumps(webs, ensure_ascii=False) + return filtered def flatten(nested_list): diff --git a/mxAgent/samples/tools/duck_search.py b/AgentSDK/samples/tools/duck_search.py similarity index 60% rename from mxAgent/samples/tools/duck_search.py rename to AgentSDK/samples/tools/duck_search.py index eb0b7291d..9b4231d90 100644 --- a/mxAgent/samples/tools/duck_search.py +++ b/AgentSDK/samples/tools/duck_search.py @@ -1,12 +1,12 @@ import json from typing import List import re +import time -from langchain_community.tools import DuckDuckGoSearchResults -from langchain_community.utilities import DuckDuckGoSearchAPIWrapper -from utils.log import LOGGER as logger +from duckduckgo_search import DDGS +from loguru import logger -from toolmngt.api import API +from agent_sdk.toolmngt.api import API class DuckDuckGoSearch(API): @@ -51,35 +51,20 @@ class DuckDuckGoSearch(API): return self.make_response(input_parameter, results="", exception=exception) -def format_result(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, "url": link} - - def call_duck_duck_go_search(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) - bingsearch_results = [] - temp = search.run(query) - logger.debug(temp) - - snippets = re.findall(r'\[(.*?)\]', temp) - snippets = [snippet.strip() for snippet in snippets] - - for snippet in snippets: - if len(snippet) == 0: - continue - logger.debug(f"snippet is {snippet}") - bingsearch_results.append(format_result(snippet)) - logger.success(f"{json.dumps(bingsearch_results, indent=4)}") - except Exception as e: - logger.error(e) - return [] - return bingsearch_results + retry = 1 + while retry <= 3: + try: + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" + } + logger.debug(f"search DuckDuckGo({query}, {count})") + results = DDGS(headers=headers).text(query, backend="html", max_results=count) + return results + except Exception as e: + retry += 1 + logger.warning("duck search error. will retry") + time.sleep(1) + if retry > 3: + logger.error(e) + return [] diff --git a/mxAgent/samples/tools/tool_finish.py b/AgentSDK/samples/tools/tool_finish.py similarity index 100% rename from mxAgent/samples/tools/tool_finish.py rename to AgentSDK/samples/tools/tool_finish.py diff --git a/mxAgent/samples/tools/tool_general_query.py b/AgentSDK/samples/tools/tool_general_query.py similarity index 100% rename from mxAgent/samples/tools/tool_general_query.py rename to AgentSDK/samples/tools/tool_general_query.py diff --git a/mxAgent/samples/tools/tool_query_accommodations.py b/AgentSDK/samples/tools/tool_query_accommodations.py similarity index 62% rename from mxAgent/samples/tools/tool_query_accommodations.py rename to AgentSDK/samples/tools/tool_query_accommodations.py index 3c80febeb..5326b3537 100644 --- a/mxAgent/samples/tools/tool_query_accommodations.py +++ b/AgentSDK/samples/tools/tool_query_accommodations.py @@ -8,13 +8,15 @@ from loguru import logger from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager -from samples.tools.common import get_website_summary +from samples.tools.common import filter_website_keywords +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." + description = "This api can discover accommodations in your desired city. It provides room information, \ + pricing, location, stars, estimation, constraints and so on" 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'}, @@ -44,23 +46,27 @@ class QueryAccommodations(API): position = input_parameter.get("position") rank = input_parameter.get("rank") llm = kwargs.get("llm", None) - keys = [destination, position, rank, "住宿"] + keys = [destination, position, rank] logger.debug(f"search accommodation key words: {','.join(keys)}") prompt = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页信息进行总结,并以精简的文本的形式进行返回, - 请添加适当的词语,使得语句内容连贯,通顺。提供的信息是为用户推荐的酒店的网页数据, - 请总结网页信息,要求从以下几个方面考虑: - 1. 酒店的地理位置,星级、评分,评价,品牌信息 - 2. 不同的户型对应的价格、房间情况,对入住用户的要求等 - 并给出一到两个例子介绍这些情况 - 若输入的内容没有包含有效的酒店和住宿信息,请统一返回:【无】 - 下面是网页的输入: - {input} - 请生成总结: - """ +请添加适当的词语,使得语句内容连贯,通顺。提供的信息是为用户推荐的酒店的网页数据, +请总结网页信息,要求从以下几个方面考虑: +1. 酒店的地理位置,星级、评分,评价,品牌信息 +2. 不同的户型对应的价格、房间情况,对入住用户的要求等 +并给出一到两个例子介绍这些情况。 +必须注意下面这个要求: +若输入的内容没有包含相关的酒店和住宿信息,请统一返回:【无】 +下面是网页的输入: +{input} +请生成总结: +""" try: - content = get_website_summary(keys, prompt, llm) - res = {"accommodation": content} + filtered = filter_website_keywords(keys) + filtered.append("住宿") + webs = WebSummary.web_summary( + filtered, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) + res = {"accommodation": json.dumps(webs, ensure_ascii=False, indent=4)} return self.make_response(input_parameter, results=res, exception="") except Exception as e: logger.error(e) diff --git a/mxAgent/samples/tools/tool_query_attractions.py b/AgentSDK/samples/tools/tool_query_attractions.py similarity index 58% rename from mxAgent/samples/tools/tool_query_attractions.py rename to AgentSDK/samples/tools/tool_query_attractions.py index de83491c9..ff16f98a0 100644 --- a/mxAgent/samples/tools/tool_query_attractions.py +++ b/AgentSDK/samples/tools/tool_query_attractions.py @@ -1,20 +1,21 @@ # -*- coding: utf-8 -*- # Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +import json import tiktoken -import yaml from loguru import logger from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager -from samples.tools.common import get_website_summary +from samples.tools.common import filter_website_keywords +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." + description = "This api can Search for tourist attractions from websites that expected, information includes \ + location, ticket, open time, booking, contact adress." 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'}, @@ -38,7 +39,7 @@ class QueryAttractions(API): "destination": "Paris", "scene": "The Louvre Museum", "type": "Museum", - "requirement": "historical" + "requirement": "free" }""") def __init__(self): @@ -51,25 +52,29 @@ class QueryAttractions(API): requirement = input_parameter.get('requirement') llm = kwargs.get("llm", None) - keys = [destination, scene, scene_type, requirement, "景点"] + keys = [destination, scene, scene_type, requirement] summary_prompt = """你是一个擅长于网页信息总结的智能助手,提供的网页是关于旅游规划的信息,现在已经从网页中获取到了相关的文字内容信息,你需要从网页中找到与**景区**介绍相关的内容,并进行提取, - 你务必保证提取的内容都来自所提供的文本,保证结果的客观性,真实性。 - 网页中可能包含多个景点的介绍,你需要以YAML文件的格式返回,每个景点的返回的参数和格式如下: - **输出格式**: - - name: xx - introduction: xx - **参数介绍**: - name:景点名称 - introduction:精简的景区介绍,可以从以下这些方面阐述:景点的基本情况、历史文化等信息、景区门票信息、景区开放时间、景区的联系方式、预约方式以及链接,景区对游客的要求等。 - **注意** - 请注意:不要添加任何解释或注释,且严格遵循YAML格式 - 下面是提供的网页文本信息: - {input} - 请开始生成: +你务必保证提取的内容都来自所提供的文本,保证结果的客观性,真实性。 +网页中可能包含多个景点的介绍,你需要以YAML文件的格式返回,每个景点的返回的参数和格式如下: +**输出格式**: +- name: xx +introduction: xx +**参数介绍**: +name:景点名称 +introduction:精简的景区介绍,可以从以下这些方面阐述:景点的基本情况、历史文化等信息、景区门票信息、景区开放时间、景区的联系方式、预约方式以及链接,景区对游客的要求等。 +**注意** +1. 请注意:不要添加任何解释或注释,且严格遵循YAML格式 +2. 若输入的内容没有包含旅游和景点相关信息,请统一返回:【无】 +下面是提供的网页文本信息: +{input} +请开始生成: """ try: - content = get_website_summary(keys, summary_prompt, llm) - res = {'attractions': content} + filtered = filter_website_keywords(keys) + filtered.append("景点") + webs = WebSummary.web_summary( + filtered, search_num=3, summary_num=3, summary_prompt=summary_prompt, llm=llm) + res = {'attractions': json.dumps(webs, ensure_ascii=False, indent=4)} return self.make_response(input_parameter, results=res, exception="") except Exception as e: logger.error(e) diff --git a/mxAgent/samples/tools/tool_query_restaurants.py b/AgentSDK/samples/tools/tool_query_restaurants.py similarity index 100% rename from mxAgent/samples/tools/tool_query_restaurants.py rename to AgentSDK/samples/tools/tool_query_restaurants.py diff --git a/mxAgent/samples/tools/tool_query_transports.py b/AgentSDK/samples/tools/tool_query_transports.py similarity index 61% rename from mxAgent/samples/tools/tool_query_transports.py rename to AgentSDK/samples/tools/tool_query_transports.py index 74bb5cc12..8a6a383eb 100644 --- a/mxAgent/samples/tools/tool_query_transports.py +++ b/AgentSDK/samples/tools/tool_query_transports.py @@ -8,18 +8,20 @@ import tiktoken from agent_sdk.toolmngt.api import API from agent_sdk.toolmngt.tool_manager import ToolManager -from samples.tools.common import get_website_summary +from samples.tools.common import filter_website_keywords +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," + description = "This API can query relevant travel traffic information from network, including flight number, \ + travel time, distance, price, constraints an so on." 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'}, + "travel_mode": {'type': 'str', 'description': "The mode of travel appointed by the user, \ + Choices include 'self-driving', 'flight', 'train', 'taxi' and so on."}, "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'}, } @@ -50,18 +52,22 @@ class QueryTransports(API): try: prefix = f"从{origin}出发" if origin else "" prefix += f"前往{destination}" if destination else "" - keys = [prefix, req, travel_mode, "购票"] + keys = [prefix, req, travel_mode] prompt = """你的任务是将提供的网页信息进行总结,并以精简的文本的形式进行返回, - 请添加适当的词语,使得语句内容连贯,通顺。输入是为用户查询的航班、高铁等交通数据,请将这些信息总结 - 请总结网页信息,要求从以下几个方面考虑: - 总结出航班或者高铁的价格区间、需要时长区间、并给出2-3例子,介绍车次、时间、时长、价格等 - 下面是网页的输入: - {input} - 请生成总结: - """ - content = get_website_summary(keys, prompt, llm) - res = {'transport': content} +请添加适当的词语,使得语句内容连贯,通顺。输入是为用户查询的航班、高铁等交通数据,请将这些信息总结 +请总结网页信息,要求从以下几个方面考虑: +总结出航班或者高铁的价格区间、需要时长区间、并给出2-3例子,介绍车次、时间、时长、价格等 +请特别注意: +若输入的内容没有包含相关的交通信息,请统一返回:【无】 +下面是网页的输入: +{input} +请生成总结: +""" + filtered = filter_website_keywords(keys) + webs = WebSummary.web_summary( + filtered, search_num=3, summary_num=3, summary_prompt=prompt, llm=llm) + res = {'transport': json.dumps(webs, ensure_ascii=False, indent=4)} return self.make_response(input_parameter, results=res, exception="") except Exception as e: logger.error(e) diff --git a/mxAgent/samples/tools/tool_query_weather.py b/AgentSDK/samples/tools/tool_query_weather.py similarity index 94% rename from mxAgent/samples/tools/tool_query_weather.py rename to AgentSDK/samples/tools/tool_query_weather.py index 8c7cb2de1..03d877a28 100644 --- a/mxAgent/samples/tools/tool_query_weather.py +++ b/AgentSDK/samples/tools/tool_query_weather.py @@ -32,7 +32,8 @@ REQUEST_HEADERS = { @ToolManager.register_tool() class QueryWeather(API): name = "QueryWeather" - description = "This API is used to query weather forecast from the network according to the user's input question," + description = "This API is used to query weather forecast from the network, information including temperature, \ + humidness, wind force, weather condition." weekday = WEEK_MAP.get(datetime.datetime.now(ZoneInfo("Asia/Shanghai")).weekday(), '') input_parameters = { @@ -59,10 +60,6 @@ class QueryWeather(API): } """) - def __init__(self, ): - os.environ['CURL_CA_BUNDLE'] = '' # 关闭SSL证书验证 - urllib3.disable_warnings() - def get_forecast(self, url, param, city=""): headers = REQUEST_HEADERS response = requests.get(url, params=param, headers=headers, timeout=5) @@ -91,7 +88,7 @@ class QueryWeather(API): # 精简输入 key_keeps = [ 'day_weather', 'day_wind_direction', 'day_wind_power', - 'max_degree', 'min_degree', 'night_weather', 'night_wind_direction', 'night_wind_power' + 'max_degree', 'min_degree', 'night_weather' ] summary_copy = [] for key, info in weekly_weather.items(): @@ -101,7 +98,7 @@ class QueryWeather(API): info_keeps = {k: info[k] for k in key_keeps if k in info} daily[time] = info_keeps summary_copy.append(daily) - return summary_copy + return summary_copy[:5] def format_request_param(self, data, weather_type): for key, value in data.items(): @@ -123,6 +120,8 @@ class QueryWeather(API): return params def call(self, input_parameter, **kwargs): + os.environ['CURL_CA_BUNDLE'] = '' + urllib3.disable_warnings() des = input_parameter.get('destination_city') departure_date = input_parameter.get("date") weather_type = "forecast_24h" @@ -133,6 +132,7 @@ class QueryWeather(API): try: data = self.get_city2province("https://wis.qq.com/city/like", des) except Exception as e: + logger.error(e) e = str(e) return self.make_response(input_parameter, results=e, success=False, exception=e) if len(data) == 0: @@ -158,14 +158,14 @@ class QueryWeather(API): formated_departure = datetime.datetime.strptime( departure_date, "%Y-%m-%d").date() except ValueError as e: - logger.warning(e) + # 默认当前日期 formated_departure = datetime.date.today() gaps = (formated_departure - datetime.date.today()).days weather_summary = summary_copy[gaps + 1:] if len(weather_summary) == 0: - weather_summary = "**抱歉,我最多只能查询最近7天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ - json.dumps(summary_copy, ensure_ascii=False) + weather_summary = "**抱歉,我最多只能查询最近5天的天气情况,例如下面是我将为你提供最近的天气预报**:\n" + \ + json.dumps(summary_copy, ensure_ascii=False, indent=4) res = { 'forecast': weather_summary } diff --git a/mxAgent/samples/tools/tool_summary.py b/AgentSDK/samples/tools/tool_summary.py similarity index 100% rename from mxAgent/samples/tools/tool_summary.py rename to AgentSDK/samples/tools/tool_summary.py diff --git a/mxAgent/samples/tools/web_summary_api.py b/AgentSDK/samples/tools/web_summary_api.py similarity index 75% rename from mxAgent/samples/tools/web_summary_api.py rename to AgentSDK/samples/tools/web_summary_api.py index dfb38dd37..a91c69f56 100644 --- a/mxAgent/samples/tools/web_summary_api.py +++ b/AgentSDK/samples/tools/web_summary_api.py @@ -5,7 +5,7 @@ import asyncio import os import re -import time +from datetime import datetime, timezone from concurrent.futures import ThreadPoolExecutor, wait, as_completed import aiohttp @@ -26,7 +26,7 @@ def check_number_input(num, crow): return True -async def bai_du(url): +async def curl_web(url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\ Chrome/126.0.0.0 Safari/537.36" @@ -50,12 +50,12 @@ class WebSummary: Chrome/126.0.0.0 Safari/537.36" } try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") + mommt = datetime.now(tz=timezone.utc) + logger.info(f"start request website: {mommt.strftime('%Y-%m-%d %H:%M:%S')},{url}") response = requests.get( url, headers=headers, timeout=(3, 3), stream=True) - mommt = time.time() - logger.info(f"finish request website: {mommt},{url}") + mommt = datetime.now(tz=timezone.utc) + logger.info(f"finish request website: {mommt.strftime('%Y-%m-%d %H:%M:%S')},{url}") if response.status_code != 200: logger.error(f"获取网页{url}内容失败") return '', f"获取网页{url}内容失败" @@ -69,8 +69,8 @@ class WebSummary: logger.error(e) return '', e res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") + mommt = datetime.now(tz=timezone.utc) + logger.info(f"finish summary website: {mommt.strftime('%Y-%m-%d %H:%M:%S')},{url}") return res, None @classmethod @@ -78,11 +78,11 @@ class WebSummary: os.environ['CURL_CA_BUNDLE'] = '' urllib3.disable_warnings() try: - mommt = time.time() - logger.info(f"start request website: {mommt},{url}") - response = await bai_du(url) - mommt = time.time() - logger.debug(f"finish request website: {mommt},{url}") + mommt = datetime.now(tz=timezone.utc) + logger.info(f"start request website: {mommt.strftime('%Y-%m-%d %H:%M:%S')},{url}") + response = await curl_web(url) + mommt = datetime.now(tz=timezone.utc) + logger.debug(f"finish request website: {mommt.strftime('%Y-%m-%d %H:%M:%S')},{url}") content = response bsobj = BeautifulSoup(content, 'html.parser') txt = bsobj.get_text() @@ -91,20 +91,22 @@ class WebSummary: if 'PleaseenableJSanddisableanyadblocker' in text: text = "" except Exception as e: + if len(str(e)) == 0: + e = Exception(f"error type: {type(e)}, when request the url: {url}") logger.error(e) return '', e if len(text) == 0: return "", "no valid website content" res = cls.generate_content(text, summary_prompt) - mommt = time.time() - logger.info(f"finish summary website: {mommt},{url}") + mommt = datetime.now(tz=timezone.utc) + logger.info(f"finish summary website: {mommt.strftime('%Y-%m-%d %H:%M:%S')},{url}") return res, None @classmethod def summary_call(cls, web, max_summary_number, summary_prompt): title = web.get("title", "") - url = web.get("url") - snippet = web.get("snippet", "") + url = web.get("href") + snippet = web.get("body", "") web_summary = {} if url is None: return web_summary @@ -112,10 +114,10 @@ class WebSummary: web_summary['title'] = title web_summary['url'] = url try: - content = asyncio.run(cls.get_details(url, summary_prompt)) + content, err = asyncio.run(cls.get_details(url, summary_prompt)) except Exception as e: logger.error(e) - if not isinstance(content, str) or len(content) == 0: + if not isinstance(content, str) or len(content) == 0 or content == "【无】": web_summary['snippet'] = snippet else: web_summary['content'] = content @@ -127,13 +129,13 @@ class WebSummary: logger.add('app.log', level='DEBUG') cls.llm = llm try: - mommt = time.time() - logger.debug(f"start google search: {mommt}") + mommt = datetime.now(tz=timezone.utc) + logger.debug(f"start duck duck go search: {mommt.strftime('%Y-%m-%d %H:%M:%S')}") if isinstance(keys, list): - keys = ",".join(keys) + keys = " ".join(keys) search_result = call_duck_duck_go_search(keys, search_num) - mommt = time.time() - logger.debug(f"finish google search: {mommt}") + mommt = datetime.now(tz=timezone.utc) + logger.debug(f"finish duck duck go search: {mommt.strftime('%Y-%m-%d %H:%M:%S')}") except Exception as e: logger.error(e) return [] diff --git a/AgentSDK/samples/travel_agent_demo/front/chat_bot_release.py b/AgentSDK/samples/travel_agent_demo/front/chat_bot_release.py new file mode 100644 index 000000000..28cbb7359 --- /dev/null +++ b/AgentSDK/samples/travel_agent_demo/front/chat_bot_release.py @@ -0,0 +1,58 @@ +import streamlit as st +from samples.travel_agent.travelagent import TravelAgent + +if __name__ == "__main__": + st.set_page_config( + page_title="旅游规划agent" + ) + st.logo("logo.jpg") + st.markdown('

旅游规划Agent

', unsafe_allow_html=True) + + + placeholder1 = st.empty() + placeholder2 = st.empty() + placeholder3 = st.empty() + + if "messages" not in st.session_state: + st.session_state.messages = [] + + if "aagent" not in st.session_state: + st.session_state.agent = TravelAgent() + + with placeholder1: + container = st.container(height=300, border=False) + with placeholder3: + _, col2, _ = st.columns([1, 20, 1]) + helloinfo = """

您好,我是旅游规划agent,擅长旅行规划、景点攻略查询

+

例如:从北京到西安旅游规划

+

例如:西安有哪些免费的博物馆景点

+

例如:查一下西安的酒店

""" + with col2: + st.markdown(helloinfo, unsafe_allow_html=True) + + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.empty() + st.markdown(message["content"]) + + prompt = st.chat_input("send message") + if prompt is not None: + st.session_state.messages.append({"role":"user", "content":prompt}) + placeholder1.empty() + placeholder2.empty() + placeholder3.empty() + + agent = st.session_state["agent"] + + with st.chat_message("user"): + st.markdown(prompt) + + with st.chat_message("assistant"): + with st.spinner("thinking..."): + response = agent.run(query=prompt, stream=True) + if isinstance(response, str): + st.markdown(response) + else: + response = st.write_stream(response) + + st.session_state.messages.append({"role":"assistant", "content":response}) \ No newline at end of file diff --git a/AgentSDK/samples/travel_agent_demo/travelagent.py b/AgentSDK/samples/travel_agent_demo/travelagent.py new file mode 100644 index 000000000..e57a27129 --- /dev/null +++ b/AgentSDK/samples/travel_agent_demo/travelagent.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + +from abc import ABC +import argparse +from loguru import logger + +from agent_sdk.llms.llm import get_llm_backend, BACKEND_OPENAI_COMPATIBLE +from agent_sdk.agentchain.recipe_agent import RecipeAgent +from agent_sdk.agentchain.router_agent import RouterAgent +from agent_sdk.agentchain.tool_less_agent import ToollessAgent +from agent_sdk.agentchain.base_agent import AgentRunResult + +from samples.tools.tool_query_accommodations import QueryAccommodations +from samples.tools.tool_query_transports import QueryTransports +from samples.tools.tool_query_attractions import QueryAttractions +from samples.tools.tool_summary import PlanSummary +from samples.tools.tool_general_query import GeneralQuery +from samples.tools.tool_query_weather import QueryWeather + + +PESUEDE_CODE = """步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息 +步骤2:根据用户的问题,从网络中查询相关的出行交通信息。 +步骤3:根据用户的问题,从网络中搜索相关的住宿和酒店信息; +步骤4:根据用户的问题,查询用户需要的城市天气情况; +步骤5:总结以上的出行信息、景点游玩、住宿信息等。""" +TRANSPORT_INST = """步骤1:根据用户的输入问题,从网络中查询相关的出行交通信息""" +ATTRACTION_INST = "步骤1:根据用户问题中对景点相关的需求,从网络中搜索相关的景点信息" +HOTEL_INST = """步骤1:根据用户的问题,从网络中搜索相关的住宿和酒店信息,""" +WEATHER_INST = "步骤一:根据用户的问题,查询用户需要的城市天气情况" +OTHER_INST = """步骤1:根据用户的输入,从互联网中查询相关的解答""" + + +GENERAL_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手,你的任务是将提供的网页摘要信息进行总结,并以markdown的格式进行返回, +请添加适当的词语,使得语句内容连贯,通顺 +请将content和snippet的信息进行综合处理,进行总结,生成一个段落。 +涉及到url字段时,使用超链接的格式将网页url链接到网页title上。 +参数介绍】: +title:网页标题 +url:网页链接 +snippet:网页摘要信息 +content:网页的内容总结 +下面是JSON格式的输入: +{text} +请生成markdown段落:""" + +WEATHER_FINAL_PROMPT = """你是一个擅长文字处理和信息总结的智能助手, +当前的工作场景是:天气出行建议;输入的内容是JSON格式的用户所查询城市未来的天气预报,请将这些信息总结为的自然语言展示天气预报的信息,并对用户的出游给除建议, +根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 +请添加适当的词语,使得语句内容连贯,通顺,并尽可能保留输入的信息和数据,但不要自行杜撰信息。 +提供的信息以JSON的格式进行展示 +【参数介绍】: +date:日期 +day_weather:白天的天气情况 +day_wind_direction:白天风向 +day_wind_power: 白天风力 +night_weather:夜晚的天气情况 +night_wind_direction:夜晚风向 +night_wind_power: 夜晚风力 +max_degree: 最高温 +min_degree:最低温 +下面是JSON格式的输入: +{text} +请生成markdown段落: +""" +PLANNER_FINAL_PROMPT = """你是一个擅长规划和文字处理的智能助手,你需要将提供的信息按照下面的步骤撰写一份旅游攻略,输出markdown格式的段落, +你可以添加适当的语句,使得段落通顺,但不要自己杜撰信息。 +步骤】 +1. 根据【用户需要旅行的天数】,将输入的景点分配到每一天的行程中,每天2-3个景点,并介绍景点的详细情况 +2. 叙述输入中推荐的住宿情况,详细介绍酒店的详细情况,和预定链接 +3. 叙述输入中查询的交通安排,详细介绍每个出行方案的价格、时间、时长等详细情况,和预定链接 +4. 介绍输入中天气预报的情况,根据天气的情况,你可以做出一些出行建议,比如是否需要雨具、防晒、保暖等 +【参数介绍】: +title:网页标题 +url:网页链接,满足用户需求的酒店筛选结果 +content:网页主要内容提取物 +snippet:网页摘要信息 +输入的信息以JSON格式,下面是的输入: +{text} +请生成markdown段落:""" + + + +TRAVEL_PLAN = "TRAVEL_PLAN" +QUERY_ATTRACTION = "QUERY_ATTRACTION" +QUERY_HOTEL = "QUERY_HOTEL" +QUERY_TRANSPORT = "QUERY_TRANSPORT" +QUERY_WEATHER = "QUERY_WEATHER" +OTHERS = "OTHERS" + +classifer = [TRAVEL_PLAN, QUERY_ATTRACTION, QUERY_HOTEL, QUERY_TRANSPORT, QUERY_WEATHER, OTHERS] + +INST_MAP = { + TRAVEL_PLAN :PESUEDE_CODE, + QUERY_ATTRACTION :ATTRACTION_INST, + QUERY_HOTEL:HOTEL_INST, + QUERY_TRANSPORT :TRANSPORT_INST, + QUERY_WEATHER :WEATHER_INST, + OTHERS:OTHER_INST +} + +FINAL_PMT_MAP = { + TRAVEL_PLAN :PLANNER_FINAL_PROMPT, + QUERY_ATTRACTION :GENERAL_FINAL_PROMPT, + QUERY_HOTEL:GENERAL_FINAL_PROMPT, + QUERY_TRANSPORT :GENERAL_FINAL_PROMPT, + QUERY_WEATHER :WEATHER_FINAL_PROMPT, + OTHERS: GENERAL_FINAL_PROMPT + +} + +TOOL_LIST_MAP = { + TRAVEL_PLAN :[QueryAccommodations, QueryAttractions, QueryTransports, PlanSummary, QueryWeather], + QUERY_ATTRACTION :[QueryAttractions], + QUERY_HOTEL:[QueryAccommodations], + QUERY_TRANSPORT :[QueryTransports], + QUERY_WEATHER : [QueryWeather], + OTHERS:[] +} + +intents = { + TRAVEL_PLAN :"询问旅行规划,问题中要求旅游项目日程安排、交通查询、查询当地住宿等方面的能力", + QUERY_ATTRACTION :"查询旅游项目、景区、旅游活动", + QUERY_HOTEL: "仅查询酒店和住宿信息", + QUERY_TRANSPORT : "与现实中出行、乘坐交通、如高铁、动车、飞机、火车等相关的意图", + QUERY_WEATHER :"包括气温、湿度、降水等与天气、天气预报相关的意图", + OTHERS :"与旅游场景不相干的查询" +} + + + +class TalkShowAgent(ToollessAgent, ABC): + def __init__(self, llm, prompt="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:'\ + 目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐,请利用你的知识回答问题,这是用户的问题:{query}", + **kwargs): + super().__init__(llm, prompt, **kwargs) + self.query = "" + + def _build_agent_prompt(self, **kwargs): + return self.prompt.format( + query=self.query + ) + + +class TravelAgent: + def __init__(self, base_url, api_key, llm_name): + self.llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + base_url=base_url, api_key=api_key, llm_name=llm_name).run + + def route_query(self, query): + router_agent = RouterAgent(llm=self.llm, intents=intents) + classify = router_agent.run(query).answer + if classify not in classifer or classify == OTHERS: + return TalkShowAgent(llm=self.llm) + return RecipeAgent(name=classify, + description="你的名字叫昇腾智搜,是一个帮助用户完成旅行规划的助手,你的能力范围包括:目的地推荐、行程规划、交通信息查询、酒店住宿推荐、旅行攻略推荐", + llm=self.llm, + tool_list=TOOL_LIST_MAP[classify], + recipe=INST_MAP[classify], + max_steps=3, + max_token_number=4096, + final_prompt=FINAL_PMT_MAP[classify]) + + def run(self, query, stream): + agent = self.route_query(query) + return agent.run(query, stream=stream) + + +def get_args(): + parse = argparse.ArgumentParser() + parse.add_argument("--model_name", type=str, default="Qwen1.5-32B-Chat", help="OpenAI客户端模型名") + parse.add_argument("--base_url", type=str, default="http://10.44.115.108:1055/v1", help="OpenAI客户端模型地址") + parse.add_argument("--api_key", type=str, default="EMPTY", help="OpenAI客户端api key") + return parse.parse_args().__dict__ + +if __name__ == "__main__": + args = get_args() + base_url = args.pop("base_url") + api_key = args.pop("api_key") + llm_name = args.pop("model_name") + + llm = get_llm_backend(backend=BACKEND_OPENAI_COMPATIBLE, + base_url=base_url, api_key=api_key, llm_name=llm_name).run + query = "帮我制定一份从北京到上海6天的旅游计划" + + travel_agent = TravelAgent(base_url, api_key, llm_name) + res = travel_agent.run(query, stream=False) + if isinstance(res, AgentRunResult): + logger.info("-----------run agent success-------------") + logger.info(res.answer) + else: + for char in res: + logger.debug(char) diff --git a/mxAgent/samples/tools/tool_cost_enquiry.py b/mxAgent/samples/tools/tool_cost_enquiry.py deleted file mode 100644 index ebe9dbe1c..000000000 --- a/mxAgent/samples/tools/tool_cost_enquiry.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. - - -from typing import Union -import json - -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 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 = json.loads(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_query_city.py b/mxAgent/samples/tools/tool_query_city.py deleted file mode 100644 index bc20932dd..000000000 --- a/mxAgent/samples/tools/tool_query_city.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- 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 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 deleted file mode 100644 index 937a11af6..000000000 --- a/mxAgent/samples/tools/tool_query_distance_matrix.py +++ /dev/null @@ -1,40 +0,0 @@ -from loguru import logger - -from agent_sdk.toolmngt.api import API -from agent_sdk.toolmngt.tool_manager import ToolManager - - -@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', 'flight' and 'taxi'."} - } - - output_parameters = { - 'origin': {'type': 'str', 'description': 'The origin city of the journey.'}, - 'destination': {'type': 'str', 'description': 'The destination city of your journey.'}, - 'cost': {'type': 'str', 'description': 'The cost of the journey.'}, - 'duration': {'type': 'str', 'description': 'The duration of the journey. Format: X hours Y minutes.'}, - 'distance': {'type': 'str', 'description': 'The distance of the journey. Format: Z km.'}, - } - - example = ( - """ - { - "origin": "Paris", - "destination": "Lyon", - "mode": "self-driving" - }""") - - def __init__(self) -> None: - logger.info("QueryGoogleDistanceMatrix API loaded.") - - 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}") -- Gitee From 9cfd2b3493fb111d82ce3b5eec8954154eb0da1b Mon Sep 17 00:00:00 2001 From: hu-xinjia Date: Fri, 3 Jan 2025 17:55:33 +0800 Subject: [PATCH 29/30] fix readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 16877894c..41c6d26c8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ EN|[CN](README.zh.md) [MindSDK](https://www.hiascend.com/software/mindx-sdk) is a software development kit (SDK) launched by Huawei, offering simple and easy-to-use, high-performance APIs and tools. It includes multiple SDKs such as Vision SDK (visual analysis), Index SDK (feature retrieval), and Rec SDK (search recommendation), aiding the Ascend AI processor in empowering various application scenarios. -To help developers quickly master the use of Vision SDK and Index SDK interfaces and rapidly implement business functions, this code repository provides various reference samples developed based on Vision SDK and Index SDK. Users can select the appropriate sample code according to their needs. +To help developers quickly master the use of Vision SDK and Index SDK interfaces and rapidly implement business functions, this code repository provides various reference samples developed based on Vision SDK and Index SDK. The Agent SDK provides interfaces to build agent application. Users can select the appropriate sample code according to their needs. ## Main Directory Structure and Description | 目录 | 说明 | |---|-----------------------------------------------| @@ -12,6 +12,7 @@ To help developers quickly master the use of Vision SDK and Index SDK interfaces | [VisionSDK](./VisionSDK) | Vision SDK official sample directory | | [tutorials](./tutorials) | Vision SDK official tutorials directory | | [IndexSDK](./IndexSDK) | Index SDK offical sample directory | +| [AgentSDK](./AgentSDK) | Agent SDK and offical sample directory | ## Related Websites -- Gitee From 44a787ca43d557b3563a847b4b0257f04444e702 Mon Sep 17 00:00:00 2001 From: hu-xinjia Date: Fri, 3 Jan 2025 18:08:08 +0800 Subject: [PATCH 30/30] fix ddgs --- AgentSDK/samples/tools/duck_search.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/AgentSDK/samples/tools/duck_search.py b/AgentSDK/samples/tools/duck_search.py index 9b4231d90..f99a79129 100644 --- a/AgentSDK/samples/tools/duck_search.py +++ b/AgentSDK/samples/tools/duck_search.py @@ -55,11 +55,8 @@ def call_duck_duck_go_search(query: str, count: int) -> List[str]: retry = 1 while retry <= 3: try: - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" - } logger.debug(f"search DuckDuckGo({query}, {count})") - results = DDGS(headers=headers).text(query, backend="html", max_results=count) + results = DDGS().text(query, backend="html", max_results=count) return results except Exception as e: retry += 1 -- Gitee