本文深入探讨了大型语言模型在三种模态(文字、图像、语音)之间的转换能力,并指出实际业务场景中更常见的是模型与工具的集成。文章详细解析了智能体的基本原理,即通过“大模型+工具调用”模式实现业务操作。重点介绍了LangGraph框架,阐述了如何通过定义状态、构建工作流图、实现流式输出和异步处理等功能,驱动复杂的业务流程。此外,文章还讨论了消息类型、上下文长度控制以及reducer自动合并数据等关键概念,为读者提供了构建高效智能体的实用指南。
01 大模型三种模态之间的转换
大模型本质上是一个"字符串进、字符串出"的系统,视觉类的模型(VLM),就是"图像进、字符出"。这一代大模型基本上就是三种模态在互相转来转去:文字、图像、语音(音频)——文字进文字出、图像进文字出、音频进文字出,总归是这三种模态之间来回转换。
对照工业界和企业里的实际场景,你会发现大部分时候并不存在"三种模态互相转换"这种需求,真实需求往往是另外的样子:
- 自动驾驶: 它不需要图像对话,也不需要语音交互,它希望你能够输出控制指令;****
- 公司报销流程: 希望人工智能能够自动判断并流转报销流程。
实际业务要的不是"转一段文字出来",而是"直接操控某个系统"。这类需求已经明显超出了大模型的能力范围——不在它的能力范围之内了。一个直观的例子:你让大模型去操纵一下数据库,它根本干不了这个活。它能做的只有生成一段字符串,仅此而已。
02 智能体的基本原理大模型+工具调用。
智能体就是来解决这个问题的。设想这样一个场景: 有一个系统,在大模型出现之前,这个系统对外暴露的是**API,**通过API与系统交互,比如执行一条SQL 语句、调度某个任务,一定有这样的接口存在。
智能体的做法是: 让大模型输出API名称 + API 的参数。大模型输出仍然是字符串, 这一点没有变; 输出完成之后, 由代码来解析这段字符串, 选择合适的API、把参数传进去、调用现有的系统。
整条链路就通了: 大模型负责"想"(决定调用什么、传什么参数), 代码负责"做"(真正去调用系统)。让大模型直接调用系统是不可能的, 因为它的输入输出只有字符串,但让它输出"API名称 + 参数", 再由代码执行就完全可行。
这就是Function Call(函数调用),在 Function Call语境下,以前叫"函数(Function)",现在叫工具(Tool)。它本质上就是一个函数。
两个关键问题:工具信息从哪来与输出如何解析
问题一:大模型怎么知道我们公司有什么函数?
大模型是阿里训练的, 它不知道我们公司内部有哪些函数,因此,必须把函数信息提供给大模型——也就是说, 在大模型输入里, 要包含函数信息: 有哪些工具、工具的名字、参数是什么, 都要提前告诉大模型,这是第一步。
问题二:大模型输出的东西, 是否能够进行解析?
大模型吐出来的是一段字符串,如果格式不受控, 你的代码就没法解析。因此第二步必须对输出schema(格式)约束: 要求输出保持特定的结构, 比如JSON格式或者Markdown格式, 保证下游代码能够解析——不允许模型自由发挥。
这套原理和LangChain是完全一致的。事实所有的智能体框架, 底层都是这个原理。
Instruct模型与官方模板: 结构化输出为什么几乎不出错
我们可以观察下千问(Qwen)模型发布的文件。任何一个大模型——千问、LLaMA,发布时都会附带一套配套文件, 一个是tokenizer(分词器)文件, 另一个config(配置)文件。在config 文件里会有一个对话模板(chat_template)字段, 专门定义了tools(工具)相关模板。
使用时只需要把工具信息(名称、参数说明等)往模板里填——不需要手动填, 框架会有一套代码帮你全部填好。
这样做的收益是按照它的模板输入, 它出来的东西就能保证是结构化的、可解析的。因在训练的时候就是这样训练的——以固定的格式进、以固定的格式出, 大量训练样本都遵循这个模式, 所以输出天然可解析。当然,这也对基座模型的强度提出了要求:基模要足够强,这个保证才成立。
现在的大模型, 都叫Instruct(指令型)模型,指的就是"指令遵循"(instruction following)——保证规范输入,就规范输出。这就是Function Call的底层逻辑。
三种输出约束方式的对比
约束大模型的输出格式一共有三种途径:
自己写工具提示词、指定格式也可以,但不能保证100%;
用官方的模板,虽然严格但也不能保证100%,但几乎接近100%,实际使用中几乎看不到出错——因为训练的时候就是这样大量训练的。
LangChain的OutputParser方法用于指定输出格式,并不局限于工具调用。它的原理是不断重试——它不会去干涉大模型运行的本身,只是对产出的结果做判断:不满意,重新再调一次;还不满意,再调一次。它是靠这种重试机制来提高成功率的,而不是靠改变模型的行为。
上下文工程Context Engineering
回到智能体本身。前面这一整套机制——工具信息的注入、格式的约束——其实都是通过提示词(输入内容)来实现的。所以现在这个事情有一个更准确的名字:上下文工程(Context Engineering)。
上下文工程做的事情是: 你要把工具的信息、背景的信息、记忆的信息,全部组织好提供给大模型,让大模型只承担逻辑判断这一件事。这样一来,大模型就可以完成工具调用了。
03 从一次调用到工作流
只靠上下文工程,只能完成大模型的一次工具操作。在实际工作中,我们更多面对的是另一个——工作流(Workflow)。
工作中经常会出现这样的场景:一个流程由多个步骤组成,步骤之间有判断、有流转、有回退。这种"多步骤、带流转"的流程,才是业务的真实形态。所以只有上下文工程是不够的,还需要一套描述和驱动工作流的办法。
线性工作流示例:请假系统
以请假系统为例。假设有一个人要操作请假系统:
- 提单:填写假期天数、休假地点等信息;
- 系统判断:比如要休5天假,先查数据库看年假余额、天数是否合规、地点能否去;
- 判断不通过:打回,重新提单;
- 判断通过:进入下一步审批——可能层层审批,一级一级往上走;
- 审批完成:开始休假;
- 休假回来:办理销假。
至此,整个流程才算完成。每一步都可以用一个函数(function)来完成判断。比如提单完成之后,程序去调数据库,查这个人还剩多少天年假,做一个比较,得出结果;审批环节则是人工参与、层层流转。所以在实际工作当中,我们面对的更多是工作流的形态。请假这个例子还相对简单、比较线性的流程。
复杂工作流:循环分支与状态机
而有些工作流可能是这样的:来一个输入,中间某个位置会循环地走,还有分支;这个分支又会跳到那个分支,那个分支又可能跳回前面,最后才走到结束。
相对于请假那种线性流程,实际业务中会有各种"奇奇怪怪"的流程形态。仔细想想就会发现:即使是请假流程,严格说也不是纯线性的——它至少带有一个"打回重提"的反馈。
再进一步观察这些流程的本质:各种状态之间来回转移——非常像状态机(State Machine):状态互相转移、来回切换。画出来就是一张图(Graph):节点之间互相转来转去,状态转移来转移去。这才是实际工作中最有可能见到的工作流形态。
LangGraph的定位
用一套框架来解决"这张图"的问题就是LangGraph。
对LangGraph的定位:
LangGraph处理的对象是工作流(图)
——有没有大模型都可以,本身与LLM没有绑定关系;
只不过现在基本上会把**LangGraph+大模型=智能体,**用LangGraph搭工作流,在图的每个节点埋一个大模型调用, 整套叫智能体。
LangGraph就是用来控制工作流的。LangGraph只是把"构建工作流图"这件事通用化、框架化了。
04 实战:LangGraph图的状态State
智能体的三种实现层次:低代码、中代码与高代码,Coze、Dify这些平台都是智能体,它们属于层层封装的产物。只要完成了"大模型 + 工具(工作流)",都可以叫智能体。因此实现智能体有三个层次:
LangGraph正好处在中间状态——有代码量,但不算离谱,对工程师来说用起来比较舒服。
第一张图:三节点线性工作流
LangGraph的核心是一张图。假设构建三个节点:node_1、node_2、node_3,先把这张图搭起来——这是一个固定的套路。
状态State:全图共享的一份数据
图里有一个核心概念叫做状态(State)。
- State是数据:整张图只有一份数据,全图共享;
- 图上所有的节点,都对这一份数据做增删改查。
所以构建图的第一步,是先把这份数据定义清楚。代码里State一定要是字典型——本例中用TypedDict做一个强制判断:
classMyState(TypedDict): foo:str字典里有一个字段foo,它对应的数据类型是字符串。
做"强制判断"是因为Python和Java这类语言不太一样:在Java里,声明一个变量是int型、是字符串型,如果传入的类型不对,直接报错;但Python的类型标注默认只是提醒——这里标注用整型,但偏偏不用整型,用浮点数、用字符串,一样不会报错。所以LangGraph使用了TypedDict做一个强制****类型校验,防止"声明了却不遵守"的情况。
每个节点可以理解成一个函数:字典进、字典出,只不过这个字典用强制类型(TypedDict)做了要求——进来的数据是字典,出去的数据类型也是字典。
构建一张图的固定套路
定义好State之后,开始构建图:
# 构建一张图,图上对应的数据类型就是这个字典 builder = StateGraph(MyState) builder.add_node("node_1", node_1) builder.add_node("node_2", node_2) builder.add_node("node_3", node_3)用StateGraph(MyState)创建一个图构建器(graph builder),声明这张图使用MyState这份数据;给这张图加上三个节点node_1、node_2、node_3,每个节点都对应一个处理函数。
START与END:两个特殊节点,START叫做图的起始位置的特殊字段。
连边的顺序是:START到node_1,node_1流向 node_2,node_2流向node_3,node_3再到 END。END也是一个特殊字段,表示结束。
builder.add_edge(START, "node_1") # START 是特殊字段 builder.add_edge("node_1", "node_2") builder.add_edge("node_2", "node_3") builder.add_edge("node_3", END) # END 也是特殊字段这个边是有向单向边,只能从 node_1 流向 node_2;node_2 是返回不了node_1 的——除非你再显式地连一条 node_2 到 node_1 的边。所以图里若要"往回走",必须专门把反向的边加上。
START不处理任何东西,它仅仅是一个开始符。实际的执行从node_1开始,沿着边走,走到 END,这张图就结束了。这就是一张图的完整流程。
编译与启动:compile与invoke
图搭好之后,要做一次编译:
graph = builder.compile()result = graph.invoke({"foo":"My"})compile()让这张图成为一张真正可执行的图。graph.invoke(...)启动这张图。传入的信息有两个要求:
一定要是字典型;
一定要包含State中定义的必填字段
(本例中即
foo)。
数据流演示:三个节点的接力
三个节点的处理函数如下,每个节点接收 state、返回对state的修改:
def node_1(state: MyState) -> MyState: print("node1", state) return {"foo": state["foo"] + " name"} def node_2(state: MyState) -> MyState: return {"foo": state["foo"] + " is"} def node_3(state: MyState) -> MyState: return {"foo": state["foo"] + " Lance"}传入{"foo": "My"}后,数据在图上是这样流转的:
node_1接收到的state是invoke传进去的;它的返回值成为node_2的输入;node_2的返回值又成为node_3的输入——一进一出,接力传递。node_3执行完到达END,流程结束。所以最终:
result = {"foo": "My name is Lance"}运行程序,输出结果正是My name is Lance。
TypedDict的校验行为:少了报错多了无效
再来看TypedDict校验的边界行为。代码里强制要求输入包含foo字段,那么:
- 传少了(缺 foo 字段): 直接报错。错误信息会明确提示必须包含这个字段;
- 传多了(带额外字段):不报错,但也没用。TypedDict只检查"我要的东西有没有",多了并不检查。node_1实际收到的输入,会发现仍然只有
foo——多余的字段根本传不进图里。
一句话总结:少了会报错;多了不报错,但传不进来、没有用。
所以写LangGraph程序的第一件事,就是把状态State定义清楚。
这是最简单的一张图、一个工作流,你会发现它和大模型没有一点关系——不用大模型,一样能用LangGraph。
05 实战:LangGraph图的stream流式输出
除了invoke之外,LangGraph还提供graph.stream():以流的方式逐节点输出中间状态,每个节点执行完就立刻吐出一条该节点的更新,便于观察图内每一步的数据变化。图的结构完全相同,只是把最后的invoke换成了stream:
graph = builder.compile() #graph.invoke({"foo":"My"}) for s in graph.stream({"foo": "My"}): print(s) --实际输出 {'node_1': {'foo': 'My name'}} {'node_2': {'foo': 'My name is'}} {'node_3': {'foo': 'My name is Lance'}}第一个节点的输出、第二个节点的输出、第三个节点的输出,按顺序逐个打印——用户端就能实时看到图的推进过程。注意:stream默认吐出的是{节点名: 本步更新的字段},即本步更新了什么,而不是完整state。
对比一下:如果用 invoke,中间过程什么都不会吐,只在全图走完后一次性返回最终结果;想在节点里看中间状态,就得自己在节点函数里写 print。
State定义的两个字段上——MyState里定义了foo和node2(文件开头的注释也点明了:“只会输出state里面定义的字段”):
### langgraph初步,只会输出state里面定义的字段 class MyState(TypedDict): foo: str node2: int图还是刚才那张图,没有变。变化在于:每个节点在返回foo之外,还会额外附带返回一个字段——node_1返回node1,node_2返回node2,node_3 返回node3:
def node_1(state: MyState) -> MyState: print("node1 input", state) return {"foo": state["foo"] + " name", "node1": 100} def node_2(state: MyState) -> MyState: print("node2 input", state) return {"foo": state["foo"] + " is", "node2": 200} def node_3(state: MyState) -> MyState: print("node3 input", state) return {"foo": state["foo"] + " Lance", "node3": 300}运行之前可以先自己推断一下结果:后面节点返回的数据里,只有State里定义过的foo和node2这两个字段会被保留、进入输出——State里定义了几个字段,就只认这几个字段;node1、node3这些字段虽然节点返回了,但在流转过程中都会被过滤掉。
实际运行输出:
node1 input {'foo': 'My'} node2 input {'foo': 'My name'} node3 input {'foo': 'My name is', 'node2': 200} {'foo': 'My name is Lance', 'node2': 200}对照输出可以看得很清楚:node_1返回的node1: 100没有出现在 node_2 收到的输入里(被过滤);node_2 返回的node2: 200因为在 State 里有定义,所以留在了 state 里,一路出现在 node_3 的输入和最终结果中;node_3 返回的node3: 300同样被过滤——最终结果里只有foo和node2。
这里再明确一条规定:节点之间传递的数据必须是字典。这是 LangGraph的规定——你要用它的框架,就得按它的规范来走。
06 实战:LangGraph图的异步处理ainvoke与并行执行
用sleep模拟耗时操作
图还是那张图,例子还是那个例子,只改一处:node_1每次处理时会暂停并睡1秒:
def node_1(state: MyState) -> MyState: time.sleep(1) return {"foo": state["foo"] + " name"}我们准备两张一模一样的图(同一张图执行两次),分别演示串行和并行。
串行执行的现象
串行就是最普通的写法:先invoke第一张图,走完了再invoke 第二张图:
result1 = graph.invoke({"foo": "My"}) print(result1) result2 = graph.invoke({"foo": "My"}) print(result2)运行时的输出节奏是:先打印第一张图的中间过程(node_2 的输入状态),走完,打印第一张图的最终结果;然后才开始第二张图,再打印它的中间过程和最终结果。一图走完再走一图,这就是串行——正常的流式顺序。
并行执行的写法与现象
并行要用异步接口ainvoke——在 invoke前面加一个a就可以了:
async def run(): # 两张图并行处理 tasks = [graph.ainvoke({"foo": "My"}), graph.ainvoke({"foo": "My"})] result = await asyncio.gather(*tasks) # 结果是一个列表 print(result) asyncio.run(run())写法要点:把需要并行的异步函数放进一个数组(tasks)里,直接用asyncio.gather调用,最终出来的结果就是一个列表,里面是两个任务各自的结果。实际运行输出:
{'foo': 'My name'} {'foo': 'My name'} [{'foo': 'My name is Lance'}, {'foo': 'My name is Lance'}]前两行是两张图各自 node_2 打印的中间状态——几乎同时出现,说明两张图在同时推进;最后一行才是 gather 汇总出来的结果列表。
对比运行结果,区别非常明显:两张图的中间结果会同时打印出来,然后才打印最终结果。也就是说,第二张图不需要等第一张图执行完——两张图是在同时推进的。中间结果能同时打出来,说明两个 node 是在同时被调用的。
为什么异步如此重要
这种并行(异步)机制非常、非常重要。为什么?
本例里的耗时只是time.sleep(1);但在真实使用 graph 的过程中,图里会大量调用大模型,而大模型的响应速度是比较慢的。如果没有异步处理机制,整条链路全部串在一起,总耗时就会非常长。
举个例子:一个流程里需要分别调用两次大模型,如果串行调用,等待时间就是两次调用的累加;如果把大模型的调用形式从串行变成并行,总时间就约等于较慢的那一次。
智能体的代码里, 异步处理机制非常多, 几乎是标配。
Python异步的固定套路
- 内部用了异步(ainvoke), 外层函数就要加
async关键字; - 用
await等待异步结果; - 最后用
asyncio.run()启动整个异步函数。
这就是Python异步的标准套路,关键词就这几个。
总结下:整体输出有invoke, 流式输出有stream; 而invoke和stream 都各有对应的异步版本(ainvoke、astream), 前面加a即可。
07 实战:LangGraph图的Reducer用Annotated自动合并数据
手动拼接的麻烦
前面几个例子里, 拼接字符串都是手动做的:每个节点先把当前的值拿出来,再和自己想加的内容相加,返回拼接后的结果。这样写比较麻烦、不方便——每次都要"读一个东西,跟现在的东西相加"。因此LangGraph提供了一个更简便的方法。
Annotated注解的语法结构
图还是原来那张图,没有变化,只改 State 的字段定义——给foo加上Annotated注解(这是 Python比较新的语法):
def concat_str( left: str, # 节点输入(该字段当前值) right: str, # 节点 return(节点真正的输出) ) -> str: return left + right class MyState(TypedDict): foo: Annotated[str, concat_str]Annotated里面有两样东西:
- 第一个元素
str:表示字段的类型,这个没有问题; - 第二个元素
concat_str:是对字段的附属信息/说明——本质上是一个函数,这里它的功能是"拼接两个字符串"。
执行逻辑:left 与right
看它怎么生效。假设输入是空字符串{"foo": ""},node_1 返回{"foo": "你"}。此时框架会自动做一件事:
- 把该字段当前的值(节点的输入,这里是空字符串)作为left;
- 把节点的return 值(这里是 “你”)作为right;
- 两者一起进入
concat_str,函数的返回值(left + right)成为真正的输出,传给下一个节点。
也就是说框架会把这个字段的输入和输出, 用指定函数自动连接起来。
三个节点分别返回"你"、"好"、"啊",运行结果是"你好啊"——框架自动完成了拼接,再也不用在节点里手动写"取出当前值再相加"了。
反例:不加reducer会怎样
做个思想实验:如果不要这个函数,foo仍然声明成普通的str,结果会是什么? 答案是:只剩最后一次写入的结果——"啊"。
因为普通字段的更新逻辑是"覆盖":每个节点返回什么,该字段就被覆盖成什么,三个节点依次写入 “你”、“好”、“啊”,最后留下的只有 node_3 的"啊"。
reducer的本质
给字段挂上这样一个附属函数,就能够对字段的输入和输出进行自动合并(merge)——这也叫做reduce功能:对数据做一次合并。
几个要点固定不变:
- reducer函数固定只有两个参数:左边是节点的输入,右边是节点的输出(节点的 return);
- 函数的返回值作为真正的输出,传给下一个节点——严格说,节点的return只是"半成品",经过reducer之后的值才叫真正的输出;
- 用一张图来理解它的位置:节点有输入、有 return,reducer 做的事就是把这两者再串联一次,得到真正的输出。
这就是一个书写技巧,也可以理解成LangGraph特有的一些语法。大家可以发现,这些内容跟大模型都没有太大关系,讲的始终是"怎么玩这张图"。
数组类型的Reducer
字段是数组类型的reducer:
如果foo声明为数组,而节点返回的还是字符串、reducer的参数还是字符串,就会出问题——字段定义是数组,写入的却是字符串,类型对不上。解决办法是把reducer也改成数组版本:
def concat_list( left: list[str], # 节点输入 right: list[str], # 节点 return ) -> list[str]: return left + right class MyState(TypedDict): foo: Annotated[list[str], concat_list]节点相应地返回数组:
def node_1(state: MyState) -> MyState: return {"foo": ["你"]} # node_2 返回 ["好"],node_3 返回 ["啊"]输入{"foo": []},最终结果是["你", "好", "啊"]——数组的逐次拼接。
核心规则:字段的类型、left的类型、right的类型,三者必须保持一致。不保持一致,就没法拼接、没法操作。
08 实战:LangGraph图的Message
MessagesState:一个特殊的字典
前面 State 里的字典都是我们自己定义的;这一部分换成 LangGraph 内置的一个特殊字典——MessagesState,并基于它调用真正的大模型。
AnyMessage 家族:四种常用消息类型
MessagesState之所以"特殊",在于它的messages字段是一个**list[AnyMessage]**——任何消息(AnyMessage)组成的列表。
AnyMessage是父类,LangChain常见的消息类型:HumanMessage、AIMessage、ChatMessage等,它们都是特定的数据形式。其中:
- AIMessage:大模型调用时,大模型给你返回的那条信息;
- HumanMessage:用户(人)输入的信息;
- SystemMessage:系统设定的信息;
- ToolMessage:工具执行结果的信息。
常用的是这四种:HumanMessage、AIMessage、SystemMessage、ToolMessage。先记住一个骨架:messages字段是一个"AnyMessage 父类组成的数组"。
add_messages:单条消息为什么也能输入
messages字段挂的注解函数是add_messages。它的实现有两个细节:
- 最典型的行为就是两个数组的拼接——把已有消息数组和新增消息数组拼起来;
- 有时输入的不是数组而是单条message也可以——因为它内部有个判断:如果message不是数组,就先把它变成数组。
这个设计放宽了对输入的要求: 既可以写单条信息, 也可以写数组信息;写单条的时候,框架会在后面自动把它变成数组。
上下文长度的隐患与截断
注意: 消息数组按这个趋势不断累加,最终会超出大模型的上下文窗口,程序直接崩掉。
所以实际应用中, 肯定还是要做一些处理:对messages的长度进行控制——长到一定程度给它截断。截断意味着"遗忘", 这是工程上接受的代价。
图的结构:human_node 与 llm_node
图中有两个节点:
def human_node(state: MessagesState) -> MessagesState: human_input = input("用户: ") # 用户在键盘上输入,人机交互 return {"messages": HumanMessage(content=human_input)} def llm_node(state: MessagesState) -> MessagesState: response = llm.invoke(state["messages"]) # 把消息交给大模型 return {"messages": response}- human_node:让用户在键盘上敲入内容, 把人的信息包装成HumanMessage返回。因为
messages字段被注解了reducer(add_messages),所以这条返回会自动拼接到消息数组上; - llm_node:拿到输入的消息数组,调用大模型,把大模型的返回(response,一条AIMessage)作为结果返回。
图的连接是:START到human_node,human_node到llm_node,llm_node到END——人的输出(HumanMessage)传给大模型, 大模型返回结果, 作为整张图的最终结果。
第一次运行, 先注入一条系统消息:
result = graph.invoke({"messages": SystemMessage(content="我是智能聊天机器人")}) print(result)然后在键盘上问它"你是谁"。此时你的输入会被包装成一个HumanMessage交给大模型。最终图返回的结果是一大串信息,这一大串其实只有**messages一个字段**, 而messages对应的是一个数组, 数组里有三个元素:
三个元素被add_messages全部拼接进同一个数组。这就是一个最简单的对话系统工作流: 系统设定+用户提问+模型回答, 完整留在状态里。
补充: LangGraph可以看作LangChain的升级版, 基础知识是相通的。Chain(链)可以理解成一种特殊的、更简单的图; 图的能力比链更复杂,但核心概念一脉相承。
为什么要区分消息角色
明明只输入了一条消息, 为什么最后拼成了一个数组?——这正是messages字段上注解的函数(add_messages)在起作用。
分成SystemMessage、HumanMessage、AIMessage是因为大模型本身需要角色(role)信息。这些消息最终都会进入大模型, 必须区分每条输入到底是谁产生的——是人输入(Human)、系统设定(System)、还是大模型上一轮返回的(AI)。相当于告诉大模型: 这段对话由谁产生。
框架在这里封装得非常好, 不需要自己去拼这些字符串。要知道大模型刚出来、还没有这些框架的时候,工程师在公司里用模型, 全都是手工拼字符串——把角色标记、对话内容一个一个拼成模型要求的格式。有了这些封装之后, 这类工作都不需要做了。
而且可以预见: 大模型的标准本身也会越来越统一。慢慢地各家模型的模板会逐渐趋同, 最终都比较类似。
2026年AI行业最大的机会,毫无疑问就在应用层!
字节跳动已有7个团队全速布局Agent
大模型岗位暴增69%,年薪破百万!
腾讯、京东、百度开放招聘技术岗,80%与AI相关……
如今,超过60%的企业都在推进AI产品落地,而真正能交付项目的大模型应用开发工程师**,**却极度稀缺!
落地AI应用绝对不是写几个prompt,调几个API就能搞定的,企业真正需要的,是能搞定这三项核心能力的人:
✅RAG:融入外部信息,修正模型输出,给模型装靠谱大脑
✅Agent智能体:让AI自主干活,通过工具调用(Tools)环境交互,多步推理完成复杂任务。比如做智能客服等等……
✅微调:针对特定任务优化,让模型适配业务
目前,脉脉上有超过1000家企业发布大模型相关岗位,人工智能岗平均月薪7.8w!实习生日薪高达4000!远超其他行业收入水平!
技术的稀缺性,才是你「值钱」的关键!
具备AI能力的程序员,比传统开发高出不止一截!有的人早就转行AI方向,拿到百万年薪!👇🏻👇🏻
AI浪潮,正在重构程序员的核心竞争力!现在入场,仍是最佳时机!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】
⭐️从大模型微调到AI Agent智能体搭建
剖析AI技术的应用场景,用实战经验落地AI技术。从GPT到最火的开源模型,让你从容面对AI技术革新!
大模型微调
掌握主流大模型(如DeepSeek、Qwen等)的微调技术,针对特定场景优化模型性能。
学习如何利用领域数据(如制造、医药、金融等)进行模型定制,提升任务准确性和效率。
RAG应用开发
- 深入理解检索增强生成(Retrieval-Augmented Generation, RAG)技术,构建高效的知识检索与生成系统。
- 应用于垂类场景(如法律文档分析、医疗诊断辅助、金融报告生成等),实现精准信息提取与内容生成。
AI Agent智能体搭建
- 学习如何设计和开发AI Agent,实现多任务协同、自主决策和复杂问题解决。
- 构建垂类场景下的智能助手(如制造业中的设备故障诊断Agent、金融领域的投资分析Agent等)。
如果你也有以下诉求:
快速链接产品/业务团队,参与前沿项目
构建技术壁垒,从竞争者中脱颖而出
避开35岁裁员危险期,顺利拿下高薪岗
迭代技术水平,延长未来20年的新职业发展!
……
那这节课你一定要来听!
因为,留给普通程序员的时间真的不多了!
立即扫码,即可免费预约
「AI技术原理 + 实战应用 + 职业发展」
「大模型应用开发实战公开课」
👇👇
👍🏻还有靠谱的内推机会+直聘权益!!
完课后赠送:大模型应用案例集、AI商业落地白皮书