从零构建AI Agent:基于LangChain与ReAct框架的智能研究助手实践
在实际 AI 应用开发中,我们经常面临一个困境:大模型本身很强大,能理解问题也能生成答案,但它就像一个知识渊博但“手无寸铁”的专家,无法主动查询信息、调用接口或执行计算。这就是 AI Agent 要解决的问题——它让大模型从一个“对话者”转变为一个能自主规划、使用工具、完成复杂任务的“行动者”。然而,很多开发者对 Agent 的理解停留在概念层面,面对 LangChain 这类框架时,往往被其复杂的抽象和快速迭代的版本所困扰,不知从何下手。
本文旨在彻底改变这一状况。我们将从一个最核心的机制——ReAct 循环——入手,解析 LangChain Agent 是如何工作的。然后,我们将完全从零开始,动手构建一个能自主进行网络搜索的智能研究助手。在这个过程中,你会理解 Agent 的四大核心组件(LLM、Agent、Tool、Memory)如何协同,并掌握将 Agent 从玩具 Demo 推向工程化落地所需的关键考量。无论你是想快速入门 LangChain,还是希望为现有系统引入自主决策能力,这篇文章都将提供一条清晰的实践路径。
1. 理解核心范式:从“对话”到“行动”的转变
在深入代码之前,必须理解 AI Agent 带来的根本性范式转变。传统的基于大模型的交互是“单轮问答”模式:用户输入问题,模型基于其内部知识生成回答。这种模式存在明显局限:模型无法获取训练数据之外的最新信息,也无法执行任何需要与外部世界交互的操作。
AI Agent 引入了“多轮规划与执行”的范式。它不再试图一次性给出最终答案,而是将任务分解为一系列“思考-行动-观察”的步骤循环。这个循环就是ReAct (Reasoning + Acting)框架的核心。在这个过程中,Agent(由大模型驱动)扮演“大脑”和“调度员”的角色,它负责:
- 推理:分析当前状态和任务目标,决定下一步该做什么。
- 行动:从可用的工具箱中选择合适的工具,并生成调用参数。
- 观察:接收工具执行的结果,更新内部状态。
这个循环会持续进行,直到 Agent 认为任务已经完成,最终输出结果。这种模式使得 AI 能够处理开放式、多步骤的复杂任务,例如“帮我研究一下 LangChain 1.0 的新特性并写一份摘要”,这远非一次简单的问答所能完成。
为什么需要 LangChain 这样的框架?理论上,你可以自己用 OpenAI API 和 Python 循环来实现 ReAct。但框架的价值在于将其中大量重复、易错的工程问题标准化,例如:
- 状态管理:如何在多次循环中维护对话历史、工具调用结果等状态。
- 工具抽象:如何将千差万别的 API、函数、数据库查询统一成 Agent 能理解的“工具”。
- 循环控制:如何设置超时、最大迭代次数,防止 Agent 陷入死循环。
- 可观测性:如何追踪和记录 Agent 的每一次决策和工具调用,这对于调试非确定性的 Agent 行为至关重要。
LangChain 正是为解决这些问题而生,它提供了一个构建、运行和观测 Agent 的高层抽象和底层运行时。
2. 环境准备与核心依赖安装
在开始构建 Agent 之前,我们需要准备好开发环境。本文将使用 Python 作为开发语言,并假设你已有基本的 Python 开发环境。
2.1 创建虚拟环境与安装 LangChain
首先,创建一个独立的 Python 虚拟环境是一个好习惯,可以避免包依赖冲突。
# 创建并激活虚拟环境 (以 macOS/Linux 为例) python -m venv langchain-agent-env source langchain-agent-env/bin/activate # 对于 Windows 用户 # langchain-agent-env\Scripts\activate接下来,安装 LangChain 的核心库。由于 LangChain 生态发展迅速,建议指定主要版本以确保示例代码的兼容性。同时,我们需要一个 LLM 提供商(这里以 OpenAI 为例)和一个搜索工具(这里以 Tavily 为例)。
# 安装 LangChain 核心库及 OpenAI 集成 pip install langchain langchain-openai # 安装 Tavily 搜索工具包 pip install tavily-python # 可选:安装 langchain-cli 用于项目脚手架(非必需,但推荐用于复杂项目) # pip install langchain-cli注意:LangChain 是一个元框架,其
langchain包提供了核心抽象,而langchain-*系列的包提供了与特定服务(如 OpenAI、向量数据库)的集成。根据你的需求,可能还需要安装其他集成包,如langchain-community(社区工具)。
2.2 获取 API 密钥
我们的 Agent 需要两个外部服务的 API 密钥:
- OpenAI API Key:用于驱动 Agent 的“大脑”(LLM)。你可以从 OpenAI Platform 获取。
- Tavily API Key:用于提供网络搜索工具。你可以从 Tavily 官网 注册获取免费额度。
获取密钥后,将其设置为环境变量,这是管理敏感信息的最佳实践。
# 在终端中设置(临时,重启终端后失效) export OPENAI_API_KEY="你的-openai-api-key" export TAVILY_API_KEY="你的-tavily-api-key" # 对于 Windows (Command Prompt) # set OPENAI_API_KEY=你的-openai-api-key # set TAVILY_API_KEY=你的-tavily-api-key # 对于 Windows (PowerShell) # $env:OPENAI_API_KEY="你的-openai-api-key" # $env:TAVILY_API_KEY="你的-tavily-api-key"更稳妥的做法是将这些变量写入项目的.env文件,并使用python-dotenv库加载。
# 安装 python-dotenv pip install python-dotenv创建一个名为.env的文件,内容如下:
OPENAI_API_KEY=sk-你的-openai-api-key TAVILY_API_KEY=你的-tavily-api-key然后在 Python 代码开头加载:
from dotenv import load_dotenv load_dotenv() # 加载 .env 文件中的所有变量 # 现在 os.getenv(‘OPENAI_API_KEY’) 就能获取到值了3. 构建你的第一个 AI Agent:智能研究助手
现在,让我们从零开始构建一个能自主进行网络搜索并整理答案的“智能研究助手”。这个 Agent 将完成以下任务:接收用户关于某个主题的提问,自动决定是否需要搜索,调用搜索工具获取信息,最后综合信息生成回答。
3.1 定义工具:赋予 Agent “手脚”
工具是 Agent 与外部世界交互的桥梁。我们首先定义一个网络搜索工具。
# research_agent.py import os from langchain.tools import tool from tavily import TavilyClient from dotenv import load_dotenv # 加载环境变量 load_dotenv() # 初始化 Tavily 客户端 tavily_client = TavilyClient(api_key=os.getenv(“TAVILY_API_KEY”)) @tool def web_search(query: str) -> str: “”“ 使用 Tavily 搜索引擎在互联网上搜索信息。 输入一个搜索查询词,返回相关的、简洁的搜索结果摘要。 参数: query (str): 搜索关键词,例如 “LangChain 1.0 new features” 返回: str: 搜索结果的文本摘要。 ”“” try: # 调用 Tavily 搜索 API,限制返回3条最相关结果 search_result = tavily_client.search(query=query, max_results=3) # 从结果中提取内容字段并拼接 contents = [result[“content”] for result in search_result.get(“results”, [])] combined_content = “\n\n”.join(contents) return f“根据网络搜索,关于 ‘{query}’ 的信息如下:\n{combined_content}” except Exception as e: # 工具调用失败时,返回清晰的错误信息,供 Agent 理解 return f“搜索工具调用失败,错误信息:{str(e)}。请尝试重新表述您的问题或稍后再试。” # 将工具放入列表,供后续创建 Agent 时使用 tools = [web_search]关键点解释:
@tool装饰器:这是 LangChain 将普通 Python 函数转化为 Agent 可识别工具的标准方式。装饰器会自动处理函数的描述、参数 schema 等元信息。- 清晰的文档字符串:工具函数的文档字符串至关重要。LLM 会读取它来理解这个工具是做什么的、需要什么输入。描述应准确、简洁。
- 错误处理:工具内部必须做好异常处理。不要让未捕获的异常直接抛给 Agent,这会导致整个运行中断。应该返回一个描述性的错误信息,让 LLM 能理解并可能采取其他行动(如重试或向用户报告)。
- 结果格式化:工具返回的字符串是 LLM 的“观察”。格式应清晰、易于 LLM 理解。这里我们添加了前缀“根据网络搜索...”,帮助 LLM 区分不同来源的信息。
3.2 创建 Agent:组装“大脑”与“工具箱”
有了工具,我们需要创建 LLM 实例和 Agent 执行器。在 LangChain 的最新版本中,推荐使用create_react_agent等高级函数来快速构建基于 ReAct 模式的 Agent。
# research_agent.py (续) from langchain_openai import ChatOpenAI from langchain.agents import create_react_agent, AgentExecutor from langchain.prompts import PromptTemplate # 1. 初始化 LLM (大脑) # 使用 gpt-3.5-turbo 以控制成本,生产环境可根据需要升级到 gpt-4 llm = ChatOpenAI(model=“gpt-3.5-turbo”, temperature=0, openai_api_key=os.getenv(“OPENAI_API_KEY”)) # 2. 定义 ReAct 提示词模板 # 这个模板指导 LLM 按照 “Thought/Action/Action Input/Observation” 的格式进行推理 react_prompt_template = “”“ 你是一个专业的研究助手。你的任务是利用所有可用的工具,全面、准确地回答用户的问题。 请严格按照以下格式响应: Thought: 你需要思考当前情况。分析用户的问题,回顾之前的对话和观察结果,然后决定下一步该做什么。 Action: 你需要调用的工具名称,必须是以下工具之一:[{tool_names}] Action Input: 调用该工具所需的输入,必须是一个字符串。 Observation: 工具返回的结果 ... (这个 Thought/Action/Action Input/Observation 循环可以重复多次) 当你有足够的信息来最终回答用户的问题时,你必须使用以下格式: Thought: 我现在掌握了所有必要信息,可以回答用户的问题了。 Final Answer: 你的最终答案,应清晰、完整地回应用户的原始问题。 开始! 之前的对话历史: {history} 用户输入:{input} {agent_scratchpad}”“” prompt = PromptTemplate.from_template(react_prompt_template) # 3. 创建 ReAct Agent agent = create_react_agent(llm=llm, tools=tools, prompt=prompt) # 4. 创建 Agent 执行器,并设置安全限制 agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, # 设置为 True 可以在控制台看到详细的思考过程,调试必备 handle_parsing_errors=True, # 处理 LLM 输出格式解析错误 max_iterations=5, # 最大循环次数,防止无限循环 early_stopping_method=“generate”, # 当 LLM 生成 “Final Answer:” 时停止 )关键点解释:
ChatOpenAI:这是 LangChain 对 OpenAI Chat 模型的封装。temperature=0使输出更确定,适合任务执行。create_react_agent:这个函数是 LangChain 提供的一个快捷方式,它根据你提供的 LLM、工具和提示词模板,构建出一个遵循 ReAct 推理格式的 Agent 逻辑。- 提示词模板:这是驱动 Agent 行为的“程序”。它定义了 LLM 在每一步应该如何思考、如何格式化输出。
{tool_names}、{history}、{input}、{agent_scratchpad}是 LangChain 会自动填充的变量。 AgentExecutor:这是实际运行 Agent 循环的组件。它负责:- 调用 Agent 获取下一步决策(Action)。
- 调用对应的工具。
- 将工具结果(Observation)拼接到上下文中。
- 再次调用 Agent,进入下一轮循环。
- 强制执行
max_iterations等约束。
verbose=True:这是开发阶段最重要的参数。将其设为True,你将在控制台看到 Agent 完整的思考过程(Thought)、行动(Action)和观察(Observation),这对于理解和调试 Agent 行为不可或缺。
3.3 运行与验证:观察 Agent 如何工作
现在,让我们运行这个 Agent,并向它提出一个问题。
# research_agent.py (续) if __name__ == “__main__”: # 用户提问 question = “LangChain 1.0 版本主要引入了哪些新特性和改进?请基于网络信息回答。” print(f“用户提问:{question}”) print(“-” * 50) try: # 调用 Agent 执行器 result = agent_executor.invoke({“input”: question, “history”: “”}) print(“-” * 50) print(“最终答案:”) print(result[“output”]) except Exception as e: print(f“Agent 执行过程中出现错误:{e}”)在终端运行这个脚本:
python research_agent.py如果一切配置正确,你将看到类似以下的输出(verbose=True时的日志):
用户提问:LangChain 1.0 版本主要引入了哪些新特性和改进?请基于网络信息回答。 -------------------------------------------------- > Entering new AgentExecutor chain... Thought: 用户想了解 LangChain 1.0 的新特性和改进,我需要最新的网络信息。我应该使用 web_search 工具。 Action: web_search Action Input: LangChain 1.0 new features and improvements Observation: 根据网络搜索,关于 ‘LangChain 1.0 new features and improvements’ 的信息如下: LangChain 1.0 标志着该框架从快速迭代的早期阶段进入了一个更稳定、更注重生产就绪性的阶段。主要改进包括:1. **更简洁一致的 API**:大量废弃了旧版中混乱的 API,引入了新的、更统一的接口,如 `create_react_agent`。2. **LangGraph 深度集成**:将 LangGraph 作为核心的编排和状态管理运行时,支持构建复杂、有状态的 Agent 工作流。3. **增强的可观测性**:与 LangSmith 的集成更加紧密,提供了更强大的跟踪、评估和监控能力。4. **改进的流式支持**:对链和 Agent 的流式输出提供了更好的原生支持。5. **模块化与性能**:代码库进行了重构,提高了模块化程度和运行性能。 Thought: 搜索到的信息已经比较全面,涵盖了 API、LangGraph 集成、可观测性、流式支持和性能等方面。我可以基于这些信息组织最终答案了。 Final Answer: LangChain 1.0 是一个重要的里程碑版本,其主要新特性和改进包括:1. **API 简化与统一**:重构了 API 设计,使其更加简洁和一致,例如引入了 `create_react_agent` 等高级构造函数,减少了样板代码。2. **LangGraph 作为核心运行时**:深度集成了 LangGraph,为构建复杂、多步骤、有状态的 Agent 工作流提供了强大的底层支持。3. **生产就绪的可观测性**:强化了与 LangSmith 的集成,使开发者能够更有效地跟踪、评估、调试和监控 Agent 的执行过程。4. **增强的流式处理**:优化了对链和 Agent 流式输出的支持,提升了实时交互体验。5. **架构优化与性能提升**:对内部架构进行了模块化重构,提高了框架的整体性能和可维护性。总之,1.0 版本的重点是从“能用”转向“好用”和“稳定”,更适合用于生产环境部署。 > Finished chain. -------------------------------------------------- 最终答案: LangChain 1.0 是一个重要的里程碑版本,其主要新特性和改进包括:1. **API 简化与统一**:重构了 API 设计,使其更加简洁和一致,例如引入了 `create_react_agent` 等高级构造函数,减少了样板代码。2. **LangGraph 作为核心运行时**:深度集成了 LangGraph,为构建复杂、多步骤、有状态的 Agent 工作流提供了强大的底层支持。3. **生产就绪的可观测性**:强化了与 LangSmith 的集成,使开发者能够更有效地跟踪、评估、调试和监控 Agent 的执行过程。4. **增强的流式处理**:优化了对链和 Agent 流式输出的支持,提升了实时交互体验。5. **架构优化与性能提升**:对内部架构进行了模块化重构,提高了框架的整体性能和可维护性。总之,1.0 版本的重点是从“能用”转向“好用”和“稳定”,更适合用于生产环境部署。过程分析:
- Thought 1:Agent(LLM)分析问题,认为需要网络信息,决定调用
web_search工具。 - Action & Action Input:它正确选择了工具,并生成了搜索关键词 “LangChain 1.0 new features and improvements”。
- Observation:工具执行,返回了搜索结果摘要。
- Thought 2:Agent 分析搜索到的信息,认为足够回答,决定结束循环。
- Final Answer:Agent 综合信息,生成结构化的最终答案。
至此,你已经成功创建并运行了第一个具备自主行动能力的 AI Agent。
4. Agent 工程化:超越 Demo 的关键考量
让一个 Agent 在控制台跑起来只是第一步。要让它在真实项目中可靠运行,必须考虑以下工程化问题。
4.1 控制循环与防止失控
Agent 在循环中自主决策,最大的风险之一是陷入无限循环或执行不必要的步骤。
解决方案与配置:
agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, handle_parsing_errors=True, # 关键安全配置 max_iterations=10, # 硬性限制,防止死循环 max_execution_time=30, # 最大执行时间(秒) early_stopping_method=“generate”, # 依赖 LLM 自己生成停止信号 # 可选:自定义停止条件 # return_intermediate_steps=True, # 返回中间步骤,用于更复杂的控制逻辑 )最佳实践:
- 设置保守的
max_iterations:根据任务复杂度设定,简单任务 3-5 轮,复杂任务 10-20 轮。始终设置一个上限。 - 监控 Token 消耗:每次循环都会累积上下文。对于长任务,要监控上下文长度,避免超出模型限制导致失败或成本激增。可以考虑使用
ConversationSummaryBufferMemory等记忆组件来压缩历史。 - 实现超时控制:
max_execution_time可以防止因单个工具调用卡住或网络问题导致整个 Agent 挂起。
4.2 工具设计的鲁棒性
工具是 Agent 与不稳定外部世界交互的接口,必须足够健壮。
常见问题与处理:
| 问题 | 原因 | 处理策略 | 代码示例(在工具函数内) |
|---|---|---|---|
| 网络超时/错误 | API 服务不可用、网络波动。 | 实现重试机制,并返回可理解的错误信息。 | 使用tenacity库进行装饰重试,或在try…except中捕获requests.exceptions相关异常。 |
| 输入格式错误 | Agent 生成的参数不符合工具要求。 | 在工具函数开头进行参数验证和类型转换。 | 使用pydantic进行数据验证,或手动检查query是否为非空字符串。 |
| API 速率限制 | 调用过于频繁。 | 在工具层或调用侧实现限流和退避。 | 使用缓存(如functools.lru_cache)减少重复调用,或使用backoff库实现指数退避。 |
| 结果解析失败 | API 返回了非预期格式的 JSON/XML。 | 解析时使用更宽松的模式(如json.loads(…, strict=False)),并提供默认值。 | 使用try…except json.JSONDecodeError,并记录警告。 |
改进后的工具示例:
import tenacity from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)) @tool def robust_web_search(query: str) -> str: “”“一个更健壮的搜索工具,包含重试和基础验证。”“” if not query or not isinstance(query, str): return “错误:搜索查询必须是一个非空字符串。” if len(query) > 200: return “错误:搜索查询过长,请精简至200字符以内。” try: search_result = tavily_client.search(query=query, max_results=3, search_depth=“basic”) # … 解析结果 return formatted_result except Exception as e: # 记录日志到文件或监控系统 # logger.error(f“Tavily search failed for query ‘{query}’: {e}”) return f“无法完成搜索。原因:{str(e)}。请检查网络或稍后重试。”4.3 记忆管理:让 Agent 拥有“记忆”
我们的第一个 Agent 是“无状态”的,它不记得之前的对话。要构建多轮对话 Agent,需要引入记忆(Memory)。
from langchain.memory import ConversationBufferMemory # 创建记忆组件 memory = ConversationBufferMemory(memory_key=“history”, return_messages=True) # 在创建 AgentExecutor 时传入 memory agent_executor = AgentExecutor( agent=agent, tools=tools, memory=memory, # 关键:注入记忆 verbose=True, max_iterations=5, ) # 调用时,memory 会自动管理历史对话的存储和加载 result = agent_executor.invoke({“input”: “LangChain 是什么?”}) # 后续调用,Agent 会记得之前的对话 result2 = agent_executor.invoke({“input”: “它和 LangGraph 有什么区别?”})记忆类型选择:
ConversationBufferMemory:简单存储所有对话历史。适用于短对话,但长对话会导致上下文爆炸。ConversationSummaryMemory:让 LLM 定期总结对话历史,只保留摘要。节省 Token,但可能丢失细节。ConversationBufferWindowMemory:只保留最近 K 轮对话。是平衡细节和长度的折中方案。VectorStoreRetrieverMemory:将历史对话存入向量数据库,按相关性检索。适合超长对话和知识密集型应用。
4.4 可观测性与调试:看清 Agent 的“思考过程”
在生产环境中,verbose=True打印日志是不够的。你需要结构化的追踪(Tracing)和评估(Evaluation)。
LangSmith 集成: LangSmith 是 LangChain 官方的可观测性平台。它记录了每次 Agent 运行的完整轨迹(Trace),包括每一步的输入、输出、工具调用、耗时和 Token 使用情况。
import os os.environ[“LANGCHAIN_TRACING_V2”] = “true” os.environ[“LANGCHAIN_ENDPOINT”] = “https://api.smith.langchain.com” os.environ[“LANGCHAIN_API_KEY”] = “你的-langsmith-api-key” # 从 LangSmith 设置中获取 os.environ[“LANGCHAIN_PROJECT”] = “My-Agent-Project” # 你的项目名 # 现在,所有通过 LangChain 执行的调用都会被自动记录到 LangSmith result = agent_executor.invoke({“input”: “…”})在 LangSmith 界面上,你可以:
- 可视化整个 ReAct 循环:查看每一步的 Thought, Action, Observation。
- 比较不同运行:调整提示词或模型后,对比效果。
- 评估运行质量:通过人工打分或自动规则评估 Agent 输出的准确性、相关性。
- 监控成本和延迟:及时发现异常。
本地日志: 对于无法使用云服务的情况,可以自定义回调函数来记录关键信息。
from langchain.callbacks.base import BaseCallbackHandler class CustomLoggingHandler(BaseCallbackHandler): def on_agent_action(self, action, **kwargs): print(f“Agent 选择工具: {action.tool}”) print(f“工具输入: {action.tool_input}”) def on_tool_end(self, output, **kwargs): print(f“工具返回: {output}”) agent_executor = AgentExecutor( agent=agent, tools=tools, callbacks=[CustomLoggingHandler()], # 注入回调 # … 其他配置 )5. 进阶方向与选型建议
5.1 LangChain 与 LangGraph:如何选择?
这是初学者常见的困惑。简单来说:
- LangChain:提供高层、开箱即用的构建块(如
create_react_agent,AgentExecutor)。它抽象了常见的模式,让你能快速搭建标准化的 Agent。适合大多数快速原型和标准任务。 - LangGraph:提供底层的、基于图(Graph)的编排运行时。它让你能以节点和边的形式,精细地控制复杂的工作流、状态流转和循环逻辑。适合需要自定义控制流、复杂状态管理、长时运行或多人协作的 Agent 系统。
实际上,LangChain 的高层 Agent 通常运行在 LangGraph 的运行时之上。对于入门和大多数应用,从 LangChain 开始完全足够。当你的工作流变得非常复杂,需要 if-else 分支、并行执行、人工审批节点等特性时,再深入 LangGraph。
5.2 扩展你的 Agent 工具箱
一个强大的 Agent 取决于其工具箱。除了网络搜索,你可以集成无数工具:
- 代码执行:使用
PythonREPLTool让 Agent 运行 Python 代码。 - 文件操作:读写本地文件、解析 PDF/Word。
- 数据库查询:连接 SQL 或向量数据库进行信息检索。
- API 调用:封装企业内部或第三方 RESTful API。
- 其他模型:调用图像生成、语音合成等专用模型。
集成示例:计算器工具
@tool def calculator(expression: str) -> str: “”“计算一个数学表达式。例如:’(3 + 5) * 2‘。”“” try: # 警告:使用 eval 有安全风险,仅作示例。生产环境应使用 ast.literal_eval 或专用库。 result = eval(expression, {“__builtins__”: {}}, {}) return f“表达式 ‘{expression}’ 的计算结果是:{result}” except Exception as e: return f“计算失败:{str(e)}。请检查表达式格式。”5.3 从单 Agent 到多 Agent 系统
对于极其复杂的任务,可以考虑设计多 Agent 系统,每个 Agent 专精于一个子领域,并通过一个“主管 Agent”或“编排器”进行协调。这属于更高级的架构模式,LangGraph 在此场景下能发挥巨大作用。
构建一个能自主行动的 AI Agent 不再是遥不可及的概念。通过 LangChain,我们能够将强大的 LLM、灵活的工具和可控的执行循环组合起来。核心在于理解 ReAct 模式,并扎实地处理好工具鲁棒性、循环控制、记忆管理和可观测性这些工程细节。不要停留在调用单个 API 的层面,尝试为你手头的任务设计一个工具,然后让 Agent 去调度它。从简单的自动摘要、数据查询开始,逐步构建更复杂的自动化工作流。真正的挑战不在于写出第一行代码,而在于如何让这个拥有一定自主权的系统,在你的业务场景中可靠、可控、有价值地运行起来。
