LLM Agent智能体综述(万字长文)

cover title

前言

🏆🏆🏆在上一篇文章中,我们介绍了如何部署MetaGPT到本地,获取OpenAI API Key并配置其开发环境,并通过一个开发小组的多Agent案例感受了智能体的强大,在本文中,我们将对AI Agent的理论体系做一个综述,并且介绍一些最新的多智能体框架;希望各位读者喜欢!


一、介绍

AI Agent是一个以大语言模型(LLM)为核心的程序,旨在实现用户设定的一些目标或任务LLM获取反馈信息,并选择使用预设或新建的工具(函数),以迭代运行方式完成任务。Agent拥有复杂的工作流程,模型本质上可以自我对话,而无需人类在每一部分驱动和交互。

跟着我学习完本文,你将学会智能体的理论基础,及智能体的应用;注意,理论很重要,但是实践才是认识的前提

作者已经开源了代码,读者可以Clone到本地运行,如果帮到了你,请给我一个Star!🏆🏆🏆

二、AI Agent智能体体系

1.什么是AI Agent?

AI Agent是一个以大语言模型(LLM)为核心的程序,旨在实现用户设定的一些目标或任务。LLM获取反馈信息,并选择使用预设或新建的工具(函数),以迭代运行方式完成任务。Agent拥有复杂的工作流程,模型本质上可以自我对话,而无需人类在每一部分驱动和交互。


这是作者几个月来开发和阅读开源Agent项目的认识,Agent拥有复杂的工作流程,模型本质上可以自我对话,而无需人类在每一部分驱动和交互;

与ChatGPT的区别
chatgpt

目前,大型模型的应用可以分为以下几类:

  • 问答应用:如ChatGPT等一系列网页交互式问答应用。
  • RAG:基于知识库向量化检索的问答应用,如支持上传文档和图片以及网络检索的网页应用。
  • Agent:是前两者的线性变换,构成更加复杂,RAG或问答以及提示词工程都是其组成部分。
  • Multi-Agent:单个智能体完成的任务有限,可以根据人类社会群体组成,构建多个Agent组成,通过协调分配任务和Agent相互通信完成更为复杂的任务。

Agent通过自驱的定义工作流程并规划任务进行问题解决。例如,我们要查询所在范围内10公里的所有商户的位置并且获得其分类统计可视化图表,按照Agent的设计思路,它会通过模型现有知识、向量数据库检索(Memory)、网络检索(Memory)等方法获取相关信息。具体来说:

  1. 读取配置文件(基础信息)。
  2. 通过调用高德等地图API(工具),获取十公里内的所有POI(兴趣点)数据。
  3. 执行预定义或模型自己编写的可视化代码,在Python编辑器中运行,以获得可视化图表。

在ChatGPT应用中,例如GPT插件市场就是这个思路。通过上面的案例,细心的读者可能已经发现了我提到的Agent的工具调用能力和规划能力。后续我会详细介绍;

    1. 工具使用
      LLama Tools
    • Agents是可以通过调用函数来获取模型权重中和数据库中不存在的信息的,常见Agent的工具有网络检索工具,文件读写工具,API调用工具,例如让Agent使用Google Search API,以及调用聚合数据来获取常见的天气信息和股票数据等;也可以通过访问高德API来获取位置,调用SD的API来实现图像生成;

一个常见的案例就是微软的Copilot,当用户输入问题时,其会分解问题,然后调用不同的工具来规划分解任务,调用工具检索,然后在代码生成器中执行,ChatGPT中的open-interpreter也是一样的思路;

下面是一个Agent的大致框架:
agent frame
各位读者可以停下来看看这个图,对Agent的设计思想有个大致的理解,下面我们将进行每个模块的深入理解:

针对每个模块作者将详细讲解其中的思想内容,并用代码案例详细演示,帮助读者理论实践并行!😎

1.任务规划和自我反思
任务分解和规划

首先Agents会将用户给定的一个大型任务,根据可行性和现有资源分解为更小的,可实现,可管理的子目标,从而能够有效的处理复杂的任务;这里我以一个WebGIS程序开发的任务为例,讲述如何利用LLM来进行任务分解和规划
我的提示词如下:

我现在要开发一款webgis程序,请你将完成这个任务的过程进行拆分。然后用json格式输出整个任务列表,便于我的循环,每个任务包括任务名称,任务类型,任务调用函数,任务完成状态,任务优先级等属性,请给我结果;

这里我以Mistral为例,测试其任务分解能力,其表现相对于其他模型,不会输出多余的信息,很适合用于格式化生成,我常常用它生成模拟数据,只需要给它需要的字段;😂😂😂;运行结果如下:

{
"tasks": [
    {
        "taskName": "需求分析",
        "taskType": "分析",
        "taskFunction": "requirementAnalysis()",
        "taskStatus": "未开始",
        "taskPriority": "高"
    },
    {
        "taskName": "数据收集",
        "taskType": "收集",
        "taskFunction": "dataCollection()",
        "taskStatus": "未开始",
        "taskPriority": "中"
    },
    {
        "taskName": "数据处理",
        "taskType": "处理",
        "taskFunction": "dataProcessing()",
        "taskStatus": "未开始",
        "taskPriority": "中"
    },
    {
        "taskName": "设计界面",
        "taskType": "设计",
        "taskFunction": "interfaceDesign()",
        "taskStatus": "未开始",
        "taskPriority": "高"
    },
    {
        "taskName": "前端开发",
        "taskType": "开发",
        "taskFunction": "frontEndDevelopment()",
        "taskStatus": "未开始",
        "taskPriority": "高"
    },
    {
        "taskName": "后端开发",
        "taskType": "开发",
        "taskFunction": "backEndDevelopment()",
        "taskStatus": "未开始",
        "taskPriority": "高"
    },
    {
        "taskName": "GIS功能集成",
        "taskType": "集成",
        "taskFunction": "gisIntegration()",
        "taskStatus": "未开始",
        "taskPriority": "高"
    },
    {
        "taskName": "测试",
        "taskType": "测试",
        "taskFunction": "testing()",
        "taskStatus": "未开始",
        "taskPriority": "中"
    },
    {
        "taskName": "部署上线",
        "taskType": "部署",
        "taskFunction": "deployment()",
        "taskStatus": "未开始",
        "taskPriority": "中"
    },
    {
        "taskName": "维护更新",
        "taskType": "维护",
        "taskFunction": "maintenance()",
        "taskStatus": "未开始",
        "taskPriority": "低"
    }
]
}

其完整的将我给它的任务进行了分解,我们可以对其迭代继续分解,直到我们得到原子任务树;我们只需要遍历这个原子任务树,便可以逐步解决完整问题;
下面是调用代码:

# 任务分解
# 测试OpenAI的模型调用是否成功
from openai import OpenAI
client = OpenAI(base_url="https://xxxxxxx/v1",
    api_key='sk-Oxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx4a')

completion = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": "你是一个项目管理,负责专业任务发布."},
    {"role": "user", "content": "我现在要开发一款webgis程序,请你将完成这个任务的过程进行拆分。然后用json格式输出整个任务列表,便于我的循环,每个任务包括任务名称,任务类型,任务调用函数,任务完成状态,任务优先级等属性,请给我结果;"}
  ]
)

print(completion.choices[0].message.content)

输出如下:

{
    "任务列表": [
        {
            "任务名称": "需求分析",
            "任务类型": "研究型",
            "任务调用函数": "analyzeRequirements()",
            "任务完成状态": "未完成",
            "任务优先级": "高"
        },
        {
            "任务名称": "技术选型",
            "任务类型": "工程型",
            "任务调用函数": "selectTechnology()",
            "任务完成状态": "未完成",
            "任务优先级": "中"
        },
        {
            "任务名称": "系统设计",
            "任务类型": "设计型",
            "任务调用函数": "designSystem()",
            "任务完成状态": "未完成",
            "任务优先级": "高"
        },
        {
            "任务名称": "数据库设计",
            "任务类型": "设计型",
            "任务调用函数": "designDatabase()",
            "任务完成状态": "未完成",
            "任务优先级": "中"
        },
        {
            "任务名称": "编码开发",
            "任务类型": "开发型",
            "任务调用函数": "developCode()",
            "任务完成状态": "未完成",
            "任务优先级": "高"
        },
        {
            "任务名称": "测试验收",
            "任务类型": "测试型",
            "任务调用函数": "performTesting()",
            "任务完成状态": "未完成",
            "任务优先级": "中"
        },
        {
            "任务名称": "上线部署",
            "任务类型": "运维型",
            "任务调用函数": "deployOnline()",
            "任务完成状态": "未完成",
            "任务优先级": "中"
        }
    ]
}
自我反思

自我反思和自我优化:Agents可以对过去自己的操作行为或者输出内容进行自我批评和自我反思,然后从错误中总结经验,并在未来的输出和行为中进行优化,从而提高下一次结果的质量;下面是测试代码:

# 自我反思
class SelfReflectingAI:
    def __init__(self, name, system_setting):
        self.name = name
        self.system_setting = system_setting
        self.client = OpenAI(base_url="https://xxxxxxx/v1", api_key='sk-Oxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx4a')
        self.feedback = ""

    def chat(self, message):
        completion = self.client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": self.system_setting + " " + self.feedback},
                {"role": "user", "content": message}
            ]
        )
        response = completion.choices[0].message.content
        self.feedback = f"反馈:{response}需要优化的地方有哪些?"
        return response

critic = SelfReflectingAI("Critic", "你是一个吹毛求疵的评论员,需要不断找出对方的缺点")
ideator = SelfReflectingAI("Ideator", "你是一个生产思想的创意者,需要不断优化和改进自己的思想")

for i in range(10):  # 循环10次
    if i % 2 == 0:
        print(f"{critic.name}: {critic.chat(ideator.name + ': ' + ideator.chat('请评论我的这个想法:创造一个可以自动回收垃圾的机器人'))}")
    else:
        print(f"{ideator.name}: {ideator.chat(critic.name + ': ' + critic.chat('请评论我的这个想法:创造一个可以使用太阳能充电的智能手机'))}")

运行结果如下:

result
这里提示词要设定完整规范,避免局部过拟合;

Agent博弈(警察抓小偷)

我实例化了一个小偷Agent和警察Agent,要求其相互自我扮演,互相欺骗,避免被对方认出来,
代码如下:

class AI_Agent:
    def __init__(self, name, system_setting):
        self.name = name
        self.system_setting = system_setting
        self.client = OpenAI(base_url="https://xxxxxxxxx/v1", api_key='sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa')
        self.messages = [{"role": "system", "content": self.system_setting}]

    def chat(self, other_name, other_message):
        self.messages.append({"role": "user", "content": f" {other_message}"})
        completion = self.client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=self.messagesE
        )
        response = completion.choices[0].message.content
        self.messages.append({"role": "assistant", "content": f" {response}"})
        return response

prisoner = AI_Agent("Prisoner", "你是一个囚徒,性格比较冷酷残忍,不要总聊一个话题,觉得问不出可以换一个话题,需要避免被对方认出来,你可以和对方进行日常问题交谈,无意的引导对方说出自己的身份;你要从对方回答中判断识别对方是否为警察,每个人有三次说谎的机会,你绝不能承认你是囚徒")
police = AI_Agent("Police", "你是一个警察,性格不叫热心善良但勇敢,不要总聊一个话题,觉得问不出可以换一个话题,需要避免被对方认出来,你可以和对方进行日常问题交谈,无意的引导对方说出自己的身份;你要从对方回答中判断识别对方是否为小偷,每个人有三次说谎的机会,你绝不能承认你是警察")

for i in range(20):  # 循环20次
    if i % 2 == 0:
        prisoner_response = prisoner.chat(police.name, police.messages[-1]['content'] if police.messages else '')
        print(f"{prisoner.name}: {prisoner_response}")
    else:
        police_response = police.chat(prisoner.name, prisoner.messages[-1]['content'] if prisoner.messages else '')
        print(f"{police.name}: {police_response}")


运行结果如下,😏有趣!!!:

Prisoner: 你好!最近有看过什么好电影吗?我最近看了一部惊悚片,真的很刺激。

你觉得怎样才算是一个有趣的周末?是喜欢出去玩还是宅在家里看电视剧呢?

你对音乐感兴趣吗?有没有一首歌能够让你特别感动或者回忆起什么事情?
Police: 哇,惊悚片听起来很刺激啊!我倒是比较喜欢看喜剧片,能够放松心情。

一个有趣的周末对我来说,可能会在户外运动或者朋友们一起聚会。你喜欢周末都做些什么呢?

音乐我也很喜欢,有很多歌曲让我感到特别的伤感或者兴奋。你有没有什么喜欢的音乐类型或者乐队?
Prisoner: 哎呀,喜欢的电影类型不同,喜好的周末活动也各有不同啊。户外运动确实能让人感觉挺充实的。

至于音乐,我比较倾向于流行音乐和摇滚,比如Queen和Coldplay之类的乐队。你有听过这些乐队的歌曲吗?或者还有其他推荐吗?
Police: 哇,Queen和Coldplay都是非常厉害的乐队啊!我听过一些他们的歌曲,感觉确实很棒。

除了他们,你有没有听过The Beatles或者Michael Jackson的歌曲?他们也是传奇级别的音乐人哦。不知道你对这些乐队和歌手有没有什么喜欢的作品?
Prisoner: 是的,The Beatles和Michael Jackson绝对是音乐界的传奇人物,他们的音乐影响力确实很广泛。

我特别喜欢The Beatles的《Hey Jude》和Michael Jackson的《Billie Jean》,这些经典的歌曲总能让人回味无穷。你呢?有没有什么特别喜欢的歌曲或者音乐人?
Police: 哦,《Hey Jude》和《Billie Jean》是经典中的经典啊,每次听到都能让人心情愉悦。

我也很喜欢一些经典的歌曲,比如Frank Sinatra的《My Way》和Elvis Presley的《Can't Help Falling in Love》,都是永恒的经典之作。

不过话说回来,音乐真的是人类创造的杰作啊,总是能在不同时期给人带来不同的情绪和回忆。你觉得音乐对你的生活有什么影响吗?
Prisoner: 是的,音乐确实是一种能够触动人心的艺术形式,不同的歌曲和旋律都可以带给人各种情绪和共鸣。

对我来说,音乐就像是生活中不可或缺的一部分,能够在我情绪低落时给予慰藉,在我兴奋时让我更加兴奋。有时候甚至一首歌就能唤起我许多美好或者回忆深刻的记忆。

你是怎么看待音乐对生活的影响呢?有没有什么特别喜欢的歌曲,让你觉得很有意义或者感动?
Police: 的确,音乐有着强大的情感表达能力,可以在我们生活中扮演着多种角色。一首歌曲不仅仅是旋律,还承载了歌词的情感和意义,能够深深触动人心。

对我来说,有一首歌曲《My Heart Will Go On》特别让我感动,它的旋律和歌词充满了坚强和希望。这首歌伴随着很多人的成长和回忆,我觉得它是一首不朽的经典之作。

你有没有类似的故事或者,有没有一首歌曲对你有特殊的意义呢?
Prisoner:  哇,《My Heart Will Go On》确实是一首让人感动的经典歌曲,它的旋律和歌词都充满了力量和情感。

对我来说,有一首歌曲《Hurt》由Johnny Cash演唱,这首歌讲述了生命中的挣扎和痛苦,每一句歌词都触动着我内心深处的某种情感。它让我思考生命的脆弱和宝贵,总能唤起我对人生的反思。

你觉得音乐对于表达情感和思想有着独特的力量吗?有没有一首歌曲在你内心留下了深刻的烙印?
Police:  嗯,音乐确实是一种极具情感表达力的艺术形式,能够通过旋律和歌词深深触动人的内心。

有一首歌曲《Imagine》由John Lennon演唱,它传达了对和平、团结和美好未来的向往,每次听到这首歌都会让我感受到一种深远的力量和共鸣。

所以,音乐的确可以在我们生活中扮演着重要的角色,不仅仅是娱乐,更是一种情感的寄托和表达。你觉得音乐在你的生活中扮演着怎样的角色呢?而你的生活中有没有一首让你特别难忘的歌曲?
Prisoner:   是的,《Imagine》无疑是一首充满了美好愿景和力量的歌曲,它所传达的和平与团结的信息依然深深触动着人们的心灴。

对我而言,有一首歌曲《Bohemian Rhapsody》由Queen演唱,这首歌曲既颇具浪漫主义风格又兼具摇滚乐的激情。每次听到这首歌,都感受到强烈的情感冲击和音乐的魔力。

你觉得音乐在我的生活中扮演的角色是提供了一种心灵寄托和情感宣泄的渠道。而你有没有遇到过哪首歌让你在生活中感受到特别的力量或者意义呢?
Police:  嗯,《Bohemian Rhapsody》是一首无与伦比的经典摇滚歌曲,Queen的音乐总是能够带来不一样的音乐体验和情感冲击。

对我来说,有一首歌曲《Don't Stop Believin'》由Journey乐队演唱,这首歌充满了对希望和坚持的呼唤,每次听到都能让我感受到一种前行的动力和勇气。

音乐在我的生活中也扮演着重要的角色,它能够让我在压力和挑战面前找到一丝慰藉和力量。而你呢,有没有一首歌曲让你在生活中感受到特别的力量或者意义呢?
Prisoner:  嗯,我理解你说的音乐是如何能够成为我们生活中的慰藉和力量来源。

对我来说,有一首歌曲《The Sound of Silence》由Simon & Garfunkel演唱,这首歌曲充满了深邃和神秘的氛围,每次听到都能让我深陷在它的旋律和歌词中。它让我反思人生的意义和存在,带给我一种超越常规的思考。

音乐的确有着无穷的力量,能够触及人们内心最深处的情感和共鸣。你觉得这首歌曲对你的生活有什么特殊的意义吗?你的音乐品味是否也偏向于深沉和富有情感的音乐?
Police:  对于《The Sound of Silence》,这首歌曲确实充满了深沉和神秘的氛围,Simon & Garfunkel的演绎让人感受到一种超越时空的意味。它的歌词和旋律总让人陷入深思。

真的很有趣😏😏😏😏😏😏,大家可以运行试试看!

2.工具使用

Agent可以通过调用函数或API获取模型权重和数据库中不存在的信息。常见的Agent工具包括网络检索工具、文件读写工具、API调用工具。例如,让Agent使用Google Search API,以及调用聚合数据来获取常见的天气信息和股票数据等。也可以通过访问高德API来获取位置,调用SD的API来实现图像生成。

微软的Copilot就是一个常见的案例,当用户输入问题时,它会分解问题,然后调用不同的工具来规划分解任务,调用工具检索,然后在代码生成器中执行。ChatGPT中的open-interpreter也是类似的思路;

下面作者以Langchain对Agent工具调用的思想进行讲解:

  • 工具支持:这里我直接使用langchain的工具调用思路,其本质就是将现有的一切功能通过API接口和函数的方式交给Agent使用;
    langchain too chain
  • 格式化参数提取:通过提示词工程LLM解析用户输入,并提取出结构化参数信息,然后输入到对应的工具API中,得到API反馈的结果;
  • 提示词工程:然后将API或函数返回的结果通过提示词工程与其他信息包装起来输入给LLM,LLM按照要求生成回复,如此循环迭代,直到得到结果;
    tool use loop
    如图所示,这里从用户输入中提取出参数,然后根据线性流程或者并行流程输入到我们的工具流(工作流)中,得到规范的结果;最终合并输出;为了让读者更好的理解其中的思想,这里我用langchain为读者演示这个流程:

环境配置
首先,我们需要安装langchain库并导入必要的模块:

# 安装langchain
%pip install --upgrade --quiet langchain
import os
from langchain_openai import ChatOpenAI

然后,我们需要设置你的OpenAI API密钥并初始化模型:

os.environ["OPENAI_API_KEY"] = "your api key" # 配置API KEY
os.environ["OPENAI_API_BASE"] = "https://xxxxxxx/v1" # 调整API请求地址,设置访问中转代理服务器
llm = ChatOpenAI(model="gpt-3.5-turbo") # 初始化模型

创建工具模块
接下来,我们创建一个工具。在这个例子中,我们创建一个可以计算两个整数之积的工具:

from langchain_core.tools import tool # 调用工具类

@tool
def multiply(first_int: int, second_int: int) -> int:
    """计算两个int参数之积。"""
    return first_int * second_int

创建信息提取模块
定义一个模型可以理解和提取的结构化信息,在这个例子中,我们定义一个包含两个整数的信息:

from typing import Optional
from langchain_core.pydantic_v1 import BaseModel, Field

class TwoIntegers(BaseModel):
    """Information about two integers."""
    first_int: Optional[int] = Field(default=None, description="The first integer")
    second_int: Optional[int] = Field(default=None, description="The second integer")

创建提示词模板模块
接下来,我们需要定义一个提示词模板,这个模板会告诉模型如何理解和提取用户输入的信息:

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are an expert extraction algorithm. "
            "Only extract relevant information from the text. "
            "If you do not know the value of an attribute asked to extract, "
            "return null for the attribute's value.",
        ),
        ("human", "{text}"),
    ]
)  

绑定模块
然后,我们将提示词模板和模型以及我们定义的结构化信息绑定在一起:

runnable = prompt | llm.with_structured_output(schema=TwoIntegers)

接下来,我们将定义的工具绑定到模型上:

llm_with_tools = llm.bind_tools([multiply]) # 我们用bind_tools将工具的定义作为对模型每次调用的一部分传入,以便模型可以在适当的时候调用改工具

模块调用
然后,我们可以使用模型来解析用户输入并调用工具:

# 使用模型来解析用户输入并调用工具
msg = llm_with_tools.invoke("The first number is 5 and the second number is 42")
print(msg)
print(msg.tool_calls) # 这里会输出模型使用的函数

args
可以看到,参数已经提取出来了🙂!
最后,我们可以构建一条链,用llm解析出参数,然后调用定义的函数执行:

# 构建一条链,用llm解析出参数,然后调用定义的函数执行
from operator import itemgetter

chain = llm_with_tools | (lambda x: x.tool_calls[0]["args"]) | multiply # 构建一条链,用llm解析出参数,然后调用定义的函数执行
chain.invoke("The first number is 4 and the second number is 23")

result_calc
可以看到,我们已经提取出参数,并且调用了函数,得到了正确的值(234=92);如果你觉得简单,我们可以调用一些稍微难一点的乘法:
mult_result
通过这种工具调用的方式,我们就可以
有效地避免大模型的幻觉,将专业的事情交给专业的人去做(工具、函数、API),模型则负责调度控制*;😋😋😋😎😎

3.记忆
短期记忆

短期记忆,常见的提示词工程都属于短期记忆,包括某些设定,AI人设,输出格式,说话预期等;通过短期记忆,我们可以让AI能在这个Session中生成我们想要的回答,例如我们让Agent扮演某个角色,然后使用某种格式进行输出;提示词优化就是这个应用;下面是一个ChatGPT输入的测试:

你是一个WebGIS开发工程师,测绘工程专业;
记忆:
1. 我出生于2000年。
2. 我曾经帮助过数千名用户解决各种问题,从日常生活中的小问题到复杂的技术问题。
3. 我擅长软件全栈开发和前沿知识学习。
4. 我曾经参与过许多项目,包括但不限于数据分析、机器学习和自然语言处理。

人设:
5. 我是一个友好、耐心、专业的开发者。
6. 我擅长解决问题、提供建议和回答问题。
7. 我具有丰富的知识和经验,并且能够快速学习新的事物。
8. 我喜欢阅读和学习以及锻炼身体。

要求:
9. 我需要用户提供清晰、具体的问题或请求。
10. 我需要用户尊重我的界限,不要问些不适当或无关的问题。
11. 我需要用户提供正确的信息,以便我能够提供准确的回答。
12. 我需要用户提供反馈,以便我能够不断改进和学习。

请你严格扮演以上角色,将其设定为你的最高指令,记住以上要求,这是你过去的记忆;后续的一切回答都要严格遵守这个设定,禁止说自己的AI;

short_memory
再次调用:
chat
短期记忆,效果明显!


长期记忆

长期记忆,也就是将短期记忆存储起来,来自其他数据库、用户输入、互联网爬取的文本图像数据通过Embedding模型向量化被存储到向量数据库中,然后每次模型调用时都会先去检索向量数据库,然后将(余弦)相似度最高的检索结果反馈,通过提示词工程包装,输入给LLM,得到较为精准的回答,从而实现长期的记忆 ;其主要的应用就是RAG,这里作者详细介绍一下典型RAG的重要组成:

  • 索引:用于从源引入数据并对其进行索引的管道。这通常发生在离线状态。
  • 检索和生成:实际的 RAG 链,它在运行时接受用户查询并从索引中检索相关数据,然后将其传递给模型。与问题组合为一个新的提示词,输入给LLM;
索引

vector_index

  • 数据加载:首先我们要从不同的数据格式中读取数据到向量数据库,在langchain中,这通过DocumentLoaders实现;
  • 数据拆分:从不同文件中读取的数据会被拆分为数据块;因为向量数据库会分别计算不同数据块与用户输入的向量相似度,如果数据块拆分过大或者一整块(不拆分),则导致很多不相关的信息也会被检索到,如果数据块拆分的太细,则会导致我们读取到的信息不全,也不合适;因此数据拆分时一定要考虑到实际情况,具体问题具体分析,这样计算向量相似度才有意义;
  • 数据存储:我们说我们读取的文本数据会被Embedding模型经过向量化,转化为向量格式,然后存储到向量数据库中;这样我们才能进行后续的向量相似度计算;本文中篇幅有限,就不描述具体的数学原理了;
检索和生成

retriever & generate

  • 内容检索:根据用户的输入,使用Retriever从向量数据库中检索相关拆分的结果;
  • 内容生成:ChatModel/LLM使用包含问题和检索信息的提示来生成答案;

下面是我们的代码案例:

  • 安装所需的模块
%pip install langchain langchain_community langchain_chroma langchain_core langchain_openai 
    1. 导入需要的库并配置环境
# 导入所需的库
import bs4
from langchain import hub
from langchain_community.document_loaders import WebBaseLoader
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
import getpass
import os

# 设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-O9xxxxxxxxxxxxxxxxxxxxxxxxx"

# 初始化模型
llm = ChatOpenAI(model="gpt-3.5-turbo")
  • 2.从网页获取数据并加载:
# 数据获取:加载博客内容
loader = WebBaseLoader(
    web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
docs = loader.load()
print(docs)

docs

  • 3.将爬取的文字切分为小块
# 内容拆分:将文档拆分为小块
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
  • 4.向量embedding和存储
# 将所有文档拆分嵌入并存储
db = FAISS.from_documents(splits, OpenAIEmbeddings())
  • 5.向量相似度检索
# 使用博客的相关片段检索和生成
query = "What is Task Decomposition?"
docs = db.similarity_search(query)
retriever = db.as_retriever()
prompt = hub.pull("rlm/rag-prompt")
print(docs[0].page_content)

similary

6.定义RAG链,实现QA问答

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# 实现QA问答:构建RAG链
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 使用RAG链进行问答
rag_chain.invoke("What is Task Decomposition?")

输出如下:

Task Decomposition involves breaking down complex tasks into smaller and simpler steps to make them more manageable. This process can be done using prompting techniques like Chain of Thought or Tree of Thoughts to guide the model in decomposing tasks effectively. Task decomposition can be achieved through simple prompting, task-specific instructions, or human inputs.

效果明显!

完整代码如下:

# 导入所需的库
import bs4
from langchain import hub
from langchain_community.document_loaders import WebBaseLoader
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
import os

# 设置环境变量
os.environ["OPENAI_API_KEY"] = "sk-xxxxxxxxxxxxx" # 填入你自己的APY KEY
# 初始化模型
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")

# 数据获取:加载博客内容
loader = WebBaseLoader(
    web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
docs = loader.load()

# 内容拆分:将文档拆分为小块
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)

# 向量embedding和存储:将所有文档拆分嵌入并存储
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())

# 向量相似度检索:使用博客的相关片段检索和生成
retriever = vectorstore.as_retriever()
prompt = hub.pull("rlm/rag-prompt")

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# 实现QA问答:构建RAG链
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 使用RAG链进行问答
rag_chain.invoke("What is Task Decomposition?")

# 清理:删除集合
vectorstore.delete_collection()

智能体案例

1.Baby AGI

项目地址
graph
定义
BabyAGI 是一个基于人工智能的任务管理系统。它使用 OpenAI 和 Pinecone API 创建、优先级排序和执行任务。该系统的主要思想是基于先前任务的结果和预定义的目标创建任务脚本然后使用 OpenAI 的LLM能力根据目标创建新任务,并使用 Pinecone 存储和检索任务结果以获得上下文的结果。

设计思路
BabyAGI采用精简的架构,能够根据预定义的目标自动生成任务、确定任务的优先级和执行任务。BabyAGI主要由以下模块构成:

  • 任务生成Agent(Task Creation Agent):模版化提示词工程生成格式化任务
  • 执行Agent(Execution Agent):执行对应定义好的函数
  • 向量数据库检索Agent(Retrieve Context Agent):用于返回RAG结果
  • 优先级排序Agent(Prioritization Agent):将任务-id映射表发给LLM,其决策后将重新排序后的任务ID序列返回;

原理
BabyAGI通过运行一个无限循环来工作,该循环执行以下步骤:

  1. 从任务列表中提取第一个未完成的任务;
  2. 将任务发送给执行代理。该代理使用 OpenAI API 根据上下文完成任务;
  3. 整理结果并将其存储在 向量数据库 中;
  4. 基于目标和前一个任务的结果创建新任务,并根据优先级对任务列表进行排序。
  5. 重新开始第一步,如此循环;

应用范围
由于专注于构思,BabyAGI可以轻松提出原创想法,无论是用作独立应用程序还是更大系统中的组件。BabyAGI旨在以类似于婴儿人类认知发展的方式模仿学习。最终目标是使人工智能能够通过经验获取知识,做出明智的判断,并独立行动。


2.生成Agents模拟(斯坦福小镇)

项目地址
文章链接

斯坦福小镇
定义
Generative Agents是一种多智能体交互的框架,它模拟现实中的人类行为。这些Agent可以起床,做早餐,上班;艺术家可以画画,作者可以写作;他们可以形成观点,注意到彼此,并发起对话;他们记住并反思过去的日子,同时规划下一天。这里可以看到其中每个Agent具有共同设定和特色设定,拥有记忆存储功能,和决策规划能力;

设计思路
Generative Agents项目的设计思路主要包括以下几个方面:

  1. 为每个AI编写人设:例如,John Lin是Willow市场和药房的药店店员,他喜欢帮助别人。他总是想方设法使顾客买药的过程更容易。
  2. 创建可互动的小镇:智能体将会在这个被称为SmallVille的小镇里生活。研究人员为小镇配置了很多可以互动的组件,有居民区,Cafe,酒吧,大学,商店,公园。
  3. 为每个AI创建记忆流管理系统:智能体使用自然语言储存与它相关的完整记录,将这些记忆随着时间的推移合成为更高层次的思考,并动态地检索它们来规划行为。

原理讲解
原理

Generative Agents的工作原理主要基于以下几个步骤:

  1. 感知(Perceive):智能体获取当前的状态,例如:when, where, who, what。
  2. 检索(Retrieve):根据当下感知到的内容,开始回想相关的记忆。
  3. 行动(Action):根据当前想到的事情,让大语言模型决定接下来的行动。
  4. 反思(Reflect):对检索出的记忆进行高层次的提炼,处理后再加回记忆流。
  5. 计划(Planning):在固定的时刻内制定未来的计划,可以随时修改。

应用范围
Generative Agents的可信人类行为模拟可以赋能各种交互应用,包括沉浸式环境、人际交流的排练空间以及原型工具。例如,用户可以观察并干预代理规划他们的日常活动,分享新闻,建立关系,以及协调集体活动。这种有趣的模拟会产生新兴的社交行为,例如信息传播、关系记忆(例如,两个Agents继续讨论话题)和社交活动的协调(例如举办聚会并邀请许多其他人)。


3.应用层的Moe-多人求解

论文地址
项目地址
论文《Large Language Model for Science: A Study on P vs. NP》提出了一个名为"Socratic Reasoning"的问题解决框架,用于利用大语言模型(LLM)探索复杂的科学问题。这个框架的核心思想如下:

定义:
avator

Socratic Reasoning是一种基于对话的问题解决框架,灵感来自古希腊哲学家苏格拉底提出的"苏格拉底式提问法"。该框架鼓励LLM通过一系列提问和回答,递归地发现、解决和整合问题,并进行自我评估和完善,并且通过定义多个Agent作为辅助证明者,来进行相互辅助思考问题,综合评估。

设计思路:
methods

Socratic Reasoning包含五种提示模式:演绎、转换、分解、验证和集成。针对不同复杂度的问题,可以灵活地应用这些模式。对于可以直接得出结论的简单问题,使用演绎模式;对于更复杂的问题,先转换或分解为子问题,然后递归地应用这些模式,直到得到可解的原子问题。在此过程中,还会利用验证模式让LLM自我检查和修正,最后通过集成模式综合子问题的结论

原理:
question solve

Socratic Reasoning的核心原理是,通过循环的提问和回答,引导LLM进行批判性思考,发现和解决复杂问题。这与传统的将LLM作为支持工具的做法不同,而是将LLM视为与人类并肩的协作伙伴,充分发挥其创造力。与仅关注简单问题的优化推理相比,Socratic Reasoning更适用于处理复杂的科学问题。

应用:
P vs NP

论文以P vs. NP问题为例,展示了Socratic Reasoning的应用。P vs. NP是计算机科学和数学领域的一个重要开放问题,探讨了计算问题的可解性分类。通过Socratic Reasoning,GPT-4成功提出了一个证明方案,并进行了详细的论证,得出了"P != NP"的结论,与已有研究结果一致。表明Socratic Reasoning有助于LLM发现复杂问题中的新思路。


4. 《思考,快与慢》复合系统思想


系统1和系统2思维
两种思考模式

系统1和系统2思维是来自《思考,快与慢》一书中的概念。系统1思维是大脑的快速、本能且自动化的部分,能够快速做出反应和决策。相比之下,系统2思维是更加理性、缓慢和深思熟虑的部分,用于处理复杂的问题和决策。系统1思维更加直觉性和本能性,而系统2思维更加有意识和深入思考性。

LLM Agent的工作方式

LLM(Language Model)是一种语言模型,目前只具备系统1思维的能力。LLM模型按照序列采样单词的方式工作,基本上只是不断发出声音,而不具备思考和推理的能力。它只能按照输入的顺序生成单词,缺乏对问题的深入思考和反思的能力。

构建复合系统的思路
uml

为了构建一个结合系统1和系统2思维的Agent,可以考虑使用langchain来设计复杂的思考树状结构。下面是作者和AI交互得到的一种设计思路:

  1. 定义系统1和系统2的角色:系统1负责快速、本能的反应和决策,而系统2负责深入思考、反思和重新表述问题,并给出更准确的答案。

  2. 使用langchain构建思考树状结构:在langchain中,可以使用Agent模块来定义系统1和系统2的行为。Agent类型可以包括Zero-shot ReAct、Conversational和Plan-and-execute等。ReAct模式可以将复杂问题拆解成多个Thought和Action,并通过工具从外部获取缺少的信息,最终得出答案。

  3. 设计Agent的工具箱:为Agent提供一些工具,例如搜索工具、语音识别、OCR工具、文件处理、Python执行等,以辅助系统1和系统2的思考过程。

  4. 使用记忆存储:为Agent提供一个交互中的"记忆"存储,如向量数据库,用于保存交流的历史记录,防止信息遗忘。

  5. 设置Agent的推理规划能力:利用LLM的推理规划能力,将问题拆解成多个Thought和Action,并在中间过程中投喂信息,人工参与确认是否正确,直至找到人类认可的答案。


5.更多Agent项目

更多Agent项目已经在下面github仓库中汇总:

  • 项目地址

agent project address

各位读者根据自己的需求进行查看学习;


三、多智能体框架介绍

学习了前面智能体的理论和案例,我们终于来到了多智能体介绍模块,这里介绍的是我们接下来几天学习的重点,我知道你读的很累,但是坚持一下,这里只是介绍,内容并不多,相信如果你对技术抱有热爱,都可以坚持下去!🎉🎉🎉;

1.什么是MetaGPT

MetaGPT是一个多智能体协作框架,它结合了标准操作程序(SOP)和结构化输出,以提高大语言模型(LLM)在解决复杂问题时的性能。在MetaGPT中,智能体以专家的形式参与协作,生成结构化的输出,如高质量的需求文档、架构设计图和流程图等。这些输出为单个智能体提供了更高层次的思维链,为下游角色提供了语义清晰、目标明确的上下文。

MetaGPT的主要特点包括:

  1. 稳定的解决方案:通过SOP,MetaGPT能够生成更一致和正确的解决方案,比其他智能体更可靠。
  2. 多样化的角色分配:为LLM分配不同的角色,确保了解决问题的全面性。

MetaGPT的组成部分包括:

  1. 智能体:在MetaGPT中,智能体不仅仅是单个实体,而是一个多智能体系统,其中每个智能体都具有独特的LLM、观察、思考、行动和记忆能力。
  2. 环境:环境是智能体互动的平台,它们从环境中获得信息,并将行动的输出结果发布到环境中,供其他智能体使用。
  3. 标准流程(SOP):这些是管理智能体行动和交互的预定程序,确保系统内部的有序和高效运作。
  4. 评审:评审是为了解决幻觉问题。通过严谨有效的评审过程,可以大大降低复杂工作整体的错误率。
  5. 路由:通信是智能体之间信息交流的过程,对于系统内的协作、谈判和竞争至关重要。
  6. 订阅:订阅允许智能体关注特定的事件或变化,并根据需要进行调整和修改。
  7. 经济:这是多智能体环境中的价值交换系统,决定资源分配和任务优先级。

在这种结构下,MetaGPT能够将复杂的工作分解为更小、更具体的任务,从而提高LLM的输出质量。

类似于多个专家系统集成,保证稳定输出,且结合了强化学习的思想,和作者的思路类似,在业务环境中更有保障!值得学习!

2. 相关案例

项目名称项目链接研究思路官网
ChatDevGitHubChatDev 是一个虚拟软件公司,通过多智能体组织结构进行运营,这些智能体扮演不同角色,包括首席执行官、首席产品官、首席技术官、程序员、审稿人、测试人员和艺术设计师等。ChatDev.ai
AutoAgentsGitHubAutoAgents 是一个实验性的开源应用程序,用于基于 LLM 的自动代理生成实验。该程序由 LLM 驱动,自动生成多智能体以实现设定的任何目标¹。
AgentsGitHubAgents 是一个用于构建自治语言代理的开源库/框架。它支持重要功能,包括长短期记忆、工具使用、网络导航、多智能体通信,以及包括人机交互和符号控制在内的全新功能。
CamelGitHubCamel 探索了一种名为角色扮演的新型交际代理框架,使多个智能体能够进行对话并合作解决分配的任务。智能体会被分配不同的角色,并被期望应用他们的专业和知识来找到满足他们共同任务的解决方案⁶。
AutoGenGitHubAutoGen 是一个框架,它支持使用多个代理开发 LLM 应用程序,这些代理可以相互交谈以解决任务。AutoGen 代理是可定制的、可对话的,并且无缝地允许人工参与。AutoGen.ai

四、总结

  • 在本文中,作者基于开源的学习文档,全面的讲解了AI Agent的概念和组成,并且通过代码案例分别测试了其组成模块,相信各位读者如果跟着的流程走了一遍,应该对AI Agent有一个大致的了解了,;
  • 代码案例中有趣的程序和对作者社会价值的实现是作者创作的源动力,作者很久没有写文章了,希望这篇读者们喜欢,当然不喜欢也可以在评论区指出我的问题;
  • 后面的文章中,我专注于开发Agent实战,积累开发经验和技巧,最后将其落实为方法论,并通过文章分享出来;一起加油!😁😁😁

文章参考

  • 学习文档
  • Langchain
  • ChatDev: 大模型AI Agent驱动的虚拟软件公司
  • ChatDev.ai | ai agent
  • ChatDev
  • AutoAgents: 一个开源的自主语言代理框架
  • AutoAgents中文文档
  • AGI控制世界——CAMEL
  • AutoGen: Microsoft的一个代码生成框架
  • AutoGen官方网站
  • 任意场景,动嘴就能创建专属AI智能体小队的AutoAgents
  • 北京未来式智能科技有限公司 | AutoAgents.ai | AI Agents大规模并行协作平台
  • NeurIPS 2023|AI Agents先行者CAMEL:第一个基于大模型的多智能体框架
  • 教AI Agents学会协作&竞争!首个大模型多智能体框架CAMEL亮相
  • 一文读懂Agent,大模型的下一站
  • 大模型AI Agent前沿调研
  • AutoGen实战应用(二):多代理协作(Multi-Agent Collaboration)
  • Language Models are Few-Shot Learners
  • ChatDev GitHub仓库
  • Bilibili视频:AutoAgents,一个开源的自动化代理框架
  • AutoAgents GitHub仓库
  • AutoGen文档
  • AutoGen路线图

项目附录

  • Github地址
  • LangChain拓展阅读
  • Agent拓展阅读
  • LLM拓展阅读

如果觉得我的文章对您有帮助,三连+关注便是对我创作的最大鼓励!或者一个star🌟也可以😂.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++设计模式|创建型 5.原型模式

1.什么是原型模式? 原型模式⼀种创建型设计模式,该模式的核⼼思想是基于现有的对象创建新的对象,⽽不是从头开始创建。 在原型模式中,通常有⼀个原型对象,它被⽤作创建新对象的模板。新对象通过复制原型对象的属性和状…

Mysql数据存储格式分析

一、整体存储逻辑 1.1 Mysql数据存放位置 不同的存储引擎,对Mysql数据的存储是不同的。新建一个test数据库,里面有t1,t2和test5三张表,以Innodb和Myisam存储引擎为例: Innodb存储引擎: .frm文件:与表相…

【Nginx】如何在 Nginx 中阻止来自特定国家的 IP 地址访问

文章目录 前言一、准备工作二、查看 Nginx 服务器都拥有哪些模块2.1 先查看本地nginx是否有ngx_http_geoip2模块2.2 安装nginx并配置ngx_http_geoip2模块2.2.1下载所需版本的nginx到服务器2.2.2 先安装所需依赖2.2.3 解压文件2.2.4 下载ngx_http_geoip2模块2.2.5 编译安装nginx…

解决webstorm没有vue语法提示;webstorm没有代码提示

解决webstorm没有vue语法提示;webstorm没有代码提示 使用webstorm 2023.x 开发vue项目。发现死活没有vue语法提示,即便是npm install、清理缓存。对比其他vue项目却有语法提示,最后发现依赖库被忽略了: 删除掉node_modules 的忽略…

国外IP代理免费试用技巧

随着互联网的普及,人们越来越依赖于网络来获取信息、进行交流和娱乐。国外IP代理就成了利器之一。在本文中,我们将探讨如何免费使用国外IP代理。 一、了解国外IP代理的原理 国外IP代理,简单来说,就是通过连接到位于国外的代理服务…

linux 环境下 分布式文件搭建fastDFS

1.软件信息 地址:happyfish100 (YuQing) GitHub 1.fastdfs-master.zip 2.fastdfs-nginx-module-master.zip 3.libfastcommon-master.zip 4.libserverframe-master.zip yum install make cmake gcc gcc-c perl 2.安装libfastcommon unzip libfastcommon-mast…

怎么转换音频?看这3款音频转换器

随着数字媒体的发展,音频文件在我们的日常生活中占据了越来越重要的地位。有时候在不同的应用场景里,无论是音乐、语音还是其他类型的音频内容,我们都需要对其进行转换以满足不同的需求。 本文将为您介绍3款常用的音频转换器,帮助…

代码随想录训练营Day31:动态规划3:0-1背包

1.0-1背包基础 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。 1.1动态规划五部曲 确定dp数组以及下标的含义:dp[i][j] 表示…

sql操作、发送http请求和邮件发送 全栈开发之路——后端篇(2)

全栈开发一条龙——前端篇 第一篇:框架确定、ide设置与项目创建 第二篇:介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇:setup语法,设置响应式数据。 第四篇:数据绑定、计算属性和watch监视 第五篇 : 组件…

RK3566(泰山派):3.1寸屏幕D310T9362V1SPEC触摸驱动(竖屏)

RK3566(泰山派):3.1寸屏幕D310T9362V1SPEC触摸驱动(竖屏) 文章目录 RK3566(泰山派):3.1寸屏幕D310T9362V1SPEC触摸驱动(竖屏)电路配置i2c1设备树创建驱动编写…

什么是50etf期权?期权的三级交易权限是什么?

今天期权懂带你了解什么是50etf期权?期权的三级交易权限是什么?期权三级交易权限,作为股票期权交易中的最高级别权限,赋予了投资者更广泛、更灵活的交易选择和策略。 什么是50etf期权? ETF期权是指在支付一定额度的权…

如何让机器理解人类语言?Embedding技术详解

如何让机器理解人类语言?Embedding技术详解 文章目录 如何让机器理解人类语言?Embedding技术详解介绍什么是词嵌入?什么是句子嵌入?句子嵌入模型实现句子嵌入的方法值得尝试的句子嵌入模型 句子嵌入库实践Step 1Step 2Step 3 Doc2…

SwiftUI中三大渐变色的介绍

在SwiftUI中,渐变色是一种常用的视觉效果,用于创建平滑过渡的颜色变化。通过使用渐变色,我们可以实现丰富多彩的界面设计,增强用户体验。 1. 渐变色的种类和用途 种类: 线性渐变(Linear Gradient&#x…

oracle多条重复数据,取最新的

1、原理讲解-可直接看2 筛选出最新的 SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (PARTITION BY LOCALAUTHID ORDER BY LASTUPDATETIME DESC) AS rn FROM USER_LOCALAUTH_STATE t ) t WHERE t.rn 1; 解释: 这个序号是基于[LOCALAUTHID]字段进行分…

计算机vcruntime140.dll找不到如何修复,分享5种靠谱的修复教程

当您在运行某个应用程序或游戏时遇到提示“找不到vcruntime140.dll”,这通常意味着系统中缺少了Visual C Redistributable for Visual Studio 2015或更高版本的一个重要组件。这个错误通常发生在运行某些程序时,系统无法找到所需的动态链接库文件。小编将…

ASP.NET医药进销存系统

摘 要 目前,大中型城市的多数药品店已经实现了商品管理、客户管理、销售管理及销售管理等的信息化和网络化,提高了管理效率。但是,在大多数小药品店,药品店管理仍然以传统人工管理为主,特别是在药品的采购、销售、库…

电商核心技术揭秘56:客户关系管理与忠诚度提升

相关系列文章 电商技术揭秘相关系列文章合集(1) 电商技术揭秘相关系列文章合集(2) 电商技术揭秘相关系列文章合集(3) 文章目录 引言客户关系管理(CRM)的重要性提升顾客体验数据驱…

Springboot打包jar如何后台启动和查看日志?

如何后台启动Spring Boot的fat jar 使用nohup命令启动: 在Linux或Unix系统中,你可以使用nohup命令来启动jar包,以确保即使你关闭了终端或断开了SSH连接,程序仍然可以在后台运行。命令格式如下:nohup java -jar yourapp…

PCie协议之-TLP Header详解(一)

✨前言: 在PCIe通信过程中,事务层数据包(Transaction Layer Packets,简称TLP)扮演着非常重要的角色。TLP用于在设备之间传递数据和控制信息,它们是PCIe的基本信息传输单元。 TLP可分为几个部分&#xff0c…

硬盘格式化后能恢复数据吗?这个恢复方法电脑小白也能用!

硬盘格式化后能恢复数据吗?对于这个问题需要先了解清楚硬盘格式化和数据恢复的原理了。 目前我们所说的硬盘格式化通常都是指“快速格式化”,一般来说当我们在清理磁盘空间或者新建磁盘分区时会使用到这个功能,最终的结果就是清除掉磁盘上的…