1. 项目缘起:当“禁令”遇上“好奇心”
最近公司里发生了一件挺有意思的事。老板,一位在传统软件行业摸爬滚打了二十多年的技术老兵,在一次内部会议上,突然宣布了一条“禁令”:所有实习生,禁止在工作电脑上使用任何类似 Claude 5 这样的前沿大语言模型工具。他的理由很直接,甚至有点“老派”:“这东西太危险了,生成的代码不可控,有安全漏洞怎么办?泄露公司数据怎么办?你们现在最重要的是打好基础,别总想着走捷径。”
会议室里一片寂静,几个实习生面面相觑。我能理解老板的担忧,在传统开发流程里,代码的可靠性、安全审计、知识产权的边界,这些都是红线。但作为一个自己也偷偷在探索 AI 编程可能性的人,我心里犯起了嘀咕:工具本身并无善恶,关键在于如何使用。把这样一个潜力巨大的助手完全拒之门外,是不是一种因噎废食?
于是,一个有点“叛逆”的想法冒了出来:老板不让用,是怕我们用在正式项目上出问题。那我偏要用,但不是去碰公司的核心业务代码,而是用它来做一个完全属于我个人的、能展示综合能力的小项目——一个桌面端应用程序。我想证明的是,这类 AI 工具在正确的引导和严格的把控下,不仅能成为学习的“加速器”,更能成为一个激发创意、验证想法的“副驾驶”。更重要的是,我想通过一个完整的作品,来展示一个实习生除了完成指派任务外,所具备的问题解决能力、技术整合能力和主动学习能力。
这个想法让我有点兴奋。我决定,就用这个“违禁品”Claude 5,从零开始“搓”一个桌面 APP。这不仅仅是一次技术尝试,更像是一次小小的“正名”行动:危险的不是工具,而是对工具的误用和无知。我要做的,是驾驭它。
2. 核心思路:用AI辅助,而非替代
决定动手之后,我并没有一头扎进代码里。我首先花时间梳理了整个项目的核心思路,这决定了后续所有动作的走向。我的核心原则非常明确:AI是强大的辅助,但绝不能成为思考的替代品。我要用它来提升效率,突破知识盲区,但项目的架构设计、关键决策、代码审查和最终集成,必须由我自己牢牢掌控。
2.1 项目定位与技术选型
我需要一个合适的“靶子”项目。它不能太简单(否则没有挑战性),也不能太复杂(毕竟时间精力有限)。最终,我选择开发一个“本地文件内容智能搜索与摘要工具”。
- 需求场景:我们团队经常需要在一堆杂乱的本地文档(Markdown、TXT、PDF)里找特定信息,Windows自带的搜索只能匹配文件名或简单文本,对于“找出所有讨论过‘用户画像’的文档并概括要点”这类需求无能为力。
- 核心功能:
- 指定文件夹后,能递归读取其中支持的文档类型。
- 提取文档中的文本内容,并进行清洗和分块(避免单次处理文本过长)。
- 集成 Claude 5 的 API,实现对文档块的智能语义理解。
- 提供自然语言搜索框:用户输入“帮我找关于项目风险评估的部分”,工具能返回相关的文档片段和由 AI 生成的简要摘要。
- 图形化界面,方便非技术同事使用。
技术选型上,我做了如下考虑:
- 桌面端框架:选择Electron。原因很简单,我会 JavaScript/TypeScript,Electron 能让我用前端技术快速构建跨平台(Windows/macOS)的桌面应用,UI 开发效率高。虽然安装包体积大些,但对于这个工具类应用可以接受。
- AI 集成:直接使用Claude 5 的官方 API。相比在本地部署开源模型,API 方式初期成本更低(有免费额度),效果稳定,且最关键的是——所有数据交互都可以通过我自己的代码严格把控,只发送我需要处理的文本块,绝不涉及任何公司敏感信息或原始文件本身。这从设计上就规避了老板最担心的数据泄露风险。
- 本地数据处理:用 Node.js 的
fs模块进行文件读取,用pdf-parse库解析 PDF,文本分块和清洗自己写逻辑。确保 AI 只接触到经过处理的纯文本片段。
这个选型过程,我没有让 AI 代劳,而是基于自己的知识储备和项目需求独立判断。之后,我才将我的选型思路描述给 Claude 5,让它帮我分析潜在的坑点,比如 Electron 的打包优化、大文件读取的内存管理、API 调用的频率限制等。它给出了非常中肯的补充建议,这验证了“人主决策,AI辅佐”模式的可行性。
2.2 规避风险的架构设计
老板的“危险论”核心在于失控。因此,我的架构设计第一原则就是“可控与隔离”。
- 数据边界清晰:应用逻辑明确区分“本地处理域”和“AI服务域”。本地域负责文件 IO、文本预处理、界面交互,这些代码完全自主编写。只有清洗后的、非敏感的文本块,才会被有控制地发送到 AI 服务域(即调用 Claude API)。绝不上传整个文件、文件路径、任何可能包含个人或公司信息的元数据。
- 网络请求可控:所有对 Claude API 的调用,都封装在一个独立的服务模块中。该模块设置了明确的超时、重试和熔断逻辑。并且,我在代码中硬编码了内容过滤器,在发送前会对文本进行二次扫描(使用简单的关键词正则匹配),如果发现可能涉及敏感词汇(如内部项目代号、服务器 IP 等),则会中止本次请求并在界面给出安全提示。虽然简单,但这是一个主动的风险控制姿态。
- 结果不可直接执行:AI 返回的是自然语言描述的搜索结果和摘要,永远不是可执行的代码片段。应用不会动态
evalAI 返回的内容。这从根本上杜绝了“AI生成恶意代码并执行”的可能性。 - 完整的日志与回滚:应用的所有操作,尤其是文件读取和 API 调用,都有本地日志记录。如果出现任何意料之外的情况(如 API 返回了奇怪的内容),我可以快速追溯并切断问题源。
我把这个架构草图讲给 Claude 5 听,让它以“安全审计员”的角度挑毛病。它指出了我忽略的一点:API Key 的存储安全。在开发版中硬编码 Key 是常见但危险的做法。它建议采用 Electron 的safeStorage或系统密钥链来加密存储,并在打包时确保 Key 不被泄露。这个提醒非常关键,我立刻采纳并实现了。
注意:这里有一个重要的实操心得。与 AI 讨论架构时,不要问“我该用什么架构?”,而要告诉它“我打算用 XXX 架构,基于 A、B、C 考虑,请你帮我审查其中的安全风险和设计缺陷”。这样能引导 AI 进行更有深度、更具批判性的辅助,而不是让它替你思考。
3. 开发实操:与AI结对编程的日与夜
有了清晰的蓝图,真正的“搓”APP过程开始了。我采用了典型的“功能驱动”开发模式,将整个过程拆解成几个核心模块,在每个模块中,我与 Claude 5 的协作方式各有不同。
3.1 环境搭建与基础框架
这部分几乎是纯手动操作,目的是建立扎实的“地基”。
- 初始化项目:
npm init创建项目,安装 Electron、TypeScript、必要的类型定义包。 - 配置基础脚本:在
package.json中配置好dev(启动开发)、build(打包)等脚本。 - 搭建主进程与渲染进程:编写 Electron 的主进程文件(
main.ts),创建浏览器窗口,加载渲染进程的 HTML。渲染进程我打算用纯原生 JavaScript 和 CSS 来写,避免引入复杂的框架以保持轻量。
这个过程我没怎么求助 AI,因为都是标准流程。但我把搭建好的基础项目结构丢给了 Claude 5,让它检查tsconfig.json配置是否合理,以及package.json里有没有遗漏的关键依赖。它帮我补上了一个用于进程间通信(IPC)的 TypeScript 类型定义包,避免了后续的类型报错。
3.2 核心模块一:本地文件处理器
这个模块负责“脏活累活”:遍历文件夹、识别文件类型、读取内容、解析 PDF、文本清洗与分块。
难点在于 PDF 解析和智能分块。PDF 的文本提取很容易格式混乱,包含大量换行和空格。我写了初步的清洗函数后,将一段混乱的 PDF 提取文本和我的处理函数一起交给 Claude 5。
我的提示词是:“以下是一段从PDF提取的原始文本,包含许多不必要的换行和空格。我写了一个清洗函数(见下方代码),目标是将其恢复成连贯的段落。请分析我的函数逻辑,指出其缺陷,并提供一个改进版本,要求能更智能地合并被错误断开的句子。”
Claude 5 没有直接给我新代码,而是先分析了我的函数:它指出我单纯用正则替换所有换行符会破坏真正的段落结构。然后,它给出了一个分步策略:先按连续换行符进行初步分段,再在每一段内部处理“软换行”(即行末没有标点符号的换行),最后合并。它还附上了一个考虑了中英文标点差异的改进版函数。
我按照这个策略重写了模块,效果立竿见影。这个过程中,我学到了如何将模糊需求转化为可执行的技术策略,这是 AI 辅助编程带来的最大价值之一——它像一个经验丰富的同事,帮你把思路理清。
3.3 核心模块二:AI 服务封装与调用
这是与 Claude 5 交互最直接的部分,也是安全关键区。
封装 API 客户端:我创建了一个
AIService.ts类,将 Anthropic 官方 SDK 的调用封装起来。关键配置包括:apiKey: 从环境变量或加密存储中读取。maxTokens: 严格控制响应长度,防止生成内容过长。temperature: 设置为较低值(如 0.2),让回答更确定、更少“创造性”,这对于搜索摘要任务很合适。systemPrompt: 这是灵魂所在。我精心设计了一段系统指令:“你是一个专业的文档分析助手。用户会给你一段文本片段和一个问题。你的任务是从该片段中找出与问题最相关的内容,并用简洁、客观的语言进行总结,直接回答用户的问题。如果片段中不包含相关信息,请直接回答‘未找到相关信息’。不要编造信息,不要添加片段中没有的内容。”
实现搜索函数:函数接收一个“查询字符串”和一个“文本块数组”。它会遍历每个文本块,将“查询字符串”和“文本块内容”组合成用户提示,调用封装好的 API 客户端。为了提高效率并控制成本,我实现了并发控制,同时发起多个 API 请求,但限制最大并发数(如 5 个),避免触发速率限制。
结果聚合与排序:AI 会为每个文本块返回一个相关性摘要。我需要将这些结果收集起来,并根据摘要的质量(是否明确回答了问题)和文本块本身的来源进行简单排序,在前端呈现。
我把这个模块的代码和我的设计思路发给 Claude 5 审查。它提出了两个宝贵建议:
- 建议一:增加重试机制。网络请求可能失败,API 也可能返回临时错误。它建议我对非 200 状态码的响应,特别是 429(过多请求)和 5xx 错误,实现指数退避的重试逻辑。
- 建议二:优化提示词。它指出我的
systemPrompt已经不错,但可以更明确地要求 AI 在摘要开头就判断相关性,例如:“首先判断该片段是否与用户问题相关。如果相关,请以‘相关:’开头,然后给出摘要;如果不相关,请以‘不相关:’开头。” 这样便于我后续的程序化处理。
我采纳了这两个建议,模块的健壮性和结果的可处理性大大提升。
3.4 核心模块三:Electron 前端界面与交互
界面我追求简洁实用。主界面包含:
- 一个文件夹选择按钮和路径显示框。
- 一个搜索输入框和搜索按钮。
- 一个结果显示区域,用于展示匹配的文档名、摘要预览。
挑战在于主进程(Node.js环境)与渲染进程(浏览器环境)之间的通信(IPC)。文件读取和 AI 调用这些耗时的、需要 Node.js 能力的操作必须在主进程进行,而界面交互在渲染进程。
我按照 Electron 的官方模式,在预加载脚本(preload.js)中暴露有限的 API 给渲染进程。例如,暴露一个window.electronAPI.selectFolder()方法,该方法内部通过ipcRenderer.invoke向主进程发送消息。
这里我遇到了一个具体问题:如何将文件读取和 AI 搜索的进度反馈到前端的进度条?我向 Claude 5 描述了场景:“我在主进程有一个异步任务,它包含多个子步骤(读取文件、分块、并发搜索)。我想在渲染进程显示一个总体进度条。主进程如何向渲染进程持续发送进度更新?”
Claude 5 给出了完美的方案:使用ipcMain和ipcRenderer的send/on方法进行单向事件通信,而不是invoke/handle这种请求-响应模式。我在主进程的每个关键步骤后,用mainWindow.webContents.send('progress-update', {step, progress})发送事件;在渲染进程用ipcRenderer.on('progress-update', ...)监听并更新 UI。这个方案实现后,用户体验流畅了很多。
4. 踩坑实录与避坑指南
整个开发过程绝非一帆风顺,AI 不是万能的,很多坑需要自己踩过才明白。以下是我遇到的几个典型问题及解决方案,这些都是你在文档里不容易看到的“实战经验”。
4.1 坑一:Electron 打包后文件路径问题
问题描述:在开发环境下,使用path.join(__dirname, '..', 'assets')来定位资源文件一切正常。但通过electron-builder打包成可执行文件后,应用启动报错,找不到资源文件。
根因分析:在打包后,__dirname指向的是应用程序解压到的临时目录(ASAR 归档内),路径结构发生了变化。而fs模块的某些同步 API 在读取 ASAR 包内的文件时行为不一致。
解决方案:
- 区分资源类型:将静态资源(如图标、配置文件)明确分为两种:需要打包进应用的和需要随应用分发的。
- 使用
app.getAppPath()和process.resourcesPath:对于打包进 ASAR 的资源,在开发和生产环境下,使用path.join(electron.app.getAppPath(), 'path/to/resource')来获取路径更可靠。对于需要放在应用安装目录下的资源(如数据库文件),则使用path.join(process.resourcesPath, '..', 'app', 'data')来定位。 - 动态判断环境:在代码中判断
electron.app.isPackaged,针对开发和生产环境采用不同的路径解析逻辑。
// 示例代码片段 import { app } from 'electron'; import path from 'path'; function getResourcePath(relativePath) { if (app.isPackaged) { // 生产环境:资源位于 resources/app.asar 内或外部 return path.join(process.resourcesPath, 'app', relativePath); } else { // 开发环境:基于项目根目录 return path.join(__dirname, '..', relativePath); } }实操心得:Electron 打包是最大的“玄学”之一。最好的办法是,尽早打包、频繁测试。不要等到所有功能都开发完了才第一次打包,那时路径问题会和其他 bug 纠缠在一起,极难排查。我在完成基础框架后(大约项目第 2 天)就打了第一个包,虽然功能不全,但验证了基础路径和依赖没问题,为后续开发扫清了障碍。
4.2 坑二:Claude API 的速率限制与成本控制
问题描述:当对一个包含数百个文档的文件夹进行搜索时,程序会快速发起大量 API 请求,很快便收到429 Too Many Requests错误。同时,免费额度消耗得飞快。
根因分析:Anthropic 的 API 有每分钟(RPM)和每天(TPD)的请求次数限制。我的并发控制虽然限制了同时请求数,但没有控制单位时间内的总请求频率。此外,我没有估算每个请求的 Token 消耗,导致成本不可预测。
解决方案:
- 实现更精细的速率限制器:我引入了一个简单的令牌桶算法。设置一个“桶”,容量为 N 个请求(如 30 个),以固定速率(如每秒 1 个)向桶中添加令牌。每次发起请求前,必须先获取一个令牌,否则就等待。这确保了请求速率平滑,不会突增。
- 添加请求队列与优先级:将所有搜索请求放入一个队列。对于用户主动触发的“即时搜索”,给予高优先级;对于后台的“批量索引”任务,给予低优先级。队列管理器结合速率限制器来调度请求。
- 估算与监控 Token 使用:在发送请求前,粗略估算输入文本的 Token 数(可以用简单规则:英文字符约 0.25 token/个,中文字符约 1-2 token/个)。在代码中添加日志,记录每次请求的估算输入 Token 和 API 返回的实际使用 Token,便于分析和预警。
- 设置成本熔断:在代码中设置一个每日估算成本的阈值(例如,免费额度的 80%)。当程序估算的当日消耗接近阈值时,自动停止发起新的 AI 请求,并在界面给出友好提示。
4.3 坑三:文本分块策略对搜索质量的影响
问题描述:初期我简单地按固定字符数(如 2000 字符)对文档进行分块。结果发现,搜索时经常返回不完整或上下文缺失的摘要。例如,一个问题被分在了两个块里,AI 只看其中一个块,自然无法给出好答案。
根因分析:固定长度分块会粗暴地切断句子和段落,破坏语义完整性。AI 模型(尤其是 Claude 这类注重上下文理解的模型)在处理一个语义破碎的文本块时,效果会大打折扣。
解决方案:实现基于语义的智能分块。
- 优先按段落分:首先用换行符(
\n\n+)将文本分割成自然段落。 - 合并小段落:如果连续几个段落都很短(如少于 100 字符),则将它们合并成一个块,直到接近目标块大小(如 1500 字符)。
- 尊重句子边界:当合并段落导致块大小超过上限时,不在句子中间切断。而是回溯到上一个句子结束处(句号、问号、感叹号)作为当前块的终点,超出的部分留给下一个块。
- 添加重叠窗口:在相邻的两个文本块之间,设置一个小的重叠区(如 200 字符)。这样能确保被边界切分的关键信息,在相邻块中仍有部分上下文,提高搜索召回率。
这个分块逻辑我反复调整了好几次。我把不同的分块结果和对应的搜索效果记录下来,形成了一些经验性规则。例如,对于技术文档,块可以稍大(2000-3000 字符),因为上下文依赖强;对于会议纪要,块应该小一些(800-1500 字符),因为话题可能转换很快。
4.4 坑四:前端长时间操作的 UI 卡顿
问题描述:当处理一个包含大量文件的文件夹时,文件读取和 AI 搜索可能需要几十秒甚至几分钟。在此期间,如果 UI 线程被阻塞,界面会完全卡住,无响应。
根因分析:尽管我把耗时操作都放在了主进程,但 Electron 的渲染进程(前端 UI)和主进程之间的通信如果处理不当,或者主进程的 CPU 密集型任务(如大量文本处理)没有适时让出控制权,仍然会影响渲染进程的响应性。
解决方案:
- 主进程任务异步化与分片:确保主进程的所有耗时操作都是异步的(
async/await)。对于超大型任务(如处理上千个文件),将其分片。例如,每处理完 10 个文件,就通过 IPC 发送一次进度更新,并且使用setImmediate或process.nextTick让事件循环有机会处理其他事件(如来自渲染进程的点击事件)。 - 渲染进程使用 Web Workers:对于前端自身的一些复杂计算(如结果排序、高亮渲染),可以放入 Web Worker 中执行,避免阻塞 UI 线程。
- 提供取消操作:在界面上提供一个明显的“取消”按钮。当用户点击时,渲染进程发送一个取消信号给主进程。主进程需要检查一个“取消标志”,在任务分片的间隙,如果发现标志被设置,则清理资源并停止后续任务。
- 优化界面反馈:除了进度条,在长时间操作时,将按钮置为禁用状态,并显示一个旋转的加载指示器,让用户明确知道应用正在工作,而非卡死。
5. 项目收尾与“意外”的转正
经过大约两周的业余时间开发、调试和优化,这个被我戏称为“Claude 搜书犬”的小工具终于能稳定运行了。我把它打包成了绿色版的可执行文件,没有连接任何后台,所有数据都在本地处理。
在一个周五的下午,我鼓足勇气,带着我的笔记本,敲开了老板办公室的门。我没有一上来就展示工具,而是先简单汇报了近期分配的实习任务进展。然后,我话锋一转:“老板,关于您上次提到 AI 工具风险的问题,我深入思考了一下。我完全认同安全可控是第一位的。为了能更好地理解这里的边界,我私下用业余时间做了一个小实验,想请您看看,这样使用 AI 的思路是否还存在您担心的那些风险?”
接着,我演示了整个工具:如何选择文件夹、如何输入自然语言进行搜索、结果如何呈现。我特意打开了开发者工具,切换到网络标签页,让她看到每次请求只发送了经过处理的纯文本片段,并且指向的是 Anthropic 的官方 API 地址。我还展示了代码中关于内容过滤和日志记录的部分。
老板一开始表情严肃,看着看着,眉头逐渐舒展开。她问了我几个很关键的问题:“你怎么保证上传的内容里不包含敏感信息?”“如果 API 返回了错误或有害信息,你的程序怎么处理?”“这个工具的效率提升具体体现在哪?”
我一一回答,基于之前架构设计时的思考:预处理过滤、系统指令约束、结果不直接执行、以及对比传统搜索方式在复杂语义查询上的时间优势。我强调,这个工具的核心价值不是 AI 本身,而是**“人设计的流程”** 对“AI 能力”的安全、有效调用。
她听完,沉默了一会儿,然后说:“代码和工具留给我看看。” 那天晚上,我收到了她的消息,内容很简单,但分量很重:“你做的这个工具,思路很清晰,风险控制考虑得也比较周全。最重要的是,你能主动思考、动手验证,并且有清晰的安全边界意识。这很好。下周一,来我办公室聊聊转正的事吧。”
后来我才知道,她私下让团队里一位资深工程师 review 了我的代码结构。反馈是:架构清晰,模块解耦做得不错,错误处理和日志记录比较完备,虽然有些地方可以优化,但作为一个实习生阶段的个人项目,完成度和思考深度都超出预期。
6. 回顾与思考:AI时代实习生的“正确姿势”
这次经历让我对如何在职场中学习、使用新技术有了更深的体会。老板的“禁令”从来不是目的,而是对潜在风险的预警。我的“违禁”也并非挑衅,而是一次建立在理解风险基础上的、负责任的探索。
安全红线是前提:无论工具多强大,数据安全、代码安全、系统稳定都是不可逾越的红线。在使用任何外部 AI 服务前,必须想清楚数据流转的边界在哪里,最坏情况是什么,如何兜底。我的项目将 AI 严格限定在“只读”和“摘要”层面,且输入经过清洗,这就是在划清安全边界。
AI 是杠杆,不是拐杖:用它来放大你的能力,而不是替代你的思考。让它帮你写那些重复、繁琐的样板代码(如数据格式化、简单的 CRUD 函数),帮你查阅不熟悉的 API 文档,帮你审查代码的潜在 bug。但架构设计、核心算法、业务逻辑、安全策略,必须由你自己主导。我的项目里,AI 帮我优化了分块算法、设计了 IPC 通信模式,但整个应用的设计理念、技术选型、安全控制框架,都是我自己的决策。
用作品说话:在职场中,尤其是作为实习生,最有说服力的往往不是你说了什么,而是你做出了什么。一个能实际运行、解决具体问题、代码整洁、考虑周全的作品,远比空谈技术趋势更有力量。这个桌面 APP 就是一个 tangible(可触摸的)的证据,证明了我不仅有兴趣,更有能力将新技术转化为实际价值。
沟通的方式很重要:如果我直接去和老板争论“AI 就是好,你该用”,结果很可能适得其反。我选择了先理解她的顾虑(安全、可控),然后用一个具体的、受控的实例来展示另一种可能性。这是一种建设性的、解决问题导向的沟通。
转正,是对我这次“冒险”的认可,但我觉得,更大的收获在于这个过程本身。它逼着我从“会用工具”到“理解工具”,从“写代码”到“设计系统”,从“完成任务”到“创造价值”。Claude 5 危险吗?在不受控的使用者手里,任何强大的工具都危险。但在一个懂得划定边界、明确目标、并愿意为之负责的开发者手里,它是一个前所未有的强大伙伴。
所以,如果你也在面对类似的技术“禁令”或疑虑,我的建议是:不要停留在争论,去动手构建一个你自己的“小项目”。在安全可控的沙盒里,充分探索技术的边界。用严谨的代码和清晰的逻辑,向你的团队证明,你不是在追逐潮流,而是在驾驭工具,解决问题。这,或许才是技术人最硬的底气。