news 2026/4/26 4:13:12

LangGraph:基于图编程构建有状态多智能体工作流的核心原理与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangGraph:基于图编程构建有状态多智能体工作流的核心原理与实践

1. 项目概述:从LangChain到LangGraph的范式跃迁

如果你在过去一两年里深度参与过AI应用开发,尤其是基于大语言模型(LLM)构建智能体(Agent)或复杂工作流,那么“LangChain”这个名字对你来说一定如雷贯耳。它几乎成了连接LLM与外部工具、数据源的“标准胶水”。然而,随着我们构建的应用从简单的“问答机器人”演变为需要自主决策、状态管理、多步协作的“智能体系统”,原始的LangChain在编排复杂、有状态的流程时,开始显得有些力不从心。开发者们常常需要自己手动维护对话历史、管理工具调用状态、处理分支和循环逻辑,代码很快变得冗长且难以维护。

这正是“langchain-ai/langgraph”诞生的背景。它不是要取代LangChain,而是站在巨人的肩膀上,提供了一个全新的、图(Graph)驱动的编程模型,专门用于构建有状态的、多智能体协作的应用程序。你可以把它理解为给LangChain装上了“大脑”和“神经系统”,让它能够处理更接近人类工作方式的、非线性的任务流。简单来说,LangGraph的核心思想是:将你的应用逻辑建模为一个有向图(Directed Graph),图中的节点(Node)代表一个执行单元(如调用LLM、执行工具、条件判断),边(Edge)定义了节点间的流转逻辑。整个图会维护一个共享的“状态(State)”,随着执行在节点间流转,状态被持续读写和更新。

这解决了什么痛点?想象一下你要构建一个客服智能体,它需要:1. 理解用户问题;2. 根据问题类型决定是查询知识库、调用订单API还是转人工;3. 如果查询知识库无果,需要进一步追问用户细节;4. 最终生成回答并可能触发一个后续的满意度调查。这个过程充满了条件分支、循环和状态依赖(比如需要记住之前追问的上下文)。用传统的线性脚本写,会是一团乱麻。而用LangGraph,你可以清晰地画出这个流程的“地图”,每个步骤是一个节点,决策点是路由边,整个对话的上下文就是流动的状态。代码结构瞬间变得清晰、可维护且易于扩展。

2. 核心概念深度解析:图、状态与循环

要玩转LangGraph,必须吃透它的三个核心概念:图(Graph)、状态(State)和循环(Cycles)。这是它区别于其他编排框架的根本。

2.1 状态(State):应用的共享记忆体

在LangGraph中,状态是一个贯穿整个图执行周期的、可变的共享数据结构。它通常被定义为一个Python的TypedDict或Pydantic模型,明确规定了图中所有节点可以读写哪些数据。

from typing import TypedDict, Annotated from typing_extensions import TypedDict import operator class AgentState(TypedDict): # 用户输入的问题 input: str # 智能体生成的思考过程或中间答案 scratchpad: Annotated[list, operator.add] # 关键:这是一个可追加的列表 # 最终返回给用户的结果 final_output: str # 决定下一个节点的路由键 next: str

这里有一个精妙的设计:Annotated[list, operator.add]Annotated是Python的类型提示扩展,它在这里用于声明scratchpad字段的“归约(Reducer)”方式。operator.add意味着当多个节点并行执行并试图修改scratchpad时,它们的修改(列表)会被相加(合并),而不是覆盖。这是实现状态并发安全更新的关键机制。常见的Reducer还有operator.setitem(直接设置)用于覆盖型更新。理解并正确使用Reducer,是避免状态管理混乱的第一步。

注意:状态的定义决定了图的复杂度。初学者常犯的错误是把所有可能用到的数据都塞进状态,导致状态对象臃肿且难以理解。最佳实践是:仅定义节点间需要共享和传递的核心数据。节点内部的临时变量应保持在节点函数内部。

2.2 节点(Node):功能执行单元

节点是图的基本构成块,它是一个普通的Python函数(或可调用对象),接收当前状态作为参数,并返回一个对该状态的更新(字典)。

def llm_node(state: AgentState) -> dict: """调用LLM生成思考或回答的节点""" # 1. 从状态中获取所需信息 messages = state.get("scratchpad", []) + [HumanMessage(content=state["input"])] # 2. 执行核心逻辑(如调用LLM) # 这里假设有一个已初始化的chat_model response = chat_model.invoke(messages) # 3. 返回要更新到状态中的内容 return { "scratchpad": messages + [response], # 将LLM回复追加到思考过程 "next": "process_tool_decision" # 指示下一个节点 }

节点的设计原则是“单一职责”。一个节点最好只做一件事:调用一次LLM、执行一个工具、做一次条件判断等。这保证了节点的可测试性和可复用性。

2.3 边(Edge)与路由:控制流的导航系统

边定义了执行完一个节点后,接下来该去哪里。LangGraph提供了几种强大的路由机制:

  1. 条件边(Conditional Edge):根据状态中的某个值,动态决定下一个节点。这是实现分支逻辑的核心。

    from langgraph.graph import END def route_after_tool(state: AgentState) -> str: """根据工具调用结果决定路由""" last_action = state["scratchpad"][-1] if last_action.tool == "search_web" and not last_action.result: return "clarify_question" # 没搜到,去澄清问题节点 else: return "generate_final_answer" # 搜到了,去生成最终答案
  2. 入口与出口STARTEND是两个特殊的节点标识符,分别代表图的开始和结束。

  3. 并行边:通过配置,可以让一个节点同时激活多个下游节点,实现并行执行,最后通过Reducer合并结果。这对于需要同时咨询多个“专家”智能体的场景非常有用。

图的构建就是将节点和边组装起来的过程,形成一个完整的、可执行的工作流蓝图。

2.4 循环(Cycles):实现迭代与反思的关键

这是LangGraph最强大的特性之一。传统的流程工具很难优雅地处理“循环”,比如智能体需要反复尝试不同的工具直到成功,或者进行多轮自我反思和修正。在LangGraph中,创建一个循环简单到只需将一条边指回之前的某个节点。

例如,一个“写作助手智能体”的图可能包含一个“review_and_edit”节点。在生成初稿后,流程会进入该节点,由LLM评审内容。如果评审认为需要修改,就将next状态设置为“rewrite_draft”节点,从而形成“生成->评审->修改”的循环,直到评审通过,再将next设置为END

实操心得:引入循环时,必须设置明确的终止条件,否则会导致无限循环。通常有两种方式:1. 在状态中设置一个计数器(如iteration_count),在路由函数中检查是否超过最大次数;2. 让LLM在某个节点(如评审节点)做出“是否完成”的布尔判断。在实际应用中,结合两者更稳妥。

3. 从零构建一个研究助手智能体:完整实操

理论说得再多,不如亲手构建一个。我们来创建一个“研究助手智能体”,它能根据一个复杂问题,自动进行网络搜索、阅读相关资料、总结并最终生成一份结构化的报告。这个智能体将展示多工具调用、条件路由和简单循环。

3.1 环境准备与依赖安装

首先,确保你的Python环境(建议3.10+)并安装核心库。我们将使用LangChain的ChatOpenAI(或其它兼容模型)、Tavily搜索工具(一个针对AI优化的搜索API),当然还有LangGraph。

# 安装核心库 pip install langgraph langchain-openai tavily-python python-dotenv # 如果你使用Anthropic或其它模型,安装对应的LangChain集成包 # pip install langchain-anthropic

创建一个.env文件来管理你的API密钥(永远不要将密钥硬编码在代码中):

OPENAI_API_KEY=sk-你的OpenAI密钥 TAVILY_API_KEY=你的Tavily密钥

3.2 定义智能体状态与工具

我们的智能体状态需要跟踪问题、收集到的资料、中间草稿和最终报告。

import os from typing import TypedDict, Annotated, List from typing_extensions import TypedDict import operator from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_community.tools.tavily_search import TavilySearchResults from langchain_core.messages import HumanMessage, AIMessage, SystemMessage load_dotenv() # 1. 定义状态 class ResearchState(TypedDict): """研究助手的状态定义""" original_query: str # 原始问题 search_queries: Annotated[List[str], operator.add] # 生成的搜索查询词列表 search_results: Annotated[List[str], operator.add] # 搜索结果的摘要列表 draft_points: Annotated[List[str], operator.add] # 根据资料整理的要点草稿 final_report: str # 最终报告 needs_more_info: bool # 是否需要进一步搜索 iteration: int # 迭代次数,防止无限循环 # 2. 初始化模型和工具 llm = ChatOpenAI(model="gpt-4-turbo-preview", temperature=0.2, api_key=os.getenv("OPENAI_API_KEY")) search_tool = TavilySearchResults(max_results=3, tavily_api_key=os.getenv("TAVILY_API_KEY")) # 系统提示词,定义智能体角色和行为准则 system_prompt = SystemMessage(content="""你是一个专业的研究助手。你的任务是分析复杂问题,通过搜索获取信息,并综合成一份清晰、准确的报告。 请逐步思考,并充分利用搜索工具。如果现有信息不足以回答核心问题,你可以决定进行更多轮次的搜索。""")

3.3 构建图节点:分解任务流

我们将把研究流程分解为以下几个节点:

  1. 生成搜索查询(generate_queries):分析原问题,生成1-3个精准的搜索关键词。
  2. 执行网络搜索(web_search):调用搜索工具,获取信息。
  3. 分析搜索结果(analyze_results):阅读搜索结果,判断信息是否充足,并提取关键信息点。
  4. 撰写报告草稿(write_draft):基于已有信息,撰写报告要点。
  5. 判断是否需要更多信息(decide_further_research):决定是否开启新一轮搜索(形成循环)或结束。
  6. 生成最终报告(generate_final_report):整合所有草稿,润色成最终报告。

让我们实现前两个节点作为示例:

from langgraph.graph import StateGraph, START, END # 节点1:生成搜索查询 def generate_queries(state: ResearchState) -> dict: """分析问题,生成搜索查询词""" query = state["original_query"] prompt = f"""用户的研究问题是:{query} 请生成最多3个用于网络搜索的关键词或短语。确保它们精准、具体,能有效找到相关信息。 请以JSON列表格式返回,例如:["关键词1", "关键词2"]。""" message = [system_prompt, HumanMessage(content=prompt)] response = llm.invoke(message) # 解析LLM返回的JSON列表。实际应用中需要更健壮的解析。 import json try: queries = json.loads(response.content) except json.JSONDecodeError: # 如果LLM没返回标准JSON,尝试提取引号内的内容 queries = [q.strip('"\' ') for q in response.content.split('"') if len(q) > 2][:3] return {"search_queries": queries, "iteration": state.get("iteration", 0) + 1} # 节点2:执行网络搜索 def web_search(state: ResearchState) -> dict: """执行所有生成的搜索查询,并汇总结果""" all_results = [] for query in state.get("search_queries", []): try: results = search_tool.invoke(query) # 简化处理:取每个结果的摘要内容 for res in results: all_results.append(f"【查询词:{query}】{res.get('content', '')}") except Exception as e: all_results.append(f"搜索 '{query}' 时出错:{e}") return {"search_results": all_results}

3.4 组装图并设置条件路由

现在,我们将所有节点组装起来,并定义它们之间的流转关系。

# 初始化图构建器 workflow = StateGraph(ResearchState) # 添加节点 workflow.add_node("generate_queries", generate_queries) workflow.add_node("web_search", web_search) # 这里省略了其他节点的add_node代码,原理相同 # workflow.add_node("analyze_results", analyze_results) # ... # 设置边的流转 workflow.add_edge(START, "generate_queries") # 从开始到生成查询 workflow.add_edge("generate_queries", "web_search") # 生成查询后必然搜索 workflow.add_edge("web_search", "analyze_results") # 搜索后必然分析 # 关键:设置条件边 def decide_after_analysis(state: ResearchState) -> str: """分析结果后,决定下一步:继续搜索还是撰写草稿""" # 这里简化逻辑:如果迭代超过3次,或者分析节点已将needs_more_info设为False,则去写草稿 if state.get("iteration", 1) > 3 or not state.get("needs_more_info", True): return "write_draft" else: # 需要更多信息,则生成新的搜索词(注意:这里应避免重复之前的查询) return "generate_queries" # 这将形成一个循环 workflow.add_conditional_edges( "analyze_results", # 从哪个节点出发 decide_after_analysis, # 路由判断函数 { "write_draft": "write_draft", # 如果函数返回"write_draft",则跳转到write_draft节点 "generate_queries": "generate_queries", # 如果返回"generate_queries",则跳回该节点 } ) # 设置后续的边 workflow.add_edge("write_draft", "decide_further_research") workflow.add_edge("decide_further_research", "generate_final_report") workflow.add_edge("generate_final_report", END) # 编译图,得到可执行对象 app = workflow.compile()

3.5 运行与调试

编译后的app就是一个可调用的智能体。你可以通过stream方法观察其执行步骤,这对于调试复杂工作流至关重要。

# 定义初始状态 initial_state = { "original_query": "量子计算对现代密码学,特别是RSA加密,会产生哪些具体威胁?其发展现状和未来时间线是怎样的?", "search_queries": [], "search_results": [], "draft_points": [], "final_report": "", "needs_more_info": True, "iteration": 0 } # 运行智能体(流式输出,便于观察) for step in app.stream(initial_state, stream_mode="values"): node_name = list(step.keys())[0] print(f"\n--- 节点 [{node_name}] 执行完毕 ---") state_update = step[node_name] # 打印关键状态更新,避免输出过长 if "search_queries" in state_update: print(f"生成的搜索词:{state_update['search_queries']}") if "search_results" in state_update and state_update['search_results']: print(f"新增搜索结果数:{len(state_update['search_results'])}") if "draft_points" in state_update: print(f"草稿要点更新:{state_update['draft_points'][-1][:100]}...") # 打印最后一条的前100字符 # 获取最终状态 final_state = app.invoke(initial_state) print("\n=== 最终报告 ===") print(final_state["final_report"])

通过stream模式,你可以清晰地看到智能体是如何一步步“思考”和“行动”的:生成查询 -> 搜索 -> 分析 -> 判断是否需要新一轮搜索 -> 撰写草稿 -> 生成报告。这种透明性对于构建可信、可靠的AI系统至关重要。

4. 高级模式与生产级实践

当你掌握了基础的单智能体工作流后,LangGraph更强大的能力在于编排多智能体协作和构建持久化、可中断的长时程任务

4.1 多智能体协作:团队的力量

在真实场景中,一个复杂任务往往需要多个具备不同专长的智能体共同完成。例如,一个“产品设计评审系统”可能包含:

  • 产品经理智能体:负责理解需求,定义核心功能点。
  • 设计师智能体:根据功能点生成UI/UX描述。
  • 工程师智能体:评估技术可行性和实现成本。
  • 协调员智能体:汇总各方意见,推动讨论,做出最终决策。

在LangGraph中,每个智能体可以建模为一个子图(Subgraph)或一个复杂的节点。协调员智能体负责管理对话流,将问题路由给合适的专家,并整合他们的反馈。

# 概念性代码:多智能体协作框架 class TeamState(TypedDict): problem: str pm_opinion: str designer_opinion: str engineer_opinion: str discussion_log: Annotated[List[str], operator.add] final_decision: str def pm_agent(state: TeamState): # 产品经理的思考逻辑 ... return {"pm_opinion": "..."} def designer_agent(state: TeamState): # 设计师的思考逻辑 ... return {"designer_opinion": "..."} # 协调员节点:决定下一步问谁,或是否结束讨论 def moderator_node(state: TeamState): opinions = [state.get("pm_opinion"), state.get("designer_opinion"), state.get("engineer_opinion")] # 简单的协调逻辑:如果还有智能体未发言,则路由过去 if not state.get("pm_opinion"): return {"next": "pm_agent"} elif not state.get("designer_opinion"): return {"next": "designer_agent"} elif ...: ... else: # 所有人都发言了,进入决策节点 return {"next": "make_decision"}

通过这种模式,你可以构建出模拟真实团队辩论、评审、创作的复杂系统。关键在于设计好协调逻辑(路由)状态结构,使得各智能体的输出能够被有效地整合。

4.2 持久化与检查点:应对长时程任务

对于可能需要运行数分钟甚至数小时的任务(如处理长文档、监控系统),或者需要支持用户中途离开后再返回的场景,持久化(Persistence)是必须的。LangGraph原生支持与LangSmith等平台集成,也可以自定义持久化后端。

核心概念是检查点(Checkpoint)。在图执行到某个节点后,其完整状态(包括所有变量值)可以被保存到数据库(如SQLite、PostgreSQL、Redis)。当需要恢复时,可以从最后一个检查点加载状态,并继续执行。

from langgraph.checkpoint import MemorySaver # 使用内存检查点(适合演示,生产环境需用数据库后端) memory = MemorySaver() persistent_app = workflow.compile(checkpointer=memory) # 第一次执行,并创建一个线程(thread)来标识这次会话 config = {"configurable": {"thread_id": "research_thread_1"}} initial_state = {"original_query": "..."} result1 = persistent_app.invoke(initial_state, config=config) # 假设此时任务中断了... # 稍后,根据thread_id恢复任务状态,并继续执行(例如从上次中断的节点开始) # 你需要设计一个机制来知道“继续”的入口点,或者从最后一个检查点自动继续。 # 以下是一个概念性示例,实际API可能更复杂: # resumed_state = persistent_app.get_state(config) # 然后从resumed_state继续invoke或stream

在生产环境中,你需要:

  1. 选择合适的存储后端:根据并发量、状态大小和延迟要求,选择Redis(快)、PostgreSQL(可靠)或MongoDB(灵活)。
  2. 设计线程/会话模型:通常用user_id+session_idtask_id来唯一标识一个执行线程。
  3. 处理并发:确保同一线程的状态更新是串行的,避免竞争条件。LangGraph的检查点机制通常能处理这一点。

4.3 性能优化与监控

当你的图变得复杂、节点增多时,性能和维护性成为挑战。

  • 异步执行:如果节点主要是I/O密集型(如调用LLM API、访问数据库),强烈建议使用异步节点(async def)和异步图执行,可以大幅提升吞吐量。

    async def async_llm_node(state): # 使用异步客户端调用LLM response = await async_chat_model.ainvoke(...) return {"result": response}
  • 节点并行化:对于彼此独立的节点,可以利用langgraph.graph中的CONCURRENT模式或Pregel的并发能力来同时执行,缩短整体耗时。

  • 监控与可观测性:利用LangSmith等工具,你可以追踪每一次图执行的详细链路:每个节点的输入/输出、耗时、Token使用量、费用等。这对于调试、优化成本和理解智能体行为模式不可或缺。为关键节点添加自定义日志和度量指标也是好习惯。

5. 常见陷阱、调试技巧与最佳实践

在近一年的LangGraph项目实践中,我踩过不少坑,也总结出一些让项目更稳健的经验。

5.1 状态管理中的经典陷阱

  1. 状态污染:多个节点意外修改了同一个状态字段,导致难以追踪的错误。解决方案:严格定义状态结构,使用Annotated和Reducer明确每个字段的更新语义(是追加、覆盖还是合并?)。为节点函数编写清晰的文档,说明它读写哪些字段。

  2. 循环失控:智能体陷入死循环,不断搜索或思考。解决方案:如前所述,必须设置硬性终止条件(如最大迭代次数)。此外,可以在路由判断函数中加入更复杂的逻辑,例如检查最近几轮的状态是否没有实质性变化(陷入循环论证),或者让LLM在某个节点明确输出“任务已完成”的信号。

  3. 状态臃肿:随着执行步数增加,scratchpadhistory列表变得巨大,导致后续LLM调用Token数爆增、速度变慢、成本升高。解决方案:实现一个“总结”节点,定期将冗长的历史对话压缩成一段摘要,并替换掉旧的历史。或者,在设计之初就避免在状态中存储完整的原始消息,只存储提炼后的关键信息。

5.2 调试:让智能体的思考过程可视化

调试一个行为异常的智能体比调试普通代码更困难,因为你面对的是一个非确定性的LLM和复杂的交互逻辑。

  • 充分利用stream模式:这是你最好的朋友。在开发阶段,始终使用app.stream()来运行,观察每个节点执行前后的状态变化。这能帮你快速定位是哪个节点的逻辑出了问题,或者是路由判断有误。

  • 给LLM调用“戴上镣铐”:在Prompt中明确要求LLM以特定格式(如JSON、XML)或遵循严格规则(“必须从A、B、C中选择”)输出。这能极大提高输出的可解析性和稳定性。对于关键的路由决策,甚至可以要求LLM输出一个{“reasoning”: “…”, “decision”: “option_a”}的结构,便于你分析和调试。

  • 单元测试节点函数:尽管整个图是动态的,但每个节点函数是纯Python函数(给定输入状态,产生输出更新)。你可以为它们编写单元测试,模拟各种输入状态,验证其输出是否符合预期。这能确保每个“齿轮”本身是可靠的。

  • 使用LangSmith进行溯源:将你的应用与LangSmith集成。它提供了完整的执行轨迹、每个LLM调用的输入输出、耗时和成本。当用户报告一个奇怪的结果时,你可以通过LangSmith的Trace直接回放整个执行过程,像看录像一样找到问题根源。

5.3 架构与代码组织最佳实践

  1. 模块化设计:不要把所有节点函数都写在一个巨大的文件里。按功能模块组织:

    my_agent/ ├── __init__.py ├── graph.py # 图的定义和组装 ├── state.py # 状态类型定义 ├── nodes/ # 节点函数包 │ ├── __init__.py │ ├── planning.py │ ├── tools.py │ └── reasoning.py └── prompts.py # 集中管理所有提示词模板
  2. 配置化:将模型类型、API密钥、最大迭代次数、温度等参数提取到配置文件(如config.yaml或环境变量)中。这样可以在不修改代码的情况下,为开发、测试、生产环境配置不同的参数。

  3. 版本化你的图:当你对图的结构(增删节点、修改边)或节点内部逻辑进行重大修改时,考虑对图定义进行版本控制。这有助于回滚和A/B测试不同版本的智能体性能。

  4. 设计“安全阀”节点:在图中加入一些负责验证和过滤的节点。例如,在调用外部工具(如发送邮件、操作数据库)之前,加入一个“人工审核”节点或一个“安全检查”节点(检查内容是否合规),防止智能体做出不可逆的危险操作。

LangGraph不是一个“即插即用”的魔法盒,它提供的是一套强大而严谨的范式。初期的学习曲线可能比直接写脚本要陡峭,但一旦你习惯了这种“用图来思考”的方式,你会发现构建复杂、健壮、可维护的AI应用变得前所未有的清晰和高效。它迫使你明确地定义状态、划分职责、设计流程,而这正是软件工程的核心思想在AI时代的具体体现。从今天开始,尝试用LangGraph重新构思你手中的下一个AI项目,你会感受到这种范式带来的秩序之美。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/26 4:10:23

【解决】认证中心无法区别同一应用的不同app问题,实现多终端会话独立管理。

文章目录 引言 I 平台会话机制: 平台授码、应用刷新token机制 核心接口涉及 II 需求 背景 一、涉及的前端改造 二、涉及的后端改造 现状: III 登录设备授权 引言 建议一个应用只对应一个app,比如可以将写码和设备安装功能聚和到一个app,这样认证中心无需做任何的改动! 本…

作者头像 李华
网站建设 2026/4/26 4:07:18

如何用PX4神经网络控制技术实现自适应无人机飞行:3个实战技巧

如何用PX4神经网络控制技术实现自适应无人机飞行:3个实战技巧 【免费下载链接】PX4-Autopilot PX4 Autopilot Software 项目地址: https://gitcode.com/gh_mirrors/px/PX4-Autopilot 你是否曾为无人机在复杂环境中的控制难题而烦恼?当传统PID控制…

作者头像 李华
网站建设 2026/4/26 4:05:57

如何在GTA V中安全使用YimMenu:从新手到专家的5个关键步骤

如何在GTA V中安全使用YimMenu:从新手到专家的5个关键步骤 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/…

作者头像 李华
网站建设 2026/4/26 3:58:42

LSTM Seq2Seq模型实战:从零构建英法翻译系统

1. 从零构建基于LSTM的Seq2Seq机器翻译模型 在自然语言处理领域,序列到序列(Seq2Seq)模型是一种强大的架构,特别适用于需要将一个序列转换为另一个序列的任务,比如机器翻译、文本摘要和对话生成。本文将带你从零开始构…

作者头像 李华