news 2026/10/12 16:24:44

Agent 开发实战:从上下文窗口、Function Calling 到 MCP 记忆与工具管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent 开发实战:从上下文窗口、Function Calling 到 MCP 记忆与工具管理

做 Agent 开发这一年多,我最大的感受是:模型的能力早就不是瓶颈,真正卡住你的永远是两个词——记忆和工具。一个 Agent 假如没有记忆,上一轮聊完这一轮就翻脸不认人;假如没有工具,它就只能输出文字,查不了天气、读写不了文件、调不了接口。这两件事,本质上都指向同一个问题:模型在一个有限的上下文窗口里,怎么拿到它需要的信息,怎么对外部世界产生真实影响。

从早年硬编码拼接 Prompt,到后来 Function Calling 成为标配,再到今天各厂商都在押注的 MCP(Model Context Protocol),这条演进路线很清晰地说明了一件事:记忆和工具正在从"每个项目手写一遍"变成"标准化基础设施"。这篇文章不聊概念,就聊我实际摸过、踩过、重构过的那些方案。适合正在做 Agent 应用、被上下文越搞越头疼、或者准备接 MCP 但不知道从哪下手的开发者。

1. 从上下文窗口说起:Agent 的"记性"到底差在哪

1.1 Token 不是无限的:窗口的现实约束

很多人对"上下文窗口"的理解是错的。以为窗口大,就等于记忆好。这是一个非常昂贵的误解。

上下文窗口的本质是模型在生成下一个 Token 时能"看到"的最大输入范围。它由两部分组成:系统提示词、历史对话、工具返回结果、外部检索到的资料,以及你当前要模型处理的问题。所有这些加在一起,才叫"占用窗口"。窗口不是只给对话留的,任何一个环节撑爆,整个请求就会报错,或者被静默截断。

我实测过一些主流模型的窗口参数。名义上给你 200K Token 的窗口,真正稳定可用的长度通常只有一半左右。因为输入越长,模型的注意力会越分散,中间部分的内容经常被"忽略"。这个现象在业界叫 lost in the middle。你塞了 50 份文档进去,模型真正记住的往往只有开头和结尾,中间那 40 份等于白给。

所以做 Agent 的第一课就是:不要迷信大窗口,要假设窗口永远不够用。把窗口当成一块需要精细规划的地皮,而不是一个无限大的仓库。

1.2 短期记忆、工作记忆与长期记忆的划分

我习惯把 Agent 的记忆分成三层,对应不同的存储和读取方式。

短期记忆就是最近几轮对话的原始内容。它最真实、最完整,但也最占空间。如果每轮对话都原样塞进上下文,三五十轮之后窗口就满了。

工作记忆是当前任务执行过程中的临时状态,比如"正在写第 3 章第 2 节""已经调用了 2 次搜索工具""用户偏好的语气是简洁"这类信息。它不需要永久保存,但必须在任务持续期间随时可取。很多 Agent 框架里把它放在一个独立的 state 对象里,而不是塞进对话历史。

长期记忆是跨会话持久化的信息,比如用户的偏好、历史项目的结论、领域知识库。它的特点是量大、复用频率高,但不需要每次全量加载。正确的做法是做成外部存储,按需检索。

这三层记忆对应三种完全不同的技术方案。短期记忆靠上下文规划,工作记忆靠结构化状态管理,长期记忆靠向量检索或数据库。把它们混为一谈,是 Agent 记忆方案设计失败的最常见原因。

2. 记忆怎么存:三种主流方案与取舍

2.1 原样对话历史:简单但不耐用

最原始的做法,就是把所有对话历史原样拼进 Prompt。代码大概长这样:

messages = [] # 每轮对话都追加 messages.append({"role": "user", "content": user_input}) messages.append({"role": "assistant", "content": assistant_output}) # 请求时全量发送 response = llm.chat(messages)

这个方案的优势是零成本、零丢失,模型的每一次输出都有完整的前因后果。但它有一个致命问题:窗口被历史吃光之后,新输入进不来。

我见过很多团队的第一版 Agent 都是这么写的,跑 Demo 没问题,一旦真人对聊超过 20 轮就开始报错。后来大家想了个补丁——只保留最近 N 轮。这个 N 到底设多少,就成了一个每次都要调的心病。

设太小,模型忘了前面的关键信息,回答问题质量断崖式下跌。设太大,窗口又不够用。更麻烦的是,这种方式只做"截断",没有做"压缩",被丢掉的早期信息里如果有用户的偏好或关键约束,模型就再也想不起来了。

2.2 摘要记忆与滚动压缩

解决"历史太长"的主流方案是摘要压缩。核心思路是:把早期的对话交给模型进行总结,提炼出关键事实和结论,然后只把摘要留在上下文里,原始对话归档到外部存储。

def compress_history(messages, max_rounds=10): if len(messages) <= max_rounds: return messages # 取最早的部分进行摘要 older = messages[:-max_rounds] recent = messages[-max_rounds:] summary = llm.summarize(older) # "用户偏好简洁回答;已确认采用Python技术栈;..." return [{"role": "system", "content": f"对话摘要:{summary}"}] + recent

注意这个摘要不是随便写写,要设计摘要的结构。我踩过坑之后总结出一套比较稳的摘要格式:关键决策、用户偏好、未完成事项、已确认的事实,四类信息分开展示。这样模型在需要时能快速定位,而不是在一段散文里翻找。

摘要记忆的代价是信息损失。模型总结得再好,也不可能把一段对话的所有细节都装进几句话里。所以它适合配合长期记忆一起用:摘要进上下文,完整的原文向量化后存进知识库,等需要细节时再检索出来。这一套组合拳打下来,Agent 的"记忆力"才算勉强够用。

2.3 向量化长期记忆与外部存储

第三层是长期记忆,也是 Agent 能不能跨会话持续进化的关键。我的做法是给每个用户或项目建一个独立的记忆空间,用向量数据库存储。

流程分三步:

  1. 写入阶段:把对话中值得记住的信息(用户偏好、项目结论、重要事实)抽取出来,转成向量存入数据库。
  2. 检索阶段:每次用户发起新对话前,把当前问题转成向量,去库里做相似度检索,取 Top-K 条相关记忆,拼进上下文。
  3. 更新阶段:定期把旧的、被新信息覆盖的记忆标记为过期,防止记忆库越积越乱。
# 写入记忆 memory_text = "用户偏好使用简洁的中文回答,不喜欢术语" vector = embed(memory_text) vector_db.insert(project_id=user_id, vector=vector, text=memory_text) # 检索记忆 query_vector = embed(user_input) hits = vector_db.search(query_vector, top_k=5) for hit in hits: context_parts.append(f"[记忆] {hit.text}")

这里有一个很容易忽略的细节:不是所有对话都值得存入长期记忆。我见过最典型的错误是把每一轮对话都做向量化入库,结果库越来越大,检索出来的东西越来越杂,反而污染了上下文。正确做法是让模型先判断"这句话值不值得记住",或者用规则过滤掉寒暄、临时状态类的内容,只保留有长期价值的信息。

记忆方案没有银弹。我现在的标准架构是三段式:短期记忆用滚动窗口,中期用摘要压缩,长期靠向量检索。三者的比例不是固定的,要看业务场景。客服类场景摘要可以激进一些,因为旧对话的价值低;复杂任务类场景摘要要保守,因为细节可能就是关键。

3. 工具:从硬编码到 Function Calling

3.1 没有工具的模型只是"嘴强王者"

给模型接工具这件事,本质上是在解决一个"知行合一"的问题:模型能理解语言,但它无法直接执行操作。你问它"现在几点了",它能编一个时间出来,但永远不知道真实时间;你让它"帮我查一下这个订单的物流",它只能说抱歉。

要让模型拥有真实能力,早期方案是硬编码。写一堆 if-else 去匹配用户意图:

if "天气" in user_input: result = weather_api(city) elif "股票" in user_input: result = stock_api(code)

这套方案在一些限定场景下能用,但遇到复杂表达就崩,比如"帮我看看明天出门要不要带伞",它匹配不到"天气"关键词,直接哑火。而且每加一个新功能就要改代码,维护成本极高。

后来主流方案变成了 Function Calling。它的核心思路是:把"模型负责理解意图"和"程序负责执行函数"彻底拆开。模型不直接调函数,它只输出一段结构化的 JSON,告诉你它想调用哪个函数、参数填什么。

3.2 Function Calling 的调用闭环

一个标准的 Function Calling 流程是这样的:

首先,你把工具的描述以 JSON Schema 的形式告诉模型:

[ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的实时天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"}, "date": {"type": "string", "description": "日期,格式YYYY-MM-DD"} }, "required": ["city"] } } } ]

然后模型在生成回答时,如果发现需要查天气,它不会直接说"我帮你查一下",而是输出类似这样的一段内容:

{ "name": "get_weather", "arguments": "{\"city\": \"上海\", \"date\": \"2025-01-15\"}" }

你的代码捕获到这个输出,执行真实的天气接口调用,把结果拼成一条新消息,再送回给模型。模型看到结果后,才能最终生成面向用户的回答。

这一步拼接非常关键。我最开始做的时候漏掉了"把工具结果回传给模型"这个环节,导致模型每次只输出一个 JSON 就停了,根本不会继续生成用户要的答案。记住这个闭环:模型发起调用 -> 程序执行函数 -> 结果回传 -> 模型生成最终回答。

3.3 工具调用的常见翻车现场

Function Calling 成熟了很多,但用起来依然有不少坑。

第一个坑是工具描述质量差。模型的 Function Calling 效果高度依赖你写的 description 是否清晰。你写"查询天气",和写"查询指定城市在指定日期的天气情况,包含温度、湿度、风力等级,用于回答用户关于出行是否需要加衣的问题",后者的触发准确率要高出一大截。原因是模型靠描述来判断"什么时候该用这个工具",描述里的细节就是它的决策依据。

第二个坑是工具返回结果太长。天气接口的原始返回可能是一大段 JSON,里面 90% 的字段模型根本用不上。你直接把它塞回上下文,既占窗口又容易让模型被无关字段干扰。正确做法是写一层工具后处理逻辑,把返回精简成几句话。比如"上海,1月15日,多云,5~10℃,东风3级,体感偏冷"。这个精简过程我是在一次线上事故之后才领悟的——那次工具返回了 8KB 的 JSON,直接把上下文撑爆了,后续几轮对话全部异常。

第三个坑是工具选择错误。模型有概率在可用工具中选错。比如你同时给了"查询订单状态"和"申请退款"两个工具,模型可能在用户只问"什么时候发货"时,错误调用了"申请退款"。缓解办法是在工具描述里加上触发条件和禁止条件,比如"仅在用户明确表示要退款时才使用本工具,询问进度时使用查询订单工具"。

4. MCP 协议:把记忆与工具做成了标准化接口

4.1 MCP 是什么:服务器-客户端架构

Function Calling 解决了"模型怎么调工具"的问题,但它没解决"工具怎么接入"的问题。每接一个数据源,你都要写胶水代码,把对方的 API 包装成 schema 再注册进 Agent。数据源一多,胶水代码的量就失控了。

MCP(Model Context Protocol,模型上下文协议)就是为了解决这个问题而生的。它的思路非常像 USB 接口:每个外设不需要为每台电脑定制接口,只要它符合 USB 标准,插上就能用。MCP 把 Agent 的工具、数据资源和 Prompt 模板统一成一套标准协议,只要数据源实现一个 MCP Server,任何支持 MCP 的 Agent 客户端都可以直接接入。

MCP 的架构分两层:

  • MCP Server:负责把外部能力(数据库、文件系统、第三方 API、知识库)暴露成标准接口。一个 Server 可以提供多个工具,也可以提供可被检索的资源。
  • MCP Client:运行在 Agent 应用内部,负责与 Server 建立连接,发现 Server 上的工具列表,并把调用请求转发给 Server。

我最初接触 MCP 的时候觉得它的架构有点重:一次工具调用,要经过 Agent 应用 -> MCP Client -> MCP Server -> 真实业务接口,多了一层跳板。但实际用下来发现这层跳板非常值,因为它把"接入"这件事从开发问题变成了配置问题。

4.2 Resource、Prompt、Tool 三类原语

MCP 协议定义了三类核心原语,分别对应 Agent 需要的三种能力。

Tool 是主动执行的操作,对应 Function Calling 里的 function。Agent 判断需要执行动作时调用它。典型场景:发送邮件、创建工单、执行 SQL 查询。Tool 在执行后有一个确定的结果返回。

Resource 是被动读取的数据,对应的是"知识"而不是"动作"。Agent 不会主动调用 Resource,而是由 Agent 应用在需要时去读取。比如一份产品文档、一张用户画像表、一个数据库视图。Resource 可以支持 URI 寻址,被设计成类似文件系统的形式,方便检索和定位。

Prompt 是预定义的模板,用来规范 Agent 的特定行为模式。比如一个"做代码审查"的 Prompt 模板,可以预设好审查角度、输出格式、注意事项。Agent 应用可以直接加载这些模板,快速切换不同的工作模式。

{ "tools": [ { "name": "write_note", "description": "写入一条新的持久化笔记", "inputSchema": { "type": "object", "properties": { "title": {"type": "string"}, "content": {"type": "string"} } } } ], "resources": [ { "uri": "memory://user/preferences", "name": "用户偏好", "description": "用户的行文风格与表达偏好" } ] }

这三类原语覆盖了 Agent 的两个核心需求:动手做事靠 Tool,动脑思考靠 Resource 和 Prompt。

4.3 传输方式:stdio 与 SSE

MCP 定义了几种传输方式,最常用的是 stdio 和 SSE。

stdio 模式下,MCP Server 以子进程方式运行,Agent 应用和它之间通过标准输入输出流通信。这种方式适合本地工具,比如文件操作、本地数据库、命令行工具。优点是轻量、安全,不需要开端口,服务器进程跟着 Agent 应用的生命周期走。

SSE 模式则通过 HTTP 进行通信,Server 作为一个独立的网络服务运行。这种方式适合远程工具,比如部署在一台服务器上的知识库服务、第三方 SaaS 的集成网关。多个 Agent 实例可以共享同一个 Server。

有一个我一开始没想明白的点:这两种方式对 Agent 应用来说是透明的。同一个 MCP Client,既可以通过 stdio 连接本地进程,也可以通过 SSE 连接远程服务,代码层面几乎不用改,只是配置文件里的连接方式不同。这也正是 MCP 的抽象价值所在。

5. 实操:用 MCP 给 Agent 接上"可插拔记忆与工具"

5.1 环境准备与项目结构

理论讲再多,不如上手跑一遍。我这边用 Python 搭建了一个最小可用的 MCP 实践项目,整体思路是:用 MCP Server 封装两类能力——一个带持久化存储的笔记工具(Tool),一个返回用户偏好的资源(Resource);再用一个支持 MCP 的客户端框架把它们接入 Agent 的对话流程。

项目结构如下:

agent-demo/ ├── server/ │ ├── note_server.py # MCP Server 实现 │ └── requirements.txt └── client/ └── agent.py # MCP Client + Agent 逻辑

依赖只需要两个:MCP 的官方 SDK,以及任意一个大模型 API 的 SDK。注意 MCP SDK 的版本迭代比较快,建议锁定你安装时的版本,不要直接拉最新版,否则 API 可能对不上。

5.2 编写一个带存储能力的 MCP Server

先写最核心的笔记工具。这个工具要做的事情很简单:写入一条笔记,并把它追加到本地 JSON 文件里。用 JSON 文件做存储是刻意为之——为了展示 MCP Server 可以封装任意存储后端,你想换成 SQLite、对象存储都没问题。

# server/note_server.py import json from pathlib import Path from mcp.server.fastmcp import FastMCP mcp = FastMCP("note-server") NOTES_FILE = Path("./notes.json") def _load_notes(): if not NOTES_FILE.exists(): return [] return json.loads(NOTES_FILE.read_text(encoding="utf-8")) def _save_notes(notes): NOTES_FILE.write_text( json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8" ) @mcp.tool() def write_note(title: str, content: str) -> str: """写入一条笔记,返回写入结果。title 是笔记标题,content 是正文。""" notes = _load_notes() notes.append({"title": title, "content": content}) _save_notes(notes) return f"笔记《{title}》已保存,当前共 {len(notes)} 条" @mcp.tool() def list_notes() -> str: """列出所有笔记的标题和摘要,用于回顾历史记录。""" notes = _load_notes() if not notes: return "暂无笔记" return "\n".join( f"- {n['title']}: {n['content'][:50]}" for n in notes )

注意到@mcp.tool()装饰器标明了这是一个工具,函数名、参数名和 docstring 会被自动转成工具 schema。这比手写 JSON Schema 爽得多,工具接入成本降了一个数量级。

再添加一个 Resource,用来暴露用户偏好。这里模拟从配置文件读取:

@mcp.resource("memory://user/preferences") def get_preferences() -> str: """返回用户的撰写偏好,供 Agent 在生成内容时参考。""" return "偏好简洁、务实的表达风格;技术术语首次出现时给出解释;避免空话套话。"

启动 Server 的代码很简单:

if __name__ == "__main__": mcp.run(transport="stdio")

5.3 在 Agent 侧配置与调用

Server 写好后,在 Agent 客户端里接入。我用的是 MCP Client,连接方式是 stdio,也就是直接拉起刚才那个 Python 进程。

# client/agent.py import asyncio from mcp.client.stdio import stdio_client, StdioServerParameters async def main(): server_params = StdioServerParameters( command="python", args=["server/note_server.py"] ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # 初始化连接,获取工具列表 await session.initialize() tools = await session.list_tools() for tool in tools.tools: print(f"可用工具: {tool.name} —— {tool.description}")

这里的关键环节是初始化握手。MCP Client 启动后会向 Server 发送 initialize 请求,Server 返回自己的能力列表,Client 才能知道有哪些工具可用。握手成功后,把工具列表注入到模型请求里,后面的调用闭环跟 Function Calling 一样。模型输出工具调用意图 -> Client 转发给 MCP Server -> Server 执行真实操作 -> 结果回传给模型。

整个过程跑下来,最直观的感受是:新接一个数据源,不再需要改 Agent 主代码了。你只需要多启动一个 MCP Server 进程,告诉 Client 它的地址,然后把它的工具列表并进整体请求就行。这就是"可插拔"三个字的实际含义。

5.4 实测记录与效果观察

我在本地实测了几轮,记录一下现象。

第一次测试,我对 Agent 说"帮我记一下:下周要完成项目结项报告,重点是性能测试部分"。模型判断需要调用 write_note,参数解析为 title="项目结项报告待办"、content="下周完成,重点是性能测试部分"。Server 正确写入 JSON 文件,模型收到结果后生成确认回复。

第二次测试,我再问"我之前记过关于结项报告的笔记吗"。模型先调用 list_notes,拿到返回的标题列表,然后基于列表信息回答"你有一条笔记,标题是'项目结项报告待办'"。注意中间有一次工具调用,但用户感知不到,模型自然地把工具的中间结果消化掉了。

第三次测试更有意思,我故意问"我想写一篇技术博客,你帮我起个标题"。模型没有调用任何工具,直接生成回答——但在我把memory://user/preferences这个 Resource 也加载进上下文之后,模型生成的标题明显更贴近用户的表达偏好。这验证了一个点:Resource 提供的是"静态知识",它不需要被"调用",它只需要被"看到",就能影响模型的输出风格。

6. 踩坑实录:上下文截断、记忆污染与工具循环

6.1 上下文截断导致"失忆"

我踩得最深的一个坑,是上下文截断策略设计不当。

有一次做客服 Agent,对话超过 30 轮之后,我把最早的对话直接丢弃。结果用户在 15 轮时说过"我是付费用户,工单优先级要高",30 轮后这条信息被挤掉了,Agent 后续把用户当成了普通用户来响应,态度和优先级处理全都错了。用户直接投诉。

这个问题的根源在于"按轮数截断"天然是错误的。截断不应该是轮数驱动的,应该是"信息价值"驱动的。我现在的做法是:在写入上下文之前,先让模型对每条历史信息做一个价值标记——是否包含用户身份信息、是否包含未完成事项、是否包含明确偏好。价值高的内容即使很老也要保留或进入摘要,价值低的寒暄可以立即丢弃。

6.2 工具返回内容太长撑爆窗口

工具调用好做,难的是对工具返回内容做瘦身。前面提到过一次因为返回 8KB JSON 撑爆窗口的事故,后来我养成了一个习惯:每一个工具在返回结果前,都先做一次格式化,只保留用户真正关心的字段,并且用自然语言输出,而不是原始 JSON。

比如一个查询订单状态的工具,原始返回是:

{"order_id": "12345", "status": "SHIPPED", "items": [...], "address": {...}, "logistics": [...], "created_at": "...", "updated_at": "...", "payment": {...}}

我应该把它精简成:"订单 12345 已发货,共 3 件商品,预计本周五送达。"这段精简后的文本,既满足模型回答用户问题的信息需求,又节省了大量 Token,还减少了模型被无关字段误导的概率。

工具瘦身这件事,MCP 本身不管,它是工具开发者的责任。这也是我反复跟团队强调的一点:MCP Server 暴露的接口质量,直接决定上层 Agent 的效果上限。接口做得糙,模型纵有千般能耐也发挥不出来。

6.3 Agent 陷入工具循环

工具循环是另一个让人头大的问题。场景是这样的:Agent 调用了一个查询工具,工具返回"查无数据",Agent 不死心,换个参数又查一次,还是没数据,它再换个查询条件……如此反复四五次,每次都消耗 Token,就是不肯停下来直接告诉用户"查不到"。

我分析下来,原因通常是两个。一个是工具描述里没有说明"空结果的正常含义",另一个是模型在"必须完成任务"的指令下产生了一种执念。缓解办法是在系统提示词里加入兜底指令:"如果连续两次工具调用都返回空结果或相同结果,请停止调用工具,直接告知用户当前无法获取有效数据,并给出建议。不要反复尝试。"

还有一个偏门但有效的方案:在代码层设置工具调用次数上限。我一般设为 3 次,达到上限后强制终止工具循环,把已获得的中间结果交给模型做最终总结。这个限制既是成本控制手段,也是防止 Agent 行为失控的保险丝。

6.4 记忆读写的安全与权限问题

最后聊一个很容易被忽视、但生产环境必须面对的议题:记忆的权限边界。

一个多租户的 Agent 应用里,A 用户的记忆绝对不能混入 B 用户的上下文。这听起来是常识,但我在代码里见到过直接用一个全局向量库存所有用户记忆、检索时不带用户过滤条件的实现——那是妥妥的数据安全事故,哪怕只发生一次,信任就没了。

我现在的规范是三条:第一,每个用户的记忆必须有独立的命名空间或租户 ID,所有读写操作的数据库查询条件里必须强制带上这个 ID;第二,工具调用的权限分层,比如"读取笔记"所有用户都能用,"删除笔记"可能需要额外授权;第三,敏感信息不能明文存入记忆存储,要么不存,要么脱敏,要么加密。模型再聪明,也扛不住你把用户的 API Key 明文写进记忆库这种低级失误。

安全这块没有捷径,只有严格按流程来。凡是涉及用户数据的读写,都要过一遍权限检查,无论调用方是模型还是人。

7. 一点自己的心得

做 Agent 这一年半载,一个很深的体会是:模型的智商是给定的,但 Agent 的实用程度完全取决于你给它配的记忆和工具。上下文窗口再大,也扛不住不做规划的硬塞;工具接口再多,也不如每个接口都被精心设计和瘦身过。MCP 的意义在于,它把这套复杂度从"每个项目内部实现细节"变成了"团队可共享的标准约定"——一个业务系统只要暴露成 MCP Server,任何上层 Agent 都能直接接入,这省掉的是真正重复劳动的苦工。

最后分享一个做 Agent 架构时的小习惯:每次新加一个工具或一类记忆,我都会问自己一个问题——如果三个不同的 Agent 场景都要用它,这个抽象合理吗?如果答案是要改,说明设计还没到位,继续磨。毕竟,Agent 的工程化本质,就是一层一层变量收纳进边界清晰的抽象里,而不是让模型在混乱中靠运气发挥。

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

Spring Boot入门实战:从环境搭建到写出第一个接口

第一次用Spring Boot的时候&#xff0c;我还在用传统SSM写项目&#xff0c;光配置XML就写了半天&#xff0c;数据源、事务、扫描包、视图解析器&#xff0c;随便一处忘了配就是各种奇奇怪怪的报错。后来转Spring Boot&#xff0c;最大的感受就是“原来项目还能这么搭”&#xf…

作者头像 李华
网站建设 2026/10/12 16:21:01

理解 ESTree 中的 defer-import-eval 扩展:模块求值延迟的 AST 表示

文档编程语言 【免费下载链接】estree The ESTree Spec 项目地址&#xff1a; https://gitcode.com/gh_mirrors/es/estree 点击查看 免费下载 导读 本文聚焦 ESTree 规范仓库中 stage3/defer-import-eval.md 所定义的 AST 扩展&#xff0c;讲解 TC39「Deferring Module Evalu…

作者头像 李华
网站建设 2026/10/12 16:20:08

四天工作制深度解析:从试点数据到个人效率落地的完整指南

周五下午三点半&#xff0c;我在某个办公区的走廊里看到这样一幕&#xff1a;一间会议室还在开着周例会&#xff0c;投屏上的幻灯片已经翻到第三十几页&#xff0c;靠窗的同事手机屏幕停在日历页面上&#xff0c;茶水间里有人在排队接咖啡。这个场景几乎每个上班族都见过——周…

作者头像 李华
网站建设 2026/10/12 16:19:12

企业级Agent架构实践:五组件、ReAct循环与私有化部署指南

1. 从单体应用到 Agent 架构&#xff1a;我们在聊的到底是什么企业级应用走到今天&#xff0c;绝大多数业务系统已经从“前端页面 单机服务 数据库”的铁三角&#xff0c;跨进了“多模型协同、多工具编排、多业务线共享”的新阶段。过去一年里我接触了不少做智能客服、知识库…

作者头像 李华
网站建设 2026/10/12 16:17:56

Python文件读写与数据格式化:从测验题到工程实践

1. 从一道测验题看透文件操作的核心逻辑很多人第一次接触 Python 的文件读写&#xff0c;都是在类似“测验7&#xff1a;文件和数据格式化”这样的练习里。题目本身可能只是让你统计一个文本里的词频、把字典写进 CSV、或者把一列数据格式化成对齐的输出。但我带了这么多期学习…

作者头像 李华
网站建设 2026/10/12 16:17:44

编译原理课程设计:从PL/0手术式扩充到语法制导翻译实践

简介&#xff1a;这是一份编译原理课程设计资源&#xff0c;围绕经典教学编译器PL/0进行语法与功能扩充&#xff0c;面向高校编译原理课程的学生、需要完成同类课设的开发者以及想深入理解编译过程的爱好者。资源包含42个文件&#xff0c;覆盖PL/0源程序、COD目标代码、CPP/C工…

作者头像 李华