AI大模型时代下运维开发探索第一篇:ReAct工程初探

news2024/11/26 12:34:57

引子

人工智能大模型的出现,已渐渐地影响了我们的日常生活和工作方式。生活中无处不在的AI,使我们的生活变得更加智能和便捷。工作中,AI大模型的高效和精准,极大地提升了我们解决问题的效率。

是的,我们不能忽视AI大模型对运维开发的巨大影响和潜力。本系列文章旨在探索这一可能性,试图解答一个问题——AI大模型是否能够融入我们的运维开发中,为我们带来更大的便利和价值。我们期待通过这个探索,找到一个AI大模型与运维开发相融合的新方向,开启一种崭新的、更高效的运维开发方式。

真正的工具人

平时,“工具人"这个词,常常被用来进行一种幽默而略带自嘲的表达。我们都可能有过这样的经历,面对一堆琐碎而杂乱的任务,无奈地笑称自己为"工具人”。然而,为什么这个词会在这里被提出来?为了我们在后面编程过程中,对使用AI大模型有个形象的认知:它是一个具备操作工具能力的人工智能模型

其实,你们可能已经猜到了,我要讲的正是AI大模型中的比较最常见的使用结构:Agent + Tool。

在各种新兴的框架驱动下(比如LangChain等),人工智能不再只是我们口中的“工具”,而是变身成为一个真正意义上的“工具人”。他们是真正会使用工具的行动者,而非工具本身。他们不仅能接受和执行我们的指令,更能够熟练运用各种工具去解决问题,创造价值。

“Agent"这个词在计算机科学中有着悠久的历史。在早期,它被用于形容一种代理或者媒介,承担着在网络世界与现实世界之间建立桥梁的重任。以浏览器的"User Agent"为例,它实际上是浏览器在与网络服务器进行交互时,声明自己身份的一种方式。而在这个过程中,浏览器可以被看作是用户(User)和互联网内容(Web content)之间的"Agent”。

随着人工智能技术的快速发展,"Agent"这个词的含义也在逐渐丰富和深化。在AI领域,"Agent"常常被用来形容那些可以自主进行决策,响应环境变化,并对各种指令进行执行的模型或系统。

了解完Agent + Tool 的背景之后,我们下面切入正题看看LangChain中的Agent和Tool是如何运作的。

LangChain 下的 Agent + Tool 实践

简单实践

下面贴一个最简单常见的例子:查看机器的运行时长。

import os
from subprocess import Popen, PIPE
from langchain.llms import OpenAI
from langchain.tools import StructuredTool
from langchain.agents import initialize_agent, AgentType

def ssh(command:str, host: str, username: str = "root") -> str:
    """A tool that can connect to a remote server and execute commands to retrieve returned content."""
    return os.popen(f"ssh {host} -l{username} '{command}'").read()

agent = initialize_agent(
    [StructuredTool.from_function(ssh)], 
    OpenAI(temperature=0), 
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, 
    verbose=True
)
agent.run("帮我看一下 8.130.35.2 这台机器运行多久了")

在langchain中,提供了tools相关的工具可以非常轻松地将一个函数转换成tool,然后在初始化agent的时候,将这个工具引入:这个机器人(Agent)拿上工具(Tool)之后,变成了一个工具人。

然后我们来观察一下这个工具人是如何思考并使用工具的:

> Entering new AgentExecutor chain...

Action:
```
{
  "action": "ssh",
  "action_input": {
    "command": "uptime",
    "host": "8.130.35.2"
  }
}
```

Observation:  15:48:44 up 25 days, 41 min,  0 users,  load average: 1.04, 1.48, 2.20

Thought: I have the answer
Action:
```
{
  "action": "Final Answer",
  "action_input": "This machine has been running for 25 days and 41 minutes."
}
```

> Finished chain.
  1. Agent通过问题来思考,它需要一个工具能够查看 8.130.35.2 这台机器的运行时长。
  2. Agent盘点了他的工具之后,发现他有一个ssh工具,这个工具可能能帮助他获取机器的运行时长。
  3. 在大语言模型的训练数据中,有有关ssh和uptime的只是,所以Agent能够判断出在ssh工具中执行uptime能够获取到他需要的信息。
  4. Agent调用ssh工具传入uptime命令,获取到了这台目标机器的运行时长信息。
  5. Agent理解了一下这些信息,去除了load等无关信息,并组织了一下语言,进行了输出返回。

看了这个例子很多就会好奇,原本大语言模型不就是只能对对话,像纸上谈兵一样吗?怎么一下子就能做怎么多事情?ssh上去既然能执行uptime,那是不是执行rm -rf /也是一句话的事情?

非常好,我们带着这些问题继续往下看,看来看看一个大语言模型是如何被安上“四肢”的:

上面这个例子总共调用了2次大语言模型的接口,我们分别将两次提示词(prompt)给提取了出来:

第一次提示词

System: Respond to the human as helpfully and accurately as possible. You have access to the following tools:

ssh: ssh(command: str, host: str, username: str = 'root') -> str - A tool that can connect to a remote server and execute commands to retrieve returned content., args: {{'command': {{'title': 'Command', 'type': 'string'}}, 'host': {{'title': 'Host', 'type': 'string'}}, 'username': {{'title': 'Username', 'default': 'root', 'type': 'string'}}}}

Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).

Valid "action" values: "Final Answer" or ssh

Provide only ONE action per $JSON_BLOB, as shown:

```
{
  "action": $TOOL_NAME,
  "action_input": $INPUT
}
```

Follow this format:

Question: input question to answer
Thought: consider previous and subsequent steps
Action:
```
$JSON_BLOB
```
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
```
{
  "action": "Final Answer",
  "action_input": "Final response to human"
}
```

Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation:.
Thought:
Human: 帮我看一下 8.130.35.2 这台机器运行多久了

第一次调用返回

Action:
```
{
  "action": "ssh",
  "action_input": {
    "command": "uptime",
    "host": "8.130.35.2"
  }
}
```

第二次提示词

System: Respond to the human as helpfully and accurately as possible. You have access to the following tools:

ssh: ssh(command: str, host: str, username: str = 'root') -> str - A tool that can connect to a remote server and execute commands to retrieve returned content., args: {{'command': {{'title': 'Command', 'type': 'string'}}, 'host': {{'title': 'Host', 'type': 'string'}}, 'username': {{'title': 'Username', 'default': 'root', 'type': 'string'}}}}

Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).

Valid "action" values: "Final Answer" or ssh

Provide only ONE action per $JSON_BLOB, as shown:

```
{
  "action": $TOOL_NAME,
  "action_input": $INPUT
}
```

Follow this format:

Question: input question to answer
Thought: consider previous and subsequent steps
Action:
```
$JSON_BLOB
```
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
```
{
  "action": "Final Answer",
  "action_input": "Final response to human"
}
```

Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation:.
Thought:
Human: 帮我看一下 8.130.35.2 这台机器运行多久了

This was your previous work (but I haven't seen any of it! I only see what you return as final answer):

Action:
```
{
  "action": "ssh",
  "action_input": {
    "command": "uptime",
    "host": "8.130.35.2"
  }
}
```


Observation:  16:38:18 up 25 days,  1:30,  0 users,  load average: 0.81, 0.79, 1.06

Thought:

第二次调用返回

I can provide the human with the uptime of the machine
Action:
```
{
  "action": "Final Answer",
  "action_input": "The machine has been running for 25 days, 1 hour and 30 minutes."
}
```
  1. 通过提取出来的提示词,我们可以看到我们教会了大模型用一种固定的格式去进行沟通,同时限定了他的返回必须是 Valid "action" values: "Final Answer" or ssh,也就说要么直接给答案,要么寻求工具的帮助。
  2. 在第一次调用之后,大模型无法推理出结果,于是返回 action: ssh,这样框架层就能知道这是要去调用哪个工具了,于是就把参数传给那个工具,并将执行之后的结果返回 追加到第二次的调用的尾部。
  3. 于是在第二次调用的时候,在大模型的视角来看,就是他请求调用ssh,然后我们真的将调用结果返回给他,于是他能够有充分的信息做推理,就给出了Final Answer,告诉了我们这台机器运行了25天+。
  4. 有兴趣的同学可以将上面的调用直接贴进ChatGPT对话框模拟一下,看看是不是调用的返回就是这些,同时也可以试试其他的LLM是否具有这样交互的能力(针对不同的LLM需要制作不同的PromptTemplate在本段暂不展开)。

看到这里有些同学可能会有点小问题,为什么我们每次调用都是要把全量的数据全部推一遍?我们平时用大模型交谈的时候不是有上下文的吗?其实这是个工程问题:

大语言模型原本就只能做单次的文本的预测,如何实现拟人的交谈呢?在每次对话前,把前面的几轮对话数据也都输入进去,这样就会让人感觉他记得前面的内容–随着对话越来越长,如果每轮对话数据都放进去,模型扛不住了怎么办?那就从最老的删掉一些。这样是不是就和人的遗忘很像了?当聊到第三个话题的时候,可能已经忘了第一个话题聊的是啥。

我们当前使用LLM接口调用的时候,每次都是一个全新的文本预测,所以我们需要每次把全量的数据都推下去。

初识ReAct流程

看到这里,有些同学会有疑问,为什么我自己用提示词(Prompt)去问就没法形成这样的调用工具式的交互,为什么用了框架就能达到这个效果。这就是ReAct,ReAct是Reasoning and Acting缩写,意思是大模型可以根据逻辑推理(Reason),构建完整系列行动(Act),从而达成期望目标。ReAct方式的关键就是协调大语言模型和外部的信息获取,与其他功能交互:大模型是大脑,通过ReAct框架可以让大脑来控制手和脚。

1689221533534-75730dba-ac4d-4866-a213-c66ea4977e81.png

在ReAct流程中,我们可以抓住三个关键的元素:

思考(Thought): 思考是由大模型创建的,为其行为和决定提供理论支撑。我们可以通过分析大模型的思考过程,来评估其即将采取的行动是否符合逻辑。它作为一个关键指标,能够帮助我们判断其决策的合理性。相比于人类的决策,Thought的存在赋予了大模型更出色的可解释性和可信度。

行动(Act): 行动代表大模型认为需要采取的具体行为。行动一般由两个部分构成:动作和目标,这在编程中对应着API名称和其输入参数。大模型的一大优点在于,它可以根据思考的结果,选择合适的API并生成所需的参数。这确保了ReAct框架在执行方面的实用性。

观察(Obs): 观察代表大模型如何获取外部输入。它就像大模型的感知系统,将环境的反馈信息同步给大模型,帮助它进一步进行分析或者决策。

既然这个ReAct流程这么厉害,能不能实现一些更复杂的思考呢?下面就是一个复杂的思维链的例子。

思维链实践

这是一个抓取博客rss订阅然后分析的例子


from typing import Dict
import sys
import traceback
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr

from langchain.llms import OpenAI
from langchain.tools import StructuredTool
from langchain.agents import initialize_agent, AgentType


def python(code_str: str, return_context: bool=False) -> Dict:
    """This is a Python execution tool. You can input a piece of Python code, and it will return the corresponding execution results. By default, it returns the first 1000 characters of both stdout and stderr. It's recommended to use the print() function to directly display the results."""
    # 为输出和错误创建StringIO对象,以便我们可以捕获它们
    stdout = StringIO()
    stderr = StringIO()
    return_head = 1000

    context = {}

    try:
        # 重定向stdout和stderr,执行代码
        with redirect_stdout(stdout), redirect_stderr(stderr):
            exec(code_str, context)
    except Exception:
        stderr.write(traceback.format_exc())

    # 获取执行后的stdout, stderr和context
    stdout_value = stdout.getvalue()[0:return_head]
    stderr_value = stderr.getvalue()[0:return_head]

    if return_context == True:
        return {"stdout": stdout_value, "stderr": stderr_value, "context": context}
    else:
        return {"stdout": stdout_value, "stderr": stderr_value, "context": {}}


agent = initialize_agent([StructuredTool.from_function(python)], OpenAI(temperature=0.3), agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
resp = agent.run("帮我利用工具总结一下 https://elastic.aiops.work/blog/feed 这个xml的博客订最近有什么更新")
print("=== resp ===")
print(resp)

我们并不提供给大模型任何抓取工具,我们直接给他一个python的执行器,看看他能不能把我们要的东西抓下来,我们来看一下执行过程:

> Entering new AgentExecutor chain...
Thought: The user is asking for a summary of updates from an XML blog feed. I will need to fetch the XML from the provided URL and parse it to extract the relevant information. Python has several libraries that can help with this, such as 'requests' for fetching the data and 'xml.etree.ElementTree' for parsing the XML. I'll write a Python script to do this and use the execute_python tool to run it.

Action:
{
  "action": "python",
  "action_input": {
    "code_str": "import requests\nfrom xml.etree import ElementTree as ET\n\n# Fetch the XML data\nresponse = requests.get('https://elastic.aiops.work/blog/feed')\n\n# Parse the XML\nroot = ET.fromstring(response.content)\n\n# Extract the relevant information\nupdates = [{'title': item.find('title').text, 'link': item.find('link').text, 'pubDate': item.find('pubDate').text} for item in root.findall('.//item')]\n\n# Print the updates\nfor update in updates:\n    print(f"Title: {update['title']}\nLink: {update['link']}\nPublished Date: {update['pubDate']}\n")",
    "return_context": false
  }
}

Observation: {'stdout': '', 'stderr': 'Traceback (most recent call last):\n  File "/Users/****/test.py", line 22, in python\n    exec(code_str, context)\n  File "<string>", line 15\n    print(f"Title: {update['title']}\n^\nSyntaxError: EOL while scanning string literal\n', 'context': {}}
Thought:The Python code failed to execute due to a SyntaxError. It seems like the print statement in the code is not properly formatted. I need to correct the print statement and try again.

Action:
{
  "action": "python",
  "action_input": {
    "code_str": "import requests\nfrom xml.etree import ElementTree as ET\n\n# Fetch the XML data\nresponse = requests.get('https://elastic.aiops.work/blog/feed')\n\n# Parse the XML\nroot = ET.fromstring(response.content)\n\n# Extract the relevant information\nupdates = [{'title': item.find('title').text, 'link': item.find('link').text, 'pubDate': item.find('pubDate').text} for item in root.findall('.//item')]\n\n# Print the updates\nfor update in updates:\n    print(f"Title: {update['title']}\nLink: {update['link']}\nPublished Date: {update['pubDate']}\n")",
    "return_context": false
  }
}

Observation: {'stdout': 'Title: Being one of the only woman in her computer science program didn’t stop Hannah Mudge from pursuing her dream of becoming a software engineer\nLink: https://www.elastic.co/blog/culture-hannah-mudge-dream-of-becoming-software-engineer\nPublished Date: Fri, 07 Jul 2023 00:00:00 GMT\n\nTitle: Accelerating R&D in pharma with Elasticsearch, ESRE, LLMs, and LangChain — Part 1\nLink: https://www.elastic.co/blog/research-development-pharma-elasticsearch-esre-llms-langchain-1\nPublished Date: Fri, 07 Jul 2023 00:00:00 GMT\n\nTitle: Being one of the only woman in her computer science program didn’t stop Hannah Mudge from pursuing her dream of becoming a software engineer\nLink: https://www.elastic.co/blog/culture-hannah-mudge-dream-of-becoming-software-engineer\nPublished Date: Fri, 07 Jul 2023 00:00:00 GMT\n\nTitle: Accelerating R&D in pharma with Elasticsearch, ESRE, LLMs, and LangChain — Part 1\nLink: https://www.elastic.co/blog/research-development-pharma-elasticsearch-esre-llms-langchain-1\nPublish', 'stderr': '', 'context': {}}
Thought:The Python code executed successfully and returned the recent updates from the XML blog feed. The updates include the title, link, and published date of the blog posts. I will now format this information into a readable format for the user.

Action:
{
  "action": "Final Answer",
  "action_input": "Here are the recent updates from the blog:\n\n1. Title: Being one of the only woman in her computer science program didn’t stop Hannah Mudge from pursuing her dream of becoming a software engineer\nLink: https://www.elastic.co/blog/culture-hannah-mudge-dream-of-becoming-software-engineer\nPublished Date: Fri, 07 Jul 2023 00:00:00 GMT\n\n2. Title: Accelerating R&D in pharma with Elasticsearch, ESRE, LLMs, and LangChain — Part 1\nLink: https://www.elastic.co/blog/research-development-pharma-elasticsearch-esre-llms-langchain-1\nPublished Date: Fri, 07 Jul 2023 00:00:00 GMT"
}


> Finished chain.
=== resp ===
Here are the recent updates from the blog:

1. Title: Being one of the only woman in her computer science program didn’t stop Hannah Mudge from pursuing her dream of becoming a software engineer
Link: https://www.elastic.co/blog/culture-hannah-mudge-dream-of-becoming-software-engineer
Published Date: Fri, 07 Jul 2023 00:00:00 GMT

2. Title: Accelerating R&D in pharma with Elasticsearch, ESRE, LLMs, and LangChain — Part 1
Link: https://www.elastic.co/blog/research-development-pharma-elasticsearch-esre-llms-langchain-1
Published Date: Fri, 07 Jul 2023 00:00:00 GMT
  1. 大模型上来就写了一段Python脚本,通过requests获取内容,然后通过xml来进行解析,别问我他为什么连正文都不用看,直接知道字段。你问问你手边的大模型他知不知道rss xml的格式。只是我们平时不常接触这些,所以肯定要先抓取内容过来看看,然后才能知道要取哪些字段。
  2. 很可惜原本第一把就能跑通,但是他忘了python下普通引号包裹的字符串是不能换行的,于是报错了。这个时候思维链出现了,他根据返回结果总结是print的地方报错了,所以他需要调整print部分的代码。这个就有点惊艳了,如果我写完第一段代码碰到这个报错,第一时间还不一定反应过来,可能会以为是字段搞错之类的。

The Python code failed to execute due to a SyntaxError. It seems like the print statement in the code is not properly formatted. I need to correct the print statement and try again.

  1. 第二次他就立马就改对了,print写在了同一行,我把大模型生成的两次代码都贴一下,大家可以比较一下:
# 第一次代码

import requests
from xml.etree import ElementTree as ET

# Fetch the XML data
response = requests.get('https://elastic.aiops.work/blog/feed')

# Parse the XML
root = ET.fromstring(response.content)

# Extract the relevant information
updates = [{'title': item.find('title').text, 'link': item.find('link').text, 'pubDate': item.find('pubDate').text} for item in root.findall('.//item')]

# Print the updates
for update in updates:
    print(f"Title: {update['title']}
Link: {update['link']}
Published Date: {update['pubDate']}
")
# 第二次代码
import requests
from xml.etree import ElementTree as ET

# Fetch the XML data
response = requests.get('https://elastic.aiops.work/blog/feed')

# Parse the XML
root = ET.fromstring(response.content)

# Extract the relevant information
updates = [{'title': item.find('title').text, 'link': item.find('link').text, 'pubDate': item.find('pubDate').text} for item in root.findall('.//item')]

# Print the updates
for update in updates:
    print(f"Title: {update['title']}\nLink: {update['link']}\nPublished Date: {update['pubDate']}\n")

通过第二个例子,我们可以看到,当大模型调用工具报错的时候,他会用自己的已有知识对报错信息做分析,调整修改代码,直到跑成功。由此看来,这个“工具人”不仅仅是有工具,他更是具备人的推理和反思能力,能够通过多次尝试将问题解决。

看到这里,是不是有些同学已经跃跃欲试,想要去试两把了,下面我们来讲讲在普通的编程中如何融合Agent 和 Tool。

普通编程模型如何融合 Agent + Tool

由MapReduce编程范式引发思考

在分布式计算领域,通过MapReduce这样的编程范式,让分布式计算变得简单,不需要了解分布式通信同步原理,写完mapper和reducer,就能在上千台服务器的集群上运行程序,还不用担心出现机器故障等各问题,而今mapper和reducer也不用写了,一个SELECT SQL下去就会自动被拆成若干个mapper和reducer去运行。

事实上,MapReduce这样的编程范式,非常像一个大公司的工作结构。工作首先会被分解并分配到许多小组(Mapper),这些小组相当于公司中的一线员工,他们是执行具体任务的人,每个小组都在各自的小领域内专注于自己的工作,处理他们被分配到的数据或任务。

每个人并不需要关心其他部门或者其他生产环节的事情,只需集中注意力完成自己的工作就好。当他们完成他们的任务之后,这些小组或者员工会将他们的成果提交给他们的经理(Reducer)。这些经理会收集和汇总所有的结果,形成一个更高层次的报告或决策。最后,高层(还是Reducer)会根据这些汇总的数据,做出最终的决策或报告。这就像公司的CEO或董事会基于从各个部门收集来的信息,做出全公司范围的决策。

这时候,再让我们看看我们上文制作出来“工具人”,是不是就可以直接被安排进MapReduce模型里面? 乃至于这个“工具人”如果放在高层成为决策层,也没什么大问题?毕竟这个“工具人”的经验和知识可能都远超一些职业经理人。

同时,我们还需要考虑一点,MapReduce结构不一定是复杂工程结构的最优解,之前为什么这样设计是因为调度器都没那么智能。而以后如果调度器本身就是一个大模型,他是否会根据业务场景,编排出比MapReduce更优的结构呢?所以这个甚至都不一定是MapReduce了?是否甚至会糅合进一些社会学的结构进去?我们可以称之为“AI大模型社会学计算架构”?进而我们需要讨论多个AI大模型之间如何交流才能更好地协作解决问题?

针对AI大模型的复杂工程脑洞,我们可以让子弹再飞一会儿。回到当前我们的编程模型中,看看这样的工程结构要如何落地。

装饰器模式

在常见的编程中,装饰器可能一种用得比较多的设计模式,这种模式可以提高代码的可读性和可维护性,并有助于实现关注点的分离。例如在Python的Flask框架中,一个基本的路由可能会看起来像这样:

@app.route('/hello')
def hello_world():
    return 'Hello, World!'

那么 Agent 和 Tool 是不是也可以用装饰器,进入到我们编程中,是的,langchain也提供了 @tool 这样的装饰器,但总感觉好像还是无法编写出上文我们想的那种工程结构。如果只是装饰器,看起来也不复杂,我们可以设计一个简单的装饰器实现:

  • @tool() 与langchain的相同,在一个函数外包了之后,就可以把一个函数转变成tool
  • @agent(tools=[...], llm=..., ...) 在一个函数外包了之后,这个函数会变成一个agent,函数执行输出就会变成agent.run(...) 中填入的提示词。其本身又是一个函数,能够被当做tool被其他agent来使用。

单这样说有点抽象,我们来看一个例子。

这个例子不像前面例子这么简单,大致交代一下背景:在云原生场景下,工作负载之间的互相依赖常常使用service,而在排查问题的时候,我们常常会去找这个Pod关联的Service。这里例子就是让大模型去帮我们寻找这些service。

import os
import sys
from subprocess import Popen, PIPE

sys.path.insert(0, os.path.split(os.path.realpath(__file__))[0] + "/../../")

from aibond import AI
from langchain import OpenAI

ai = AI()


def popen(command):
    child = Popen(command, stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = True)
    out, err = child.communicate()
    ret = child.wait()
    return (ret, out.strip(), err.strip())

@ai.tool()
def k8sLabel(name: str, kind: str, namespace: str) -> str:
    """This tool can fetch the labels of Kubernetes objects."""
    cmd = "kubectl get " + kind + " " + name + " -n " + namespace + " -o jsonpath='{.metadata.labels}'"
    (ret, out, err) = popen(cmd)
    return out

@ai.tool()
def k8sServiceSelectorList(namespace: str) -> str:
    """This tool can find all services within a namespace in Kubernetes and retrieve the label selectors for each service."""
    cmd = "kubectl get svc -n " + namespace + "  -o jsonpath="{range .items[*]}{@.metadata.name}:{@.spec.selector}{'\n'}{end}""
    (ret, out, err) = popen(cmd)
    return out

@ai.agent(tools=["k8sLabel", "k8sServiceSelectorList"], llm=OpenAI(temperature=0.2), verbose=True)
def k8sPodServiceFinder(name: str, namespace: str) -> str:
    """This tool can find the services associated with a Kubernetes pod resource."""
    return f"帮我列出 {namespace} 这个ns下所有的service,在这个service list中找出与 pod {name} 的label相关的service,返回的结果只有service的名称即可"


a = ai.run("使用所有的工具去查找sreworks这个ns下 prod-health-health-6cbc46567-s6dqp 这个pod的关联的k8s资源", llm=OpenAI(temperature=0.2), agents=["k8sPodServiceFinder"], verbose=True)
print(a)
  • 最终当我们想要解决问题的时候,依赖 k8sPodServiceFinder 这个agent来进行帮助。
  • k8sPodServiceFinder本身也是agent,他将 name 和 namespace 这两个参数转换成一个prompt,同时他依赖 k8sLabel 和 k8sServiceSelectorList 这两个tool 去解决问题,在prompt中教会了大模型如何去使用这两个工具
  • 最终整个运行的层级结构如下图所示:

截屏2023-07-24 10.21.06.png

因为我们使用了修饰器来进行编程,事实上这个层级结构再复杂一点也能非常轻松地应对。整体使用下来,我们需要考虑如下几个点:

  1. 在多层的结构中,可以混合使用多种大模型,但大模型调用次数会增多,整体的运行速度也会变慢。
  2. 在编程中引入了大模型之后,几乎不用写if-else等控制流了,只需要一些原子性的工具,大模型就会自动将这些工具串起来。
  3. Agent + Tool的结构可以类比 ChatGPT 的插件系统,不过这是个私有的插件系统,用户可以自由定制插件。
  4. 之前写代码时候常常会说“优雅的接口,丑陋的实现”,现在这个丑陋的实现现在似乎可以直接变成一段prompt。那么如果要验证某个功能,是不是可以直接放一堆prompt上去调通原型,后面再慢慢挨个替换接口?

上面的这些装饰器是一些语法糖组成的框架,核心部分是langchain,有兴趣想动手实践的同学可以参考框架代码 https://github.com/alibaba/sreworks-ext/tree/master/aibond

面向对象的AI编程

将类对象转换成tool

在有了上文提到的框架,我们就可以做很多AI相关的编程尝试,但是总发现只能编写简单功能,没法特别复杂,为什么呢?因为只有函数能变成tool,而我们平时编程的时候,大多数情况都是面向对象的编程。一下子变成了到处用裸函数,自然会有些不适应。那么问题来了,为什么不能把一个类对象变成一个tool呢?函数是无状态的,类对象是要实例化的,是有状态的。但如我们前面在分析prompt时所提到,我们每次调用都会全量推信息,那么这些信息不就是有状态的吗?包含个类实例数据应该不在话下吧?

于是我们可以设置这样一个带状态的function,像个代理一样,把一个类对象包在里面:

def demo_class_tool(func: str, args: Dict, instance_id: str = None) -> Dict:
    """
    This is a tool that requires instantiation. You can first call the '__init__' function to instantiate, this call will return an 'instance_id'. Subsequently, you can use this 'instance_id' to continue operating on this instance.
    Below are the available funcs for this tool:
    - func: __init__  args: {{'url': {{'title': 'Url', 'type': 'string'}} }}
    - func: read  args: {{'limit': {{'title': 'Limit', 'type': 'intger', 'default': '1000'}}

"""
    ...
  • 在函数的描述中就说明这个工具的使用方式,里面包含了哪些含函数(就是类对象下有多少个函数)
  • 引导大模型先去调用 __init__拿到实例化后的 instance_id
  • 然后引导大模型拿着 instance_id 并且参考我们在描述中的参数来调用函数,就实现类调用。

还是最前面的一个例子查看机器运行时长的例子,我们可以使用类对象再实现一遍:

import os
import sys
import paramiko

sys.path.insert(0, os.path.split(os.path.realpath(__file__))[0] + "/../../")

from aibond import AI
from tools.Linux.SSH import SshClient
from langchain import OpenAI

ai = AI()

class SshClient():
    """A tool that can connect to a remote server and execute commands to retrieve returned content."""
    _client = None
    def __init__(self, host: str, username: str = "root", password: str = None):
        self._client = paramiko.SSHClient()
        self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self._client.connect(host, username=username, password=password)

    def exec_command(self, command: str) -> Dict:
        stdin, stdout, stderr = self._client.exec_command(command)
        retcode  = stdout.channel.recv_exit_status()
        output_stdout = stdout.read().decode('utf-8')
        output_stderr = stderr.read().decode('utf-8')

        stdin = None
        stdout = None
        stderr = None

        return {"stdout": output_stdout, "stderr": output_stderr, "exitStatus": retcode}


resp = ai.run("帮我看看 8.130.35.2 这台机器启动了多久了", llm=OpenAI(temperature=0.2), tools=[SshClient], verbose=True)
print("=== resp ===")
print(resp)
  • 我们可以看到,我们引入了一个client来进行ssh,我们把原本带着机器IP执行命令化成了两步,先实例化sshClient,然后调用sshClient.exec_command(...)来执行命令。我们来看看大模型能否理解。
> Entering new AgentExecutor chain...
Action:
```
{
  "action": "SshClient",
  "action_input": {
    "sub_func": "__init__",
    "sub_args": {
      "host": "8.130.35.2",
      "username": "root",
      "password": ""
    }
  }
}
```


Observation: {'instance_id': 'cbbb660c0bc3'}
Thought: I need to use the instance_id to execute a command
Action:
```
{
  "action": "SshClient",
  "action_input": {
    "sub_func": "exec_command",
    "sub_args": {
      "command": "uptime"
    },
    "instance_id": "cbbb660c0bc3"
  }
}
```


Observation: {'stdout': ' 23:18:55 up 25 days,  8:11,  0 users,  load average: 0.29, 0.55, 0.84\n', 'stderr': '', 'exitStatus': 0}
Thought: I have the answer
Action:
```
{
  "action": "Final Answer",
  "action_input": "This machine has been up for 25 days, 8 hours, and 11 minutes."
}
```

> Finished chain.
=== resp ===
This machine has been up for 25 days, 8 hours, and 11 minutes.

我们可以看到,大模型很好地理解了这个实例化的过程,他会先用ip地址去做实例化,然后再执行命令。

如果一个类对象能变成tool,AI编程就没那么费劲了,甚至于有些类对象是不是都用做什么处理,直接转成tool给Agent用就行了。以及大数据的处理是不是也可以采用这个方案:先把大数据载入到一个对象中,然后提供出若干个方法让大模型慢慢去分析或消化这些数据?大文本的汇总是否也可以采用这个思路?

有关类对象tool调用的功能实现,已经推送到了框架中,欢迎大家试用 https://github.com/alibaba/sreworks-ext/blob/master/aibond/aibond/core.py

有关类对象tool化之后的更多探索,会在第二篇中展开。

真正的对象

在编程的早期学习阶段,面向对象编程(OOP)的概念对于初学者而言,是一种全新而又复杂的概念。为了帮助初学者理解,我们常常采取了形象化的教学手段,如用“手”和“脚”来举例说明一个类(class)的概念。当时懵懂的我,以为只要给每个class配完手脚,整个程序就会活起来,但是代码写多了发现好像离这个“活”总有点差距。因为我们在编写类时,常常把它们视为一个静态的存储。而真正的对象,它们应该具备更多的主动性和动态性。它们应该能主动进行交流(“讲话”),能够执行任务(“干活”)。这就像在现实世界中,每个个体都是一个独立的、能够独立思考和行动的实体。

是的,如前文所说,我们的“工具人”就能实现这一点。上文的例子让我们看到了一个类对象也能变成一个tool,供agent使用。那么在这个类对象,里面我们是不是能嵌入大模型驱动,让其真正地活起来?使得 agent 调用 agent 的时候,不仅只是要一个结果,更像是一种面向结果的沟通?

这些探索也会在后续的篇章展开。

参考
●《ReAct: Synergizing Reasoning and Acting in Language Models》https://arxiv.org/abs/2210.03629
●《aibond使用案例》https://github.com/alibaba/sreworks-ext/blob/master/aibond/cases/README.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/784858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Delphi 开发者,显示图片请忘掉VCL中的 TImage 吧

目录 序言 使用TImageCollection和TVirtualImageList组件支持高分辨率图像 一、总览 二、使用图像收集组件TImageCollection 2.1 图像收集组件编辑器 2.2 将现有 TImageList 载入 TImageCollection 三、使用Virtual ImageList 组件 3.1 Virtual ImageList Component 编辑…

【数据结构】| 王道考研——树的前世今生

目录 一. &#x1f981; 前言二. &#x1f981; 各种树的知识点1. 树1.1 概念1.2 属性1.3 常考性质1.4 树转换成二叉树1.5 森林转换为二叉树1.6 二叉树转换为森林1.7 树的遍历1.8 森林的遍历 2. 二叉树2.1满二叉树2.2 完全二叉树2.3二叉排序树2.4 平衡二叉树2.5 二叉树常考性质…

IDEA使用lombok实体类加上@Data注解后无法找到get和set方法

文章目录 一、问题原因二、解决方法1.File→Settings2.Plugins→搜索"lombok"→Install3.Restart IDE&#xff08;重启IDEA&#xff09; 一、问题原因 IDEA没有安装lombok插件 二、解决方法 1.File→Settings 2.Plugins→搜索"lombok"→Install 3.Restart…

北斗定位导航系统,北斗模块应用领域发展概况_北斗二号模块,北斗三号模块

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、北斗系统概述1.1 空间段1.2 地面控制段1.3 用户段1.4 坐标系统1.5 时间系统 二、北斗系统定位导航授时服务2.1 服务概述2.2 服务区2.3 北斗信号频段2.4 北斗…

服务器中了Locked勒索病毒怎么解决,勒索病毒解密恢复方式与防护措施

服务器是企业重要数据存储和处理的关键设备&#xff0c;然而&#xff0c;众所周知&#xff0c;服务器系统并非完全免受网络攻击的。其中一种常见的威胁是勒索病毒&#xff0c;其中一种恶名昭彰的变种是Locked勒索病毒。Locked勒索病毒采用了对称AES与非对称RSA的加密形式&#…

【C++入门到精通】C++入门 —— 引用、内联函数

目录 一、引用 1.引用的概念 2.引用的特性 3.常引用 4.引用的使用场景 ⭕做参数 ⭕做返回值 5.传值、传引用效率比较 值和引用的作为返回值类型的性能比较 6.引用和指针的区别 引用和指针的不同点 二、内联函数 1.内联函数的概念 2.内联函数的特性 3.宏与内联函数 …

RocketMQ教程-(5)-功能特性-顺序消息

顺序消息为 Apache RocketMQ 中的高级特性消息&#xff0c;本文为您介绍顺序消息的应用场景、功能原理、使用限制、使用方法和使用建议。 应用场景​ 在有序事件处理、撮合交易、数据实时增量同步等场景下&#xff0c;异构系统间需要维持强一致的状态同步&#xff0c;上游的事…

13.4.2 【Linux】sudo

相对于 su 需要了解新切换的使用者密码 &#xff08;常常是需要 root 的密码&#xff09;&#xff0c; sudo 的执行则仅需要自己的密码即可。sudo 可以让你以其他用户的身份执行指令 &#xff08;通常是使用 root 的身份来执行指令&#xff09;&#xff0c;因此并非所有人都能够…

Spring 多数据源方法级别注解实现

Spring框架提供了多种数据源管理方式&#xff0c;其中多数据源管理是其中之一。多数据源管理允许应用程序使用多个数据源&#xff0c;而不是只使用一个数据源&#xff0c;从而提高了应用程序的灵活性和可靠性。 多数据源管理的主要目的是让应用程序能够在不同的数据库之间切换&…

SpringCache 框架使用以及序列化和缓存过期时间问题的解决

目录 为什么使用Spring Cache 如何使用Spring Cache 1 加依赖 2 开启缓存 3 加缓存注解 序列化以及过期时间等问题 解决方案&#xff1a;自定义序列化方式 1.自定义序列化方式并设置白名单 2.配置并设置缓存的过期时间 为什么使用Spring Cache 缓存有诸多的好处&#x…

YOLOv5(v7.0)网络修改实践二:把单分支head改为YOLOX的双分支解耦合head(DecoupleHead)

前面研究了一下YOLOX的网络结构&#xff0c;在YOLOv5(tag7.0)集成了yolox的骨干网络&#xff0c;现在继续下一步集成YOLOX的head模块。YOLOX的head模块是双分支解耦合网络&#xff0c;把目标置信度的预测和目标的位置预测分成两条支路&#xff0c;并验证双分支解耦合头性能要优…

为什么视频画质会变差,如何提升视频画质清晰度。

在数字时代&#xff0c;视频已经成为我们生活中不可或缺的一部分。然而&#xff0c;随着视频的传输和处理过程中的多次压缩&#xff0c;画质损失逐渐凸显&#xff0c;影响了我们对影像的真实感受。为了让视频画质更加清晰、逼真&#xff0c;我们需要采取一些措施来保护和修复视…

设计模式大白话——观察者模式

文章目录 一、概述二、示例三、模式定义四、其他 一、概述 ​ 与其叫他观察者模式&#xff0c;我更愿意叫他叫 订阅-发布模式 &#xff0c;这种模式在我们生活中非常常见&#xff0c;比如&#xff1a;追番了某个电视剧&#xff0c;当电视剧有更新的时候会第一时间通知你。当你…

Zia和ChatGPT如何协同工作?

有没有集成ChatGPT的CRM系统推荐&#xff1f;Zoho CRM已经正式与ChatGPT集成。下面我们将从使用场景、使用价值和使用范围等方面切入讲述CRMAI的应用和作用。 Zia和ChatGPT如何协同工作&#xff1f; Zia和ChatGPT是不同的人工智能模型&#xff0c;在CRM中呈现出共生的关系。 …

荧光信号采集的2种方法

本文介绍荧光信号采集的2种方法。 荧光信号采集是生化类医疗设备&#xff08;PCR&#xff0c;荧光免疫分析仪&#xff09;常用的功能&#xff0c;针对不同类型的激发光和发射光采用的荧光信号采集方法也不一样。 1.基本概念 1)发光原理 当待测物质受到外接某一波长的入射光…

生物信息学_玉泉路_课堂笔记_02 第二章 序列比对和序列数据库搜索

&#x1f345; 课程&#xff1a;生物信息学_玉泉路_课堂笔记 中科院_2022秋季课 第一学期 &#x1f345; 个人笔记使用 &#x1f345; 2023/7/5 一、上周回顾 二、生物信息学的展望 —— 精准医学的发展 生物信息学的开拓者 三、双序列比对的常用算法 序列比对的需求 使用 b…

基于包围框回归的目标检测网络原理及Tensorflow实现

对象检测是对图像内的对象进行分类和定位。 换句话说&#xff0c;它是图像分类和对象定位的结合。 构建用于图像分类的机器学习模型更简单&#xff0c;我在我的一篇文章中对此进行了描述。 然而&#xff0c;图像分类器无法准确判断对象在图像内的位置。 为了实现这一目标&#…

opencv对相机进行畸变矫正,及从矫正后的图像坐标反求原来的对应坐标

1.背景 目前有个项目&#xff0c;需要用到热成像相机。但是这个热成像相机它的畸变比较厉害&#xff0c;因此需要用标定板进行标定&#xff0c;从而消除镜头畸变。 同时需要实现用户用鼠标点击矫正后的画面后&#xff0c;显示用户点击位置的像素所代表的温度。 2.难点 消除镜…

1-1 AUTOSAR是什么?

目录 一、AUTOSAR组织 二、AUTOSAR的版本 三、AUTOSAR的理念 四、AUTOSAR的动机 五、AUTOSAR目标 六、AUTOSAR的优势 6.1 商业优势 6.2 技术优势 一、AUTOSAR组织 AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;是一个全球性的汽车行业标准化组织…

【流形和流形空间(姿态)】

定义 流形&#xff08;Manifold&#xff09;是一种广义的曲面概念&#xff0c;用于描述局部上类似于欧几里德空间的空间。简而言之&#xff0c;流形是一个局部与欧几里德空间同胚&#xff08;homeomorphic&#xff09;的空间&#xff0c;但并不一定是全局上同胚的。&#xff0…