当前位置:   article > 正文

最简明的大模型agent教程_怎么做大模型agent

怎么做大模型agent

大模型相关目录

大模型,包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容
从0起步,扬帆起航。

  1. 大模型应用向开发路径及一点个人思考
  2. 大模型应用开发实用开源项目汇总
  3. 大模型问答项目问答性能评估方法
  4. 大模型数据侧总结
  5. 大模型token等基本概念及参数和内存的关系
  6. 大模型应用开发-华为大模型生态规划
  7. 从零开始的LLaMA-Factory的指令增量微调
  8. 基于实体抽取-SMC-语义向量的大模型能力评估通用算法(附代码)
  9. 基于Langchain-chatchat的向量库构建及检索(附代码)
  10. 一文教你成为合格的Prompt工程师
  11. 最简明的大模型agent教程


一、Agent简介

大模型Agent是结合了大规模神经网络模型和自主计算实体的技术,它具备强大的表达、学习和交互能力,能够在无人干预的情况下,根据环境信息自主决策和控制行为。

简单而言之,agent是增强大模型能力的技术方案路径。主要包括:工具、工具选择方案,大模型工具应用3个部分。运行大体流程:

1用户给出一个任务(Prompt) -> 2思考(Thought) -> 3行动(Action) -> 4观察(Observation)
在这里插入图片描述

二、langchain-chatchat下的Agent开发

更详细地流程可参考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")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

但我们仍不知道大模型、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)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187

三、langchain的Agent开发

主控:

prompt = get_tools_agent_prompt(query)
agent = get_tools_agent()
response = agent.run(prompt)
  • 1
  • 2
  • 3

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
# 获取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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
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 "您输入的参数数量不匹配,请仔细核对。"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
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}。"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

最后介绍一下 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通常使用
其他的都作妖

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/546998
推荐阅读
相关标签
  

闽ICP备14008679号