赞
踩
大模型,包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容
从0起步,扬帆起航。
大模型Agent是结合了大规模神经网络模型和自主计算实体的技术,它具备强大的表达、学习和交互能力,能够在无人干预的情况下,根据环境信息自主决策和控制行为。
简单而言之,agent是增强大模型能力的技术方案路径。主要包括:工具、工具选择方案,大模型工具应用3个部分。运行大体流程:
1用户给出一个任务(Prompt) -> 2思考(Thought) -> 3行动(Action) -> 4观察(Observation)
更详细地流程可参考GitHub wiki介绍
于tools_select.py预设新增的工具、工具描述等信息。
于下述路径下新增同名新工具py文件,并进行工具内容定义。
工具样例如下:
from pydantic import BaseModel, Field import requests from configs.kb_config import SENIVERSE_API_KEY def weather(location: str, api_key: str): url = f"https://api.seniverse.com/v3/weather/now.json?key={api_key}&location={location}&language=zh-Hans&unit=c" response = requests.get(url) if response.status_code == 200: data = response.json() weather = { "temperature": data["results"][0]["now"]["temperature"], "description": data["results"][0]["now"]["text"], } return weather else: raise Exception( f"Failed to retrieve weather: {response.status_code}") def weathercheck(location: str): return weather(location, SENIVERSE_API_KEY) class WeatherInput(BaseModel): location: str = Field(description="City name,include city and county")
但我们仍不知道大模型、langchain框架选择工具、使用工具、进行输出等过程的调度原理,可参考如下代码:
from langchain.utilities import ArxivAPIWrapper from langchain_experimental.tools import PythonAstREPLTool from typing import Dict, Tuple import os import json arxiv = ArxivAPIWrapper() python=PythonAstREPLTool() class ElectricityBillTool: def run(self, name, start_date, end_date): # 假设这里执行了某些操作来查询电费 # 为了简化,我们直接返回一条测试信息 return f"电费查询结果:姓名:{name}, 期间:{start_date} 到 {end_date}, 电费:100元" def tool_wrapper_for_model(tool, expects_kwargs=True): def tool_(args_json): args = json.loads(args_json) if expects_kwargs: return tool.run(**args) # 使用 **args 将字典展开为关键字参数 else: # 如果 run 方法期望位置参数,我们假设所有参数都聚合在一个叫 'query' 的键下 return tool.run(args['query']) # 使用位置参数调用 return tool_ electricity_bill_tool = ElectricityBillTool() # 以下是给模型看的工具描述: TOOLS = [ { 'name_for_human': 'arxiv', 'name_for_model': 'Arxiv', 'description_for_model': 'A wrapper around Arxiv.org Useful for when you need to answer questions about Physics, Mathematics, Computer Science, Quantitative Biology, Quantitative Finance, Statistics, Electrical Engineering, and Economics from scientific articles on arxiv.org.', 'parameters': [{ "name": "query", "type": "string", "description": "the document id of arxiv to search", 'required': True }], 'tool_api': tool_wrapper_for_model(arxiv, expects_kwargs=False) }, { 'name_for_human': 'python', 'name_for_model': 'python', 'description_for_model': "A Python shell. Use this to execute python commands. When using this tool, sometimes output is abbreviated - Make sure it does not look abbreviated before using it in your answer. " "Don't add comments to your python code.", 'parameters': [{ "name": "query", "type": "string", "description": "a valid python command.", 'required': True }], 'tool_api': tool_wrapper_for_model(python, expects_kwargs=False) }, { 'name_for_human': 'electricity_bill', 'name_for_model': 'ElectricityBill', 'description_for_model': '查询电费工具,根据姓名、开始时间和结束时间查询电费。', 'parameters': [ {"name": "name", "type": "string", "description": "姓名", 'required': True}, {"name": "start_date", "type": "string", "description": "开始时间", 'required': True}, {"name": "end_date", "type": "string", "description": "结束时间", 'required': True} ], 'tool_api': tool_wrapper_for_model(electricity_bill_tool, expects_kwargs=True) } ] TOOL_DESC = """{name_for_model}: Call this tool to interact with the {name_for_human} API. What is the {name_for_human} API useful for? {description_for_model} Parameters: {parameters} Format the arguments as a JSON object.""" REACT_PROMPT = """Answer the following questions as best you can. You have access to the following tools: {tool_descs} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can be repeated zero or more times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: {query}""" def build_planning_prompt(TOOLS, query): tool_descs = [] tool_names = [] for info in TOOLS: tool_descs.append( TOOL_DESC.format( name_for_model=info['name_for_model'], name_for_human=info['name_for_human'], description_for_model=info['description_for_model'], parameters=json.dumps( info['parameters'], ensure_ascii=False), ) ) tool_names.append(info['name_for_model']) tool_descs = '\n\n'.join(tool_descs) tool_names = ','.join(tool_names) prompt = REACT_PROMPT.format(tool_descs=tool_descs, tool_names=tool_names, query=query) return prompt def parse_latest_plugin_call(text: str) -> Tuple[str, str]: i = text.rfind('\nAction:') j = text.rfind('\nAction Input:') k = text.rfind('\nObservation:') if 0 <= i < j: # If the text has `Action` and `Action input`, if k < j: # but does not contain `Observation`, # then it is likely that `Observation` is ommited by the LLM, # because the output text may have discarded the stop word. text = text.rstrip() + '\nObservation:' # Add it back. k = text.rfind('\nObservation:') if 0 <= i < j < k: plugin_name = text[i + len('\nAction:'):j].strip() plugin_args = text[j + len('\nAction Input:'):k].strip() return plugin_name, plugin_args return '', '' def use_api(tools, response): use_toolname, action_input = parse_latest_plugin_call(response) if use_toolname == "": return "no tool founds" used_tool_meta = list(filter(lambda x: x["name_for_model"] == use_toolname, tools)) if len(used_tool_meta) == 0: return "no tool founds" api_output = used_tool_meta[0]["tool_api"](action_input) return api_output def get_model_response(prompt, stop): from openai import OpenAI client = OpenAI() completion = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": prompt} ], stream=False, stop = stop, ) response = completion.choices[0].message.content return response def main(query, choose_tools): prompt = build_planning_prompt(choose_tools, query) # 组织prompt stop = ["Observation:", "Observation:\n"] print(prompt) response = get_model_response(prompt, stop) while "Final Answer:" not in response: # 出现final Answer时结束 api_output = use_api(choose_tools, response) # 抽取入参并执行api api_output = str(api_output) # 部分api工具返回结果非字符串格式需进行转化后输出 if "no tool founds" == api_output: break print("\033[32m" + response + "\033[0m" + "\033[34m" + ' ' + api_output + "\033[0m") prompt = prompt + response + ' ' + api_output # 合并api输出 response = get_model_response(prompt, stop) print("\033[32m" + response + "\033[0m") if __name__ == "__main__": query = "查一下张三2024年1月的电费" # 所提问题 choose_tools = TOOLS # 选择备选工具 print("=" * 10) main(query, choose_tools)
主控:
prompt = get_tools_agent_prompt(query)
agent = get_tools_agent()
response = agent.run(prompt)
get_tools_agent_prompt:
def get_tools_agent_prompt(query, history=None): from datetime import datetime current_time = datetime.now() prompt = ( f""" 如果查询未指定年/月,请参考当前时刻:{current_time}。 请用中文思考及回复。 优先考虑使用工具解决问题,当工具无法解决时,尝试根据经验直接回答。 最终回复时若内容较多需要注意保持可读性,添加合理的换行。 下面是用户问题: """ + query ) return prompt
get_tools_agent
def get_tools_agent():
llm = get_llm()
tools = get_tools()
agent = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
max_iterations=5,
handle_parsing_errors=True,
)
return agent
# 获取llm def get_llm(): api_key = os.getenv("PROXY_API_KEY") api_url = os.getenv("PROXY_SERVER_URL") api_url = api_url.split('/chat')[0] model = os.getenv("PROXYLLM_BACKEND") if "openai" in api_url: llm = ChatOpenAI(temperature=0,model=model,openai_api_base=api_url,openai_api_key=api_key,) elif "bigmodel" in api_url: llm = ChatZhipuAI(temperature=0.01,api_key=api_key,model="glm-4",) elif "dashscope" in api_url: from langchain_community.chat_models.tongyi import ChatTongyi llm = ChatTongyi(model="qwen-max",top_p=0.01,streaming=True,dashscope_api_key=api_key) return llm
def get_tools(): llm=get_llm() tools = [ Tool( name="查询负荷数据及同比环比", func=calculate_growth, description=""" 当你想要查询某地负荷(最大值、最小值、平均值),查询最大/最小值发生时间,或者查询同比或环比变化的时候很有用,该工具返回某地负荷数据及同比变化率。 输入应该包括地区(公司)名(如果未指定,则默认为直供区)、时间(仅允许YYYY,YYYY-QQ,YYYY-MM,YYYY-MM-DD,YYYY-节气五种格式,也即年、年-季度、年-月、年-月-日,年-节气)、最大/最小/平均(若未明确提及,默认为平均)、同比/环比(可以不输入),以|分隔。 查询某一年时,仅输入年份即可。 例如:上海|2023|最大|环比、广州|2023-Q2|最小|环比、深圳|2021-07|平均、北京|2023-05-01|最大。 """, ) ] tools.append( Tool( name="查询负荷新高(低)", func=find_record_breaking_loads, description=""" 当你想要查询某地负荷几创新高或几创新低的时候很有用,该工具返回创下新高/低的次数,及对应的详细信息。 输入应该包括地区(公司)名(如果未指定,则默认为直供区)、基础时间(仅允许YYYY,YYYY-QQ,YYYY-MM,YYYY-MM-DD,YYYY-节气五种格式,也即年、年-季度、年-月、年-月-日,年-节气)、对比时间(如未指定,默认与基础时间的前一年做对比,时间类型同前者)、高/低,以|分隔。 例如:上海|2023-Q1|2022-Q1|高、广州|2023-寒露|2021-寒露|低、深圳|2023|2021|高。 """, ), ) return tools
def calculate_growth(context): # 读取CSV文件 # df = pd.read_csv('./data/load.csv') # 分割输入参数 params = context.split("|") # 确保至少有三个参数 if len(params) < 3: return "至少需要三个参数(region, time_period, compare_type)。" # 提取参数,如果缺少第四个参数,则默认为"同比" elif len(params) == 3: region, time_period, compare_type = params[:3] growth_type = None elif len(params) == 4: region, time_period, compare_type, growth_type = context.split("|") else: return "您输入的参数数量不匹配,请仔细核对。"
def find_record_breaking_loads(context): # 分割输入参数 params = context.split("|") if len(params) not in [3, 4]: return "您输入的参数数量不匹配,请仔细核对。" region = params[0] current_time_period = params[1] high_low = params[-1] # compare_time_period = params[2] if len(params) == 4 else None compare_time_period = params[2] if len(params) == 4 else str(int(params[1][:4])-1) + params[1][4:] # 筛选指定地区的数据 df_region = df[df['公司'] == region] # 解析当前时间段 try: current_period_parsed = parse_time_period(df, current_time_period) except ValueError as e: return f"当前时间段解析错误:{e}" # 解析对比时间段,如果有 if compare_time_period: try: compare_period_parsed = parse_time_period(df, compare_time_period) except ValueError as e: return f"对比时间段解析错误:{e}" else: compare_period_parsed = None # 根据时间段获取数据 if isinstance(current_period_parsed, tuple): current_start_date, current_end_date = current_period_parsed df_current = df_region[(df_region['时间'] >= current_start_date) & (df_region['时间'] < current_end_date)] if compare_period_parsed: compare_start_date, compare_end_date = compare_period_parsed df_compare = df_region[(df_region['时间'] >= compare_start_date) & (df_region['时间'] < compare_end_date)] else: # 默认比较去年同期 year = int(current_start_date.split('-')[0]) previous_year = year - 1 df_compare = df_region[(df_region['时间'] >= f"{previous_year}-{current_start_date[5:]}") & (df_region['时间'] < f"{previous_year}-{current_end_date[5:]}")] # 比较负荷值 if high_low == '高': reference_load = df_compare['负荷'].max() else: reference_load = df_compare['负荷'].min() # 计算创纪录的次数 if high_low == '高': df_records = df_current[df_current['负荷'] > reference_load] else: df_records = df_current[df_current['负荷'] < reference_load] # 结果处理 if not df_records.empty: record_count = len(df_records) top_record = df_records.sort_values(by="负荷", ascending=(high_low != '高')).iloc[0] return f"{region}在{current_time_period}相较{compare_time_period}共有{record_count}次创下新{high_low},其中最{high_low}负荷为{top_record['负荷']}MW,时间为{top_record['时间']}。" else: return f"{region}在{current_time_period}相较{compare_time_period}未创新{high_low}。"
最后介绍一下 AgentType.ZERO_SHOT_REACT_DESCRIPTION
首先介绍一些常见的关键词:
ReAct:由单词“Reason”和“Act”组合而成,前者对应于推理,即大模型的通用文本逻辑判断能力,或者说是对问题进行思考和拆解的能力;后者对应于行动,即具备专业知识的特定领域精确回答能力,或者说是调用外部工具的能力。ReAct顾名思义就是把思考和行动相结合,通过二者的依次迭代执行完成任务。
Zero-shot:零样本,或者说是无记忆。在运行时,只考虑与当前代理的一次交互,不保留对话历史。
Conversational:引入了对话历史,因为有记忆了,所以需要初始化代理时引入memory参数。其一个缺点是可能无法执行复杂的Tool调用任务。
Chat:常规情况下以OpenAI方式初始化LLM,此类Agent可以以ChatOpenAI方式初始化模型。前者是更通用的接口,用于与不同类型的语言模型进行交互,可以与各种LLM模型集成。ChatOpenAI接口是对其的高级封装,更专注于对话式交互。
以上内容转自:https://wangjn.blog.csdn.net/article/details/134806188
ZERO_SHOT_REACT_DESCRIPTION通常使用
其他的都作妖
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。