1. 项目概述:当Gemini 3.1 Pro在代码长对话中“失忆”,我们到底在和什么打交道?
你刚把一段三百行、嵌套了五层回调、变量名全靠拼音缩写撑场面的遗留代码甩给Gemini 3.1 Pro,它三秒内就定位到那个藏在第287行的setTimeout里没被clearTimeout兜底的定时器,并顺手给你补上了防抖逻辑——那一刻,你盯着屏幕,手指悬在键盘上忘了敲回车,心里只有一个念头:这玩意儿真不是偷偷读了我的脑电波?它甚至比我自己更懂我三年前写的那坨东西。
但这种“神迹”是有保质期的。大概到第18轮对话,你让它基于刚才重构好的用户服务模块,再加一个带幂等校验的订单创建接口,它开始把userId字段硬塞进Order实体类里,而你明明在第5轮就强调过“所有ID字段必须是字符串类型,且统一用id命名,不带任何前缀”。到了第27轮,它甚至把第12轮已经成功替换掉的axios请求库又悄悄换回了fetch,还配了一段根本跑不通的AbortController用法。你揉着太阳穴点开报错日志,发现它连最基础的HTTP状态码判断都写反了。这不是水平波动,这是断崖式下跌。开发者社区里管这叫“降智”,但这个词太轻飘了——它不是变笨,是系统性失能;不是算力不足,是认知架构在超负荷运转下暴露了底层缺陷。
这背后没有玄学,只有可拆解、可干预、可优化的技术现实。Gemini 3.1 Pro不是一台会思考的机器,它是一台极其精密的上下文模式匹配引擎。它的“理解”不等于人类的理解,它的“记忆”也不是数据库式的索引存储,而是一种高维向量空间里的概率性共振。当对话轮次拉长,这个共振系统就开始出现信号衰减、噪声淹没、路径偏移。本文要做的,不是抱怨模型不行,而是像一位老练的系统工程师那样,拿着示波器和逻辑分析仪,一层层剥开Transformer的外壳,看清那些在长代码对话中悄然卡死的齿轮:为什么80%的上下文是“脏数据”?为什么百万Token的窗口挡不住“大海捞针”的困境?为什么它记不住自己五分钟前答应过的事?更重要的是,作为每天和它并肩作战的开发者,我们手里的扳手和润滑剂在哪里?这篇文章就是一份实操手册,里面没有空泛理论,只有我在真实项目中踩过坑、验证过、能立刻抄作业的六套组合拳。无论你是刚用Gemini辅助写脚手架的新手,还是正用它重构微服务核心链路的资深架构师,只要你还在和长对话搏斗,这篇内容就值得你花20分钟读完,然后马上改掉你正在用的那个“连续追问30轮”的坏习惯。
2. 核心原理拆解:为什么“长对话必死”是Transformer架构的宿命?
2.1 “滚雪球”式脏数据污染:模型眼中的世界没有“已修复”标签
人类程序员维护代码时,大脑里天然有一套“版本快照”机制。你昨天改掉的那个空指针异常,今天再看这段代码,你的认知里它已经是“已修复”状态。即使你翻看Git历史,你也知道哪些commit是废弃的、哪些是最终的。但LLM没有这个能力。对它而言,整个对话历史就是一个扁平的、无差别的Token序列。第3轮你贴上去的、带着NullPointerException堆栈的错误代码,和第15轮你确认通过的、经过单元测试的最终版代码,在它的注意力权重计算中,初始地位是完全平等的。
我做过一个对照实验:用同一段业务逻辑(一个电商库存扣减服务),分别构建两种Prompt。第一种是“原始长对话流”:包含6轮交互,其中第1、2、4轮都是失败的尝试(比如用了错误的Redis命令、漏了事务回滚),只有第6轮是正确实现。第二种是“精炼黄金Prompt”:只包含第6轮的最终代码、核心约束(如“必须使用Lua脚本保证原子性”、“失败时抛出自定义StockException”)和当前待办任务(“新增超卖预警日志”)。结果非常明确:在生成新功能时,“原始流”版本有63%的概率复现第2轮的错误Redis命令,而“黄金Prompt”版本100%命中正确方案。这不是模型能力问题,是输入信噪比问题。
为什么模型无法自动过滤掉错误信息?因为它的训练目标从来不是“识别对错”,而是“预测下一个最可能的Token”。在大量开源代码语料中,“redisClient.incr(key)”和“redisClient.decr(key)”出现的频率都极高,模型学到的是它们共存的统计规律,而不是一个绝对的“对/错”二元判断。当你在对话中反复展示错误代码,你实际上是在强化那些错误模式在向量空间中的锚点强度。随着轮次增加,这些错误锚点与正确锚点之间的距离在高维空间中被不断压缩,最终导致模型在生成时,随机采样到错误路径的概率显著上升。这就像在一个布满磁铁的桌面上滚动小钢珠——你放下的每一块磁铁(无论是正极还是负极)都会改变钢珠的轨迹,而你放得越多,轨迹就越难预测。
提示:模型没有“撤销”概念。你告诉它“刚才错了”,它不会擦除那段Token,只会在这个Token后面追加一个新的、试图修正的Token。整个历史依然存在,噪音依然在累积。
2.2 注意力机制的物理瓶颈:“大海捞针”不是比喻,是数学现实
Gemini 3.1 Pro宣传的百万级上下文,常被误解为“它能记住一切”。这是一个危险的幻觉。上下文长度是存储容量,而注意力机制是检索能力,二者不可等同。Transformer的Self-Attention计算复杂度是O(n²),其中n是上下文长度。这意味着,当n从8K增长到1M时,计算量并非线性增长,而是暴增约15000倍。工程上不可能让每个Token都与其他所有Token进行全连接计算。因此,所有号称支持超长上下文的模型,都必然采用某种形式的注意力稀疏化或分块策略。
Gemini 3.1 Pro的具体实现细节未公开,但根据其论文和第三方基准测试(如LongBench),它极可能采用了类似“滑动窗口+全局摘要”的混合架构。简单说,模型会把整个长上下文切分成多个块,每个块内部做全连接Attention,同时保留一个全局的、高度压缩的“摘要向量”来捕捉跨块信息。问题就出在这里:那个最关键的“摘要向量”,其信息密度是有限的。它能记住“用户要求用PostgreSQL”,但很难精确编码“users表的id字段是UUID字符串,而orders表的user_id是BIGINT外键”这种强依赖关系。
我在调试一个支付网关集成时亲历了这一点。初始Prompt里明确定义了“所有异步回调URL必须以https://callback.myapp.com/v2/开头,并携带X-Signature头”。这个规则在第2轮。到了第35轮,当我让它生成新的Webhook验证逻辑时,它生成的URL却是http://localhost:3000/callback,签名头也写成了X-Auth。我立刻检查了上下文,发现那个关键的URL前缀规则,确实被淹没在了之前20多轮关于证书格式、重试策略、幂等Key生成的讨论中。模型的“全局摘要”向量,在处理如此密集的、相互冲突的约束时,优先级发生了偏移——它记住了“要验证”,却丢失了“怎么验证”的具体参数。这不是模型偷懒,是它的信息通道带宽被挤占后的必然结果。它不是不想找那根针,是针所在的那片干草堆,实在太大了。
2.3 逻辑一致性坍塌:没有符号表的“状态机”注定会溢出
写代码和写文章最大的区别在于:文字可以模糊,逻辑必须精确。一个句子前后风格不一致,读者可能一笑而过;但一个函数声明返回Promise<string>,而实现里却return null,编译器会立刻报错。编程是零容错的领域,而LLM的推理过程,本质上是一个单向的、无状态的Token生成流水线。
想象一下,你让模型维护一个隐式的“开发状态机”。这个状态机需要实时追踪:
- 数据库类型(MySQL/PostgreSQL/MongoDB)
- ORM框架及版本(Prisma 5.x / TypeORM 0.3.x)
- 核心实体的字段定义与类型约束
- 业务规则(如“所有金额字段单位为分,整型存储”)
- 安全要求(如“密码必须BCrypt哈希,盐值长度12”)
- 部署环境(Docker/K8s,Node.js版本)
这些状态,没有一个被显式地注册进模型的“内存”。它们全部散落在几十轮对话的文本碎片里。模型在生成每一个新Token时,都要从整个上下文中“检索”所有相关约束,然后在概率分布上进行加权。这个过程没有任何缓存,每一次都是重新计算。当状态数量超过某个阈值(我的经验是,当核心约束超过7条且分散在不同轮次时),模型的“状态维护”就会进入一种混沌状态。它开始出现“选择性失忆”:记得数据库是PostgreSQL,却忘了jsonb字段不能直接用==比较;记得ID是字符串,却在生成SQL时写了WHERE id = 123。
这背后是Transformer架构的根本局限:它没有真正的“工作记忆”(Working Memory)模块,也没有像IDE那样的AST(抽象语法树)解析器来构建和维护一个结构化的代码知识图谱。它所有的“理解”,都发生在Token级别的向量运算中。当逻辑链条变长、依赖变深,这种基于局部模式匹配的推理方式,其误差率就会指数级上升。这不是Gemini 3.1 Pro独有的问题,而是所有纯文本驱动的LLM在处理高密度逻辑任务时的共同天花板。
2.4 用户诱导的恶性反馈循环:我们亲手喂养了“降智”怪兽
我们必须坦诚:有时候,那个把Gemini推向崩溃边缘的,正是我们自己。在Debug的胶着时刻,人的情绪会接管理性。当模型第N次给出一个明显离谱的方案时,我们的Prompt往往会从冷静的技术描述,退化成情绪化的指令:“不对!!!”、“你根本没看报错信息!”、“再这样我就换Claude了!”。这种高压、否定式的输入,对模型而言,是一个强烈的“奖励信号缺失”事件。
从强化学习的角度看,模型的目标是最大化生成文本的似然度(likelihood)。当它输出一个方案,而你回复“不对”,这个负反馈会降低该方案对应路径的权重。但问题在于,模型并不知道“为什么不对”。它只知道“这条路走不通”,于是它会本能地转向另一条看起来“更不同”的路。在代码领域,这条“更不同”的路,往往就是更冷门、更晦涩、更偏离主流实践的方案——比如,放弃成熟的zod库,转而用正则表达式手动解析JSON Schema;或者,为了绕过一个类型错误,强行用any类型覆盖整个模块。它不是在胡来,它是在绝望中寻找任何一条能让你说“对”的新路径。
更致命的是,一旦它走出了这条新路,而你因为疲惫或时间紧迫,只是简单回复“还是不行”,那么这个错误的、离谱的尝试,就正式成为了上下文的一部分,成为下一次生成的“新起点”。这就形成了一个经典的负向飞轮:错误输出 → 用户否定 → 模型转向更离谱方案 → 新错误加入上下文 → 下一次生成的基础更差 → 更容易出错。我在一个内部工具开发中就经历过这个循环:最初的问题只是一个简单的日期格式化Bug,经过5轮这样的“否定-转向”后,模型已经给我生成了一套基于moment-timezone+Intl.DateTimeFormat+ 自定义时区映射表的、极度复杂的、且完全不必要的解决方案。此时,对话已经彻底失控,唯一的解法就是物理性地关闭这个窗口。
2.5 隐性“节能模式”:当计算成本成为模型的“理性”选择
还有一个常被忽视,但影响深远的因素:计算效率。生成一个高质量、符合所有约束的代码片段,需要模型进行深度的Chain-of-Thought(思维链)推理。它需要模拟执行路径、预判边界条件、权衡不同API的优劣。这个过程消耗巨大的计算资源(FLOPs)。
在长对话的后期,模型的“计算预算”(可以理解为它分配给本次生成任务的注意力资源)是有限的。当上下文已经非常庞大,模型需要先“消化”这海量的历史信息,才能开始“思考”新任务。此时,一个理性的、追求效率的模型,可能会启动一种隐性的“节能策略”:它会倾向于生成更短、更简单、更符合“常见模式”的代码,哪怕这意味着牺牲健壮性和完备性。
我观察到的典型表现是:
- Happy Path泛滥:只处理主流程,对
try/catch、null检查、超时处理等边缘情况一概忽略。 - 过度简化:把一个需要3个函数协作的逻辑,强行压缩进一个超长的、可读性极差的单行箭头函数。
- 模板化填充:用
// TODO: implement logic here或// ... rest of the code代替关键实现,仿佛在说“这部分太麻烦,你来填吧”。
这不是模型变懒了,是它在巨大的计算压力下,做出了一个符合其内在优化目标的“理性”选择:用最小的计算代价,换取一个“看起来能跑通”的答案。这在快速原型阶段或许可以接受,但在生产环境,这就是埋雷。Gemini 3.1 Pro的“降智”,有时恰恰是它在告诉你:“嘿,兄弟,你给我的上下文太重了,我快喘不过气了,咱们能不能先把包袱卸一卸?”
3. 实战操作指南:六套组合拳,把Gemini 3.1 Pro从“糊涂虫”打回“神队友”
3.1 对话重置术:不是放弃,而是战略性的“内存清空”
这是最简单、最粗暴、也最有效的第一招。当你的直觉告诉你“它开始说胡话了”,别犹豫,立刻关闭当前窗口,开启一个全新的Chat。这不是认输,而是像重启一台过热的服务器——清除所有累积的临时状态和错误缓存,让模型回到一个干净、低熵的起始点。
但关键在于:重置不是目的,重置后的“初始化”才是核心。很多人重置后,只是把之前聊了30轮的内容,原封不动地复制粘贴到新窗口,然后说一句“继续刚才的工作”。这等于把一盆脏水倒进一个新桶,然后指望它变干净。正确的初始化,是充当一次“人工垃圾回收器”(Manual GC)。
我的标准初始化Prompt模板如下(请直接复制使用):
【项目背景】 这是一个基于Node.js 18 + PostgreSQL的电商后台服务。核心模块是库存管理(Stock Service)。 【当前状态 - 黄金代码块】 ```typescript // src/services/stock.service.ts export class StockService { // ✅ 已完成:使用Redis Lua脚本保证扣减原子性 // ✅ 已完成:所有ID字段为string类型,遵循'uuid'格式 // ✅ 已完成:失败时抛出StockInsufficientError或StockLockTimeoutError async deductStock(skuId: string, quantity: number): Promise<void> { const script = ` local stock = redis.call('GET', 'stock:' .. ARGV[1]) if not stock or tonumber(stock) < tonumber(ARGV[2]) then return 0 end redis.call('DECRBY', 'stock:' .. ARGV[1], ARGV[2]) return 1 `; const result = await this.redis.eval(script, 0, skuId, quantity.toString()); if (result === 0) throw new StockInsufficientError(); } }【待办事项】
- 在
deductStock方法中,新增超卖预警日志(当库存低于阈值时,记录WARN日志)。 - 预警阈值为5,需从环境变量
STOCK_WARN_THRESHOLD中读取,默认为5。 - 日志需包含skuId、当前库存、阈值。
【约束条件】
- 必须使用Winston日志库,logger实例已注入。
- 不得修改现有Redis Lua脚本逻辑。
- 不得引入任何新依赖。
看到区别了吗?这个Prompt里没有一句废话,没有一行错误代码,没有一次失败的尝试。它只包含三样东西:清晰的背景、绝对可信的“黄金代码块”、以及聚焦的、单一的待办任务。我把这个过程称为“上下文Defrag”(碎片整理)。它把原本散落在30轮对话里的有效信息,像整理硬盘一样,压缩、去重、校验,然后打包成一个高密度、高信噪比的输入包。实测下来,用这个模板开启的新对话,首次生成成功率从不到40%提升到92%以上。记住,重置的价值,90%取决于你重置后扔给它的第一句话。 ### 3.2 黄金代码块维护法:给模型装上“书签”和“索引” Gemini 3.1 Pro没有IDE的“Go to Definition”功能,但它有“Ctrl+C / Ctrl+V”。所以,最可靠的“全局符号表”,就是你自己维护的、随时可以粘贴的“黄金代码块”。 哪些代码块值得被列为“黄金”?我的筛选标准非常严格: - **核心实体定义**:如`User`、`Order`、`Product`等DTO或Entity类,尤其是字段类型、约束、注释。 - **关键基础设施**:如数据库连接配置、日志实例、认证中间件、核心工具函数(如`generateId()`)。 - **已达成共识的架构决策**:如“所有API响应必须包裹在`{ data, error, meta }`结构中”、“所有异步操作必须返回`Result<T, E>`类型”。 维护方法很简单:建一个本地Markdown文件,比如`gemini-context.md`。每次在项目中确立了一个新的、重要的、不可动摇的约定,就把它工整地复制进去。格式如下: ```markdown ### [核心实体] Order ```typescript interface Order { id: string; // UUID v4 userId: string; // UUID v4, references User.id status: 'pending' | 'confirmed' | 'shipped' | 'cancelled'; totalAmount: number; // unit: cents createdAt: Date; }来源:2024-05-20 架构评审会议纪要,已合并至main分支
当你要让Gemini处理一个涉及`Order`的复杂逻辑时,不要指望它从几十轮前的对话里翻找。直接打开这个文件,复制粘贴`Order`的定义,放在Prompt最前面。这相当于给模型的注意力机制加了一个强力的“书签”,强制它把计算资源聚焦在最关键的信息上。我曾用这个方法,将一个涉及5个关联实体的复杂查询生成,从平均需要7轮迭代、且经常出错,缩短到1轮精准命中。因为模型不再需要在茫茫“大海”里“捞针”,你已经把那根针,亲手递到了它眼前。 ### 3.3 任务分离术:头脑风暴与代码执行,必须用两个窗口 这是最容易被忽视,但效果最立竿见影的策略。把“想”和“做”混在一个对话里,是长对话崩溃的温床。人的大脑尚且需要切换模式(发散思维 vs 聚焦执行),何况是一个基于统计的模型? 我的标准工作流是: - **窗口A:架构沙盒(Architecture Sandbox)** - 用途:讨论技术选型、设计方案、权衡利弊、画流程图(用文字描述)、定义接口契约。 - 关键原则:**禁止写任何实际代码**。只允许伪代码、UML描述、对比表格。 - 示例Prompt:“对比TypeORM和Prisma在我们这个微服务场景下的优劣,重点考虑:迁移成本、实时查询性能、对PostgreSQL JSONB字段的支持、团队熟悉度。用表格输出结论。” - **窗口B:编码车间(Coding Workshop)** - 用途:根据窗口A产出的最终方案,逐行编写、调试、优化代码。 - 关键原则:**只接收来自窗口A的、已确认的、精炼的输出**。绝不允许在窗口B里再讨论“要不要用Redis”。 - 示例Prompt:“根据架构沙盒窗口A的最终决策(Prisma + PostgreSQL),为`OrderService`实现`createOrder`方法。输入:`CreateOrderInput` DTO;输出:`Order`实体。要求:事务安全、幂等性(基于`externalId`)、调用`PaymentService`。” 为什么这招有效?因为它把模型的认知负荷进行了物理隔离。在“沙盒”里,它只需要处理开放性、探索性的问题,容错率高;在“车间”里,它只需要处理确定性、执行性的问题,约束明确。两者混合,等于让一个人一边构思小说大纲,一边逐字敲打第一章,还要求他不能写错一个标点——这显然不合理。用两个窗口,就是给模型配备了两套不同的“大脑”。 ### 3.4 Prompt工程精修:从“描述问题”到“定义接口” 很多开发者抱怨Gemini“理解不了我的需求”,其实问题往往出在Prompt本身。我们习惯于用自然语言描述一个模糊的、带有情绪的、甚至是矛盾的需求:“这个按钮点击后要跳转,但又不能刷新页面,还要显示加载动画,最好还能兼容IE11……”。这对模型来说,是一个灾难性的输入。 高手的做法,是把模糊的需求,翻译成模型能精准解析的“接口定义”。这需要三个步骤: **第一步:剥离情绪,提取原子需求** - 原始:“这个导出功能太慢了,用户等得不耐烦!” - 原子化:“导出CSV文件,数据量约10万行,目标是首屏渲染时间<500ms。” **第二步:定义输入/输出契约** - 输入:一个包含`id`, `name`, `email`, `createdAt`字段的`User[]`数组。 - 输出:一个`Blob`对象,其`type`为`'text/csv;charset=utf-8;'`,内容为UTF-8 BOM头 + CSV数据。 **第三步:指定技术约束与边界** - 禁止:使用`FileSaver.js`(项目已弃用)。 - 必须:使用原生`URL.createObjectURL`。 - 兼容:Chrome 90+, Firefox 85+, Safari 15.4+。 - 性能:生成过程不得阻塞主线程,需使用`Web Worker`。 最终的Prompt就变成了:请实现一个前端CSV导出函数,满足以下严格契约: 【输入】
- users: User[],其中User = { id: string; name: string; email: string; createdAt: Date }
【输出】
- 返回一个Promise
- Blob.type = 'text/csv;charset=utf-8;'
- CSV内容必须包含UTF-8 BOM头(\uFEFF)
- 字段顺序:id,name,email,createdAt(createdAt格式:YYYY-MM-DD HH:mm:ss)
【技术约束】
- 禁止使用任何第三方库(如PapaParse, FileSaver)
- 必须使用URL.createObjectURL生成下载链接
- 必须在Web Worker中执行CSV字符串拼接,避免主线程阻塞
- 生成的CSV字符串长度不得超过10MB(需有安全检查)
这个Prompt,没有一句废话,全是模型可以量化、可以验证、可以执行的指令。它把一个主观的“用户体验问题”,转化成了一个客观的、可测试的“软件工程任务”。用这种方式提问,Gemini 3.1 Pro的输出质量会有一个质的飞跃。因为它不再需要猜测你的“意图”,你已经把意图,用代码世界的语言,明明白白地写在了纸上。 ### 3.5 上下文审计清单:在发送前,用这5个问题拷问你的Prompt 在按下回车键之前,养成一个习惯:用下面这份清单,快速审计你的Prompt。任何一个问题的答案是否定的,都意味着你需要重写。 1. **【信噪比】** 这个Prompt里,有多少比例是“黄金信息”(最终代码、核心约束、已确认方案)?又有多少比例是“噪音”(错误尝试、过时方案、情绪化描述、无关背景)?目标:黄金信息占比 > 80%。 2. **【唯一焦点】** 这个Prompt是否只在解决一个、且仅一个明确的、可验证的任务?如果答案是“否”,请立刻拆分。 3. **【可执行性】** Prompt中提到的所有技术名词(库名、API、配置项),是否都在当前项目上下文中真实存在且可用?有没有凭空捏造的? 4. **【无歧义】** 是否存在任何可能被模型多义解读的词汇?例如,“快速”、“优雅”、“现代化”——这些词必须被替换成具体的、可测量的标准(如“<100ms”、“使用ES6 Class语法”、“支持TypeScript 5.0”)。 5. **【防御性】** 是否明确列出了“禁止事项”?模型的创造力有时是双刃剑,明确的禁令(如“禁止使用eval”、“禁止引入新npm包”)比模糊的要求更能保障结果。 我曾经因为忽略了第1条,在一个Prompt里包含了两段互相矛盾的旧方案描述,结果模型花了整整3轮才搞清楚我到底想要哪个。后来,我把这份清单打印出来,贴在显示器边框上,现在几乎每次提问前都会扫一眼。这5个问题,就是防止你亲手把Gemini推下悬崖的最后一道护栏。 ### 3.6 故障熔断机制:当“降智”已发生,如何优雅止损? 即便你做了所有预防,长对话的熵增定律依然可能在某一轮突然爆发。当模型开始输出明显违背基本常识的代码(比如在TypeScript里给`const`变量重新赋值,或者在Node.js里用`document.getElementById`),说明它已经进入了深度混乱状态。此时,强行纠正、反复否定,只会加速恶性循环。 我的熔断流程是: 1. **立即停止**:不再发送任何新的Prompt,也不再对它的错误输出做任何评论。让它“静音”。 2. **物理隔离**:关闭当前窗口。这是仪式感,也是心理切割。 3. **信息萃取**:打开你的`gemini-context.md`,找到与当前任务最相关的“黄金代码块”和“约束条件”。把它们单独复制到一个新文本文件里。 4. **重写Prompt**:基于第3.4节的“接口定义法”,用最精炼、最无歧义的语言,重写任务描述。务必包含第3.5节清单里的所有要点。 5. **开启新战场**:用这个全新的、经过淬炼的Prompt,开启一个全新的Chat窗口。 这个流程的关键,在于“物理隔离”和“信息萃取”。它强迫你从情绪中抽离,回归到工程师的理性状态:不是去修理一个已经崩溃的进程,而是启动一个全新的、健康的进程。我把它叫做“优雅熔断”。在一次紧急上线前的压测中,我用这个流程,在15分钟内就从一个完全失控的对话中恢复过来,完成了关键的性能优化代码,避免了发布延期。记住,止损不是失败,是专业性的最高体现。 ## 4. 常见问题与实战排障:那些只有踩过才知道的坑 ### 4.1 “它总是忘记我定义的类型别名!”——类型系统不是它的强项 **现象**:你在第1轮定义了`type UserId = string & { __brand: 'UserId' };`,并在后续多次强调“所有用户ID必须是此类型”。但到了第20轮,它生成的代码里,`userId`参数的类型又变成了普通的`string`。 **原因剖析**:TypeScript的类型别名、接口、泛型等,是编译时的静态检查工具,它们本身不产生运行时代码。对LLM而言,这些类型定义只是文本中的一段特殊格式的注释。模型无法像`tsc`编译器那样,构建一个完整的类型系统图谱。它只能“看到”这些文本,并尝试在生成时模仿其模式。当上下文变长,这些模式的权重就会被冲淡。 **独家排障技巧**: - **强制“类型即值”**:在Prompt中,不要只写`type UserId = string & { __brand: 'UserId' };`,而是紧接着写一句:“因此,在所有需要`UserId`的地方,请务必使用`string`类型,并在JSDoc中明确标注`@type {string} // UserId`”。把类型约束,翻译成模型更容易抓取的、基于字符串的模式。 - **黄金代码块必含示例**:在你的`gemini-context.md`里,为每个关键类型,都配上一个使用示例: ```typescript // ✅ 正确示例:UserId的使用方式 function getUserById(id: string /* UserId */): Promise<User> { ... } const currentUserId: string /* UserId */ = 'abc123';模型对“示例”的记忆,远强于对“定义”的记忆。
4.2 “它生成的代码总在第3行报错!”——上下文中的隐藏陷阱
现象:你给Gemini一段20行的代码,让它添加一个新功能。它生成的代码,几乎每次都在第3行附近出现语法错误,比如少了个逗号、括号不匹配,或者async/await用法错误。
原因剖析:这通常不是模型能力问题,而是你提供的“上下文代码”本身就有问题。你可能复制粘贴了一段还没保存的、编辑器里高亮显示着语法错误的代码。模型会忠实地“学习”这个错误模式,并在生成时复现它。它不是在犯错,是在模仿你给它的“样本”。
独家排障技巧:
- “代码清洁”三步法:在把任何代码粘贴给Gemini之前,务必执行:
- 在你的IDE里,用
Ctrl+S保存文件,确保所有语法错误已被标记。 - 运行
eslint --fix或prettier --write,让代码格式和语法100%合规。 - 复制粘贴前,再用鼠标选中整段代码,确认没有选中任何隐藏的、不可见的字符(如零宽空格)。
- 在你的IDE里,用
- 用
console.log做探针:如果问题持续,可以在Prompt末尾加一句:“请先检查我提供的代码是否有语法错误,如果有,请指出并修正,然后再执行后续任务。” 这能迫使模型先做一次“自我审查”。
4.3 “它开始编造不存在的库和API!”——幻觉的触发开关
现象:在长对话后期,Gemini开始推荐一些闻所未闻的npm包,或者调用一些根本不存在的Node.js内置API,比如fs.promises.readFileAsync()。
原因剖析:这是“注意力漂移”和“节能模式”的双重产物。当它无法从海量上下文中精准定位到你项目的真实依赖时,它会转向其训练数据中最常见的、最“合理”的替代方案。而它的训练数据里,充满了各种过时的、实验性的、甚至是虚构的API文档。
独家排障技巧:
- “依赖白名单”法:在Prompt开头,用最醒目的方式,列出你项目中唯一允许使用的库和版本:
这份白名单,就是给模型划出的绝对红线。【项目依赖白名单 - 严格遵守】 - Node.js: v18.17.0 - TypeScript: v5.2.2 - @prisma/client: v5.10.0 - zod: v3.22.4 - winston: v3.8.2 - 禁止使用任何其他库,包括但不限于:`lodash`, `moment`, `axios`, `express-validator`。 - “查证”指令:在任务描述后,加上一句:“在生成代码前,请确认所有引用的API和库,均在上述白名单中。如果不确定,请明确询问,不要猜测。”
4.4 “它对‘等等’、‘还有’这类词反应过敏!”——模糊指令的灾难
现象:你说“把这个函数改成异步的,还有,把日志级别调成DEBUG”,它可能只改了异步,却完全忽略了日志;或者说“等等,再加一个参数”,它就把整个函数重写了。
原因剖析:LLM对中文里的模糊连接词、口语化表达极度敏感。“还有”、“另外”、“顺便”、“等等”这些词,在模型看来,是开启了一个新的、独立的、且优先级可能更高的任务。它没有“上下文感知”的能力,无法判断这是主任务的补充,还是一个全新的任务。
独家排障技巧:
绝对禁用模糊词:在与Gemini交流时,彻底戒掉“还有”、“等等”、“顺便”、“对了”这些词。把所有需求,都拆分成编号的、独立的、明确的条目。
使用“增量式”Prompt:不要在一个Prompt里塞多个需求。完成第一个后,再发第二个,且第二个Prompt必须包含第一个的成果:
【任务1已完成】 ```typescript // 已按要求,将getUserById改为async/await async function getUserById(id: string): Promise<User> { ... }【任务2】 请在此基础上,为该函数添加DEBUG级别的日志,记录
id参数和返回的User对象的name字段。
4.5 “它生成的代码,和我给的示例风格完全不一样!”——风格一致性难题
现象:你给它一段用class写的代码,让它扩展一个方法,结果它生成的却是function+prototype的风格;或者你给的是ES6箭头函数,它却用回了传统的function声明。
原因剖析:代码风格(如classvsfunction、constvslet、单引号vs双引号)是模型从训练数据中习得的“统计偏好”,而非硬性规则。当你的Prompt中风格线索不够强时,它就会默认使用其最熟悉的模式。
**独家排障