ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

本地部署RAG情感智能助手:原理、实现与调优实践

本地部署RAG情感智能助手:原理、实现与调优实践 1. 项目概述与定位1.1 这个项目的核心是什么我在本地搭了一套基于RAG检索增强生成的感情智能助手。说白了就是把大语言模型完全跑在自己电脑上让它可以读取你的私人资料——日记、聊天记录、收藏的文章、读书笔记——然后基于这些内容和你对话并且能理解你说话时的情绪状态做出带有温度的回应。这和普通的聊天机器人有本质区别普通聊天机器人是人多力量大式的通用回答你问它什么它从训练数据里泛泛地答。而这个助手是私人定制式的它知道你看过什么书、写过什么日记、关注过哪些事。你对它说我最近好累它不会给你灌一篇标准化的心灵鸡汤而是会想起你上个月日记里写的那次项目冲刺想起你收藏的那篇关于倦怠管理的文章然后基于这些东西来回你。项目解决的核心问题有两个。第一是隐私感情类数据是最敏感的日记、聊天记录这些东西让云端模型去读很多人心理上就过不去这一关本地部署能从根本上切断数据外流的路径。第二是情感理解和上下文结合纯靠大模型的通用能力做情感陪伴往往流于表面而RAG能把私人的、碎片化的历史信息变成助手的记忆让它的回应有的放矢。适合做这个项目的人有两类。一类是对RAG技术本身感兴趣的开发者想搞明白检索增强是怎么落地的这个项目难度适中技术栈完整是个很好的练手对象。另一类是对AI情感陪伴有真实需求的人比如想给自己做一个长期记忆的树洞或者想给孩子做一个能和学校知识、家庭记录联动的智能伙伴这个项目可以做到完全离线、完全可控。1.2 这个项目不是聪明小玩具在动手之前我得先把预期管理做好。这套系统不会像ChatGPT那样什么都懂、张口就来因为它的知识范围被刻意限制在了你喂给它的私人资料里。但恰恰是这个限制造就了它最大的价值确定性。你问它日报里写过的某个决定它不会瞎编因为它会先去检索引擎里找到相关段落再基于那段内容回答。如果检索不到它会老老实实说这个我没有记录而不是编一个答案糊弄你。感情智能的部分也不是那种浮夸的我检测到您今天情绪低落为您播放轻音乐。我做的更克制一些它会识别你当前的情绪倾向正面、负面、焦虑、疲惫等在检索和生成时考虑这个信号调整回应的策略。你情绪低落时它少一点俏皮话多倾听多共情你明显开心的时候它再开玩笑。这种动态调整比一个固定人格的聊天机器人真实得多。另外提醒一下别把这个项目当成什么科研工作它就是一个务实落地的东西。技术栈选型也全部以本地能跑、配置不苛刻、效果够用为原则所以我会尽量避免那种动辄要求24GB显存的企业级方案。2. 技术选型与架构设计2.1 模型选型本地跑得动的才是好模型整个系统的核心引擎是本地大模型我选了Ollama作为模型运行时。原因很直接Ollama对普通用户友好到极致安装完敲两行命令就能拉起模型而且它对内存和CPU的利用优化做得不错笔记本也能跑得动中小尺寸的模型。模型本身我建议从Qwen2.5和Llama 3.2这两个系列里选。注意看参数规模不要贪大模型参数规模量化方式内存需求中文情感理解推荐场景Qwen2.5-7B-Instruct7BQ4_K_M约6GB优秀中文语感好首选通用对话能力强Qwen2.5-3B-Instruct3BQ4_K_M约3GB良好CPU也能跑低配机器选它Llama 3.2-3B-Instruct3BQ4_K_M约3GB一般英文更好英文资料多的场景Llama 3.2-1B-Instruct1BQ8_0约1.5GB较弱纯测试验证流程用我实测下来7B的Qwen2.5在情感理解上明显甩开同尺寸的Llama尤其在处理中文的含蓄表达时比如没事这种话Qwen能读出潜在的低落情绪Llama经常当真觉得没事。如果你手里有16GB内存的电脑建议直接上7B Q4量化版如果只有8GB内存老实点用3B把上下文窗口开大一点效果也能接受。2.2 向量库选型轻量优先RAG需要向量数据库来存资料切片。市面上方案很多Milvus、Weaviate、Qdrant这些都是好工具但对本地单机项目来说太重了像是开卡车去菜市场买菜。我选的是ChromaDB理由很务实纯Python实现pip装完就能用不用额外起服务默认本地持久化数据落盘就是一个文件夹备份迁移很方便API设计简洁检索、写入、过滤一两条代码搞定单机千万级向量以内性能足够这个项目根本到不了那个量级如果说你的资料量特别大超过了几十万个切片再考虑换Qdrant社区版它有更好的过滤和索引机制。但绝大多数个人助手项目ChromaDB都是最优解。2.3 整体架构的工作流程整个系统跑起来之后一次对话的处理链路是这样的用户输入一句话先经过情绪识别模块情绪识别结果和用户原话一起进入检索器检索器把用户原话做向量化从向量库里召回最相关的Top-K个文本切片把切片、用户的原话、情绪标签、对话历史拼成一个大提示词交给本地大模型生成回复这里有个关键点情绪识别是先用一个轻量规则模型做的不是直接丢给大模型。因为每次对话都经过大模型做情绪识别的话耗时会增加两三秒而且会让整体成本翻倍。规则模型做初步情感分类把值得关注的情况标出来大模型只在生成时感知情绪这样既快又稳。2.4 为什么不用纯Prompt硬怼有人可能会想直接把资料全部塞进上下文不搞RAG不是更简单吗我试过这条路走不通。7B模型的上下文窗口一般是8K到32K token折算成中文大约是一万到四万字你根本没法把一整年的日记塞进去。塞进去之后还有一个更严重的问题大模型对长上下文的注意力是分散的中间段内容经常被忽略生成的回答反而因为信息过载变差。RAG的本质是外挂硬盘先做精确检索再去读需要的那一小段。既不受上下文窗口限制又能保证回答是基于检索到的具体内容生成的。这也是RAG在本地部署场景下依然是主流方案的根本原因。3. 环境准备与模型部署3.1 硬件配置与量化概念先说硬件底线。我的主力测试机是Intel i5-12400 32GB内存 GTX 16606GB显存跑Qwen2.5-7B的Q4量化版生成速度大概是每秒15到20个token体感就是打字速度略慢一点但够用。如果你没有独立显卡用纯CPU跑7B Q4也不是不行但速度会掉到每秒3到5个token急死人建议用3B模型。这里解释一下量化。大模型的参数通常是FP16半精度浮点数存储的7B模型的FP16权重大约14GB普通家用机扛不住。量化就是把权重压缩成4bit或8bit牺牲一点精度换取体积和速度。Q4_K_M是4bit量化里的一个不错方案质量损失很小但模型文件直接从14GB降到4.4GB左右。本地部署量化不是可选项是必需品。3.2 Ollama的安装与模型拉取Ollama官网下载对应系统的安装包装完就完事了。Windows和macOS都有原生安装包Linux用一条curl命令。装完之后验证一下ollama --version然后拉取模型。我先拉对话模型ollama pull qwen2.5:7b-instruct-q4_K_M这里有个细节Ollama的模型名是带标签的不指定标签默认拉取该系列的最新版本。如果内存不够换成3Bollama pull qwen2.5:3b-instruct-q4_K_M拉完以后可以用命令行先聊两句试试ollama run qwen2.5:7b-instruct-q4_K_M3.3 Embedding模型的选择RAG的检索环节需要embedding模型它的作用是把文本变成一串向量数字让语义上相近的文本在向量空间里靠得近。这个模型不需要有对话能力但必须对中文语义理解足够好。我推荐用Ollama里的nomic-embed-text或者bge-m3。对比一下输入侧embedding的选型直接决定了检索质量的上限支持8000 token的输入长度对长文档切片很友好中文效果比同尺寸的英文embedding模型好一个档次拉取命令ollama pull bge-m3如果你不喜欢Ollama管理embedding也可以直接用HuggingFace上的sentence-transformers库但Ollama的方式更省事反正都是本地跑的。3.4 启动模型的注意事项Ollama默认会常驻后台服务端口是11434。启动模型的时候可以调整两个参数OLLAMA_CONTEXT_LENGTH控制上下文窗口长度OLLAMA_NUM_PARALLEL控制并发请求数。对个人助手来说上下文窗口可以放到8192以上并发数1就够了因为你就一个人用。注意Ollama默认不设置context length时有的模型只会用2048的窗口这在RAG场景下会截断太多内容。建议在启动Ollama服务前设置环境变量OLLAMA_CONTEXT_LENGTH8192或者在后端代码调用时显式传入num_ctx参数。我一开始没注意这个结果检索回来的原文被截断得七零八落答非所问。4. 知识库构建从原始资料到向量索引4.1 文档处理与文本拆分知识库的原材料就是你喂给助手的资料日记txt、微信聊天导出、读书笔记Markdown、收藏的网页。第一步是解析成纯文本然后把长文本切成适合作检索的块。文本拆分是整个RAG流程里最容易被低估的环节。块太大检索回来的内容太泛大模型读不过来块太小语义被切断检索召回率低。我实测下来中文场景下每块200到300字比较合适。具体实现用递归字符分割按段落、句子、标点逐层切from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size250, chunk_overlap50, separators[\n\n, \n, 。, , , , , ] ) chunks text_splitter.split_text(raw_text)chunk_overlap这个参数值得说道一下它让相邻两个块之间保留50字的交叠部分这样跨块的上下文信息不会被拦腰截断。我最早没设overlap结果出现了大量句子前半截在上一块、后半截在下一块的情况检索哪怕命中了一块也是残缺信息生成的质量大打折扣。4.2 向量化与写入向量库拿到文本块之后需要用embedding模型把它们变成向量然后写到ChromaDB里。完整流程import chromadb from chromadb.utils import embedding_functions # 使用Ollama的embedding模型 ollama_ef embedding_functions.OllamaEmbeddingFunction( urlhttp://localhost:11434/api/embeddings, model_namebge-m3 ) client chromadb.PersistentClient(path./memory_store) collection client.get_or_create_collection( namepersonal_memory, embedding_functionollama_ef, metadata{hnsw:space: cosine} ) # 写入 collection.add( ids[fchunk_{i} for i in range(len(chunks))], documentschunks, metadatas[{source: source_name, date: date_str} for source_name in ...] )这里面有几个值得注意的决策点。第一hnsw:space用cosine还是ip取决于你的embedding模型bge系列模型官方推荐cosine效果最稳。第二元数据一定加上source和date后面按时间过滤、按来源追溯都会用到。第三ChromaDB的embedding_function参数可以不传直接传已经算好的向量但我建议让它内部调用Ollama省事且统一。4.3 检索策略与效果调优检索的时候最基础的方法是直接取相似度最高的几个结果。但直接这样做效果一般原因在于用户问题往往是口语化的和知识库里的书面表达存在措辞差异。我用的优化策略是查询改写多路召回倒排权重。查询改写在交给embedding前先用一个轻量prompt把用户的说法翻译成更标准的检索式。比如用户说我上个月写的那个关于假期计划的日记改写成假期计划 日记 上个月能显著提升召回效果。多路召回先用embedding向量检索取前20个结果再用关键词重叠度比如BM25或简单的Jaccard相似度取前10个结果然后去重合并重排序后取前5个作为上下文。这个重排序的细节很多人会忽略。向量相似度高的块不一定是最相关的因为在语义空间里最近有时候是话题接近而不是事实相关。我的做法是把检索回来的结果拼接一个问题让本地小模型打分但这个方案对7B模型来说耗时太长。折中方案是把向量相似度和关键词重合度做个加权平均final_score 0.7 * vector_score 0.3 * keyword_score实测这招对细节回忆型问题的提升非常明显。4.4 多格式文档的入库技巧除了纯文本你很可能有PDF、Word、图片里的文字。我踩过的坑是pdfminer这类纯文本解析工具对扫描版PDF完全没辙。后来用了MinerU来做文档解析它能输出结构化的Markdown对排版、表格、图片中的文字都有很好的处理效果。MinerU本身是开源的支持本地部署正好契合我们的隐私要求。处理图片里的文字可以用OCR工具比如PaddleOCR它对中文印刷体和手写体的识别率都不错。日记如果是以图片形式存的比如手机拍照先OCR再入库。注意OCR出来的文本错误率不低直接入库会污染检索结果。我的做法是OCR之后做一遍轻量校对脚本至少把明显的错误字符比如把我识别成找替换归类。这个步骤耗费的精力不少但是值得。5. 情感理解模块与人格设定5.1 情感识别不用大模型情感识别模块我选择了基于情感词典和句式的规则引擎。别觉得这方法土它快、稳、可解释。每一条用户输入先做情感打分正负情感词比如开心、烦、焦虑、舒服对应不同权重词库程度副词非常、有点、太作为加权系数句式判断反问句和感叹句的情感表达强度更高举个例子我真的好累什么都提不起兴趣但生活还得继续。这个句子里的累、提不起兴趣是负向词但生活还得继续表达了一种隐性的积极坚持整体情绪标签可以打上疲惫但坚韧。这种混合情感识别用规则很不好搞后来我又调了一次先粗分类成正向、负向、中性三类再对负向做细分厌倦、焦虑、悲伤、愤怒、孤独。5.2 Prompt工程让大模型感知情绪状态情感标签不是直接裹进用户原话里的而是作为一个额外的上下文块传进最后的提示词。我的Prompt结构大致是系统提示 你是小暖一个温柔但不油腻的私人情感陪伴助手。 你的知识来源是用户的私人记忆库请基于检索到的内容作答。 当前检测到用户的情绪倾向【疲惫且焦虑】。 当用户情绪低落时你的回应应当以倾听和共情为主 减少说教和提问避免轻飘飘的鼓励。 检索到的记忆片段 {context} 对话历史 {history} 用户当前消息{user_input}这里有个关键经验不要把情绪标签写成一个命令式的硬指令比如用户很伤心你必须安慰他而是作为状态描述交给模型。因为语言模型对状态感知模式的遵从度远高于硬性指令模式前者是共情后者是执行任务生成出来的语气差异非常大。我对比过同一个输入在两种写法下的输出状态描述模式明显更自然。5.3 记忆封装的层次结构RAG提供的是长时记忆知识库对话本身还有短期记忆多轮上下文。我设计了一套简单的记忆层次短期记忆最近10轮对话原文直接拼在prompt里长期记忆RAG检索得到的相关文本块核心人格系统提示词中固定的角色描述这三个层次的优先级和权重需要反复调试。短期记忆太长了会挤压RAG的检索结果空间我后来做了个简单策略——如果最近5轮对话里已经提到了某个话题那么RAG检索到的相关文本块可以少放一些因为短期记忆已经覆盖了一部分。这个策略节省了不少上下文空间。5.4 多角色扩展这套情感助手的架构其实是支持多角色的我开发的时候就顺手做了个配置。比如你想让它以知心姐姐、老同学、心理教练等不同身份出现只需要切换系统提示词和检索时的过滤条件。从同一个知识库里你可以用不同的人格视角去提取信息。比如知心姐姐模式检索时更看重日记里的情绪词汇老同学模式更看重共同经历的时间点。这个扩展思路如果是做产品的话很有价值一套数据支撑多种人格。6. 完整流程的代码实现6.1 主流程从检索到生成先把核心的对话处理函数写出来这个函数是系统的中枢每次用户输入都会走到这里。import ollama def chat_with_assistant(user_input, history): # 第1步情感识别 emotion detect_emotion(user_input) # 第2步查询改写 refined_query rewrite_query(user_input) # 第3步向量检索 vector_results collection.query( query_texts[refined_query], n_results20 ) # 第4步关键词召回 keyword_results keyword_search(refined_query) # 第5步合并、去重、重排序 final_chunks merge_and_rerank(vector_results, keyword_results, top_n5) # 第6步拼装Prompt context \n\n.join(final_chunks) messages [ {role: system, content: build_system_prompt(emotion)}, {role: user, content: f记忆片段\n{context}\n\n我的消息{user_input}} ] # 第7步调用本地大模型生成 response ollama.chat( modelqwen2.5:7b-instruct-q4_K_M, messagesmessages, options{temperature: 0.7, num_ctx: 8192} ) return response[message][content]这个流程顺序是固定的但每一步都有值得优化的空间后面我会展开讲。注意我把对话历史也拼进了messages里但实际测试发现如果历史太长生成速度会明显变慢。目前实测8轮对话以内是流畅的超过之后需要用滑动窗口只保留最近几轮。6.2 查询改写与关键词检索的实现查询改写不一定要用独立的模型我直接用同一个Ollama模型但加上一个专门的改写指令。因为改写本身不需要太多生成开销可以接受def rewrite_query(user_input): prompt f请把用户的问题改写成更适合检索的关键词组合要求简洁。\n用户问题{user_input}\n改写结果 resp ollama.generate(modelqwen2.5:3b, promptprompt, options{temperature: 0}) return resp[response].strip()关键词检索我用了一个简单但有效的方案jieba分词之后统计每个词的TF-IDF权重再将词汇与向量库里的文本做重合度匹配。对这其实就是BM25的思想但自己写的好处是可以控制细节、方便调试def keyword_search(query, top_k10): query_tokens set(jieba.lcut(query)) scores [] # 从ChromaDB里取所有文档计算重叠度 all_docs collection.get() for i, doc in enumerate(all_docs[documents]): doc_tokens set(jieba.lcut(doc)) overlap len(query_tokens doc_tokens) / max(1, len(query_tokens)) scores.append((all_docs[ids][i], doc, overlap)) scores.sort(keylambda x: x[2], reverseTrue) return scores[:top_k]这个方案的优点是零额外服务依赖缺点是数据量大了之后全量扫描很慢。好在个人知识库一般几千个切片全量扫描也就几十毫秒完全能接受。6.3 前端交互与入口封装后端流程跑通了还要有一个能交互的界面。我试过几套方案命令行CLI最省事适合自己用Gradio Web界面给不太懂命令行的人用支持聊天框历史记录微信公众号接入给几个朋友用但配置麻烦最终我选了Gradio因为它是纯Python的而且界面足够好看。核心代码import gradio as gr with gr.Blocks(title本地情感助手) as demo: chatbot gr.Chatbot(height450) msg gr.Textbox(label说点什么吧, placeholder跟我说说你的近况...) clear gr.Button(清空对话) def respond(user_msg, chat_history): if not user_msg.strip(): return , chat_history reply chat_with_assistant(user_msg, chat_history) chat_history.append((user_msg, reply)) return , chat_history msg.submit(respond, [msg, chatbot], [msg, chatbot]) clear.click(lambda: None, None, chatbot, queueFalse) demo.launch(server_name127.0.0.1, server_port7860)Gradio默认启动在7860端口绑定127.0.0.1就只允许本机访问数据不出电脑。如果想从局域网的其他设备访问改一下server_name就行但这样就要注意局域网安全了。6.4 后台服务与定时任务情感助手最好是常驻的我写了一个service.py统一管理所有组件Ollama进程、ChromaDB连接、Gradio界面。然后注册成系统服务开机自启。这样不用每次手动敲命令。Windows下用计划任务macOS/Linux下用systemd。核心就是一条启动命令python service.py写日志的环节也别跳过。正常对话日志、检索召回日志、错误日志分开写后面做问题排查时全靠这些日志。我吃过一次亏回复内容质量差但没留日志根本分不清是检索没召回还是模型生成出了问题。7. 常见问题与排查实录7.1 问题速查表症状可能原因解决方法回答完全脱离知识库检索到的相关块context为空检查embedding模型是否正确加载检查向量库里是否有数据回答沾边但不具体chunk太大或overlap不足调整chunk_size到200~300overlap到50以上回答像AI套话系统提示词设定不够具体强化人格设定加入情绪感知描述回答卡顿严重上下文窗口过长或量化程度不够限制历史轮数使用更小的模型检索总是漏掉关键信息查询改写没生效检查rewrite_query的输出看关键词是否保留了核心实体词启动报错连不上ChromaDB路径冲突或数据损坏换一个PersistentClient路径旧的vector store另存备份中文乱码文件编码问题入库前统一转成UTF-8清洗不可见字符7.2 效果调优实验记录我做了一组对比实验记录几个让我印象深刻的结论。第一个是chunk_size从500改成250之后回答的相关性评分明显上升。500字的块意味着一个块里可能包含了三个话题检索命中时会夹带大量无关内容。改成250字之后每个块更聚焦召回的内容也更精准代价是检索结果的碎片感更强两块信息之间的连接有时会断掉。最终用overlap 50缓解了这个断裂问题。第二个是temperature参数的调整。情感陪伴场景下temperature调得太低比如0.1回答会非常稳妥但缺乏人情味像是AI在背话术调得太高比如1.0回答又容易跑偏甚至开始教育用户。我最后稳定在0.7到0.8之间。这个区间里模型能写出一些意外的表达但又不会神游太虚。第三个是对话历史的轮数。我发现超过10轮之后模型的注意力会被历史对话稀释越早的对话被遗忘得越干净。而且对话历史越长单次响应时间就越久。后来做了一个综合策略超过8轮后只保留最后6轮其余压缩成一段摘要用另一个轻量模型把前面对话的内容总结成3句话放进上下文。这样既保留了长程记忆又不至于挤占注意力。7.3 数据隐私的自我保护本地部署最大的价值就是隐私但这里有个容易被忽略的隐患如果Gradio的服务端口绑定了所有网卡接口0.0.0.0同一局域网内的其他设备是可以访问到的。我在实际使用时只绑定127.0.0.1并且加了简单的token认证。虽然Gradio本身没有内置用户系统但可以在前端加一个密码校验。别小看这个日记这种数据一旦被别人看到后果是灾难性的。另一个隐私隐患是日志文件。调试是好事但日志里最好不要记录完整的对话原文只记录情绪标签、检索返回的文档ID、生成耗时这些元信息就足够了。万一日志文件被翻到也不至于泄露私人内容。7.4 Ollama的调优参数Ollama有一些隐藏参数值得调一下。# 设置并发数为1减少内存争抢 OLLAMA_NUM_PARALLEL1 # 关闭keep_alive模型常驻内存加快响应速度 OLLAMA_KEEP_ALIVE30m # 显式指定上下文长度避免默认2048的坑 OLLAMA_CONTEXT_LENGTH8192特别是OLLAMA_CONTEXT_LENGTH这个参数的影响我前面反复提过。如果不设置很多模型默认只使用2048的上下文窗口检索回来的内容稍微长一点后段就会被截断回复引用的信息就残缺了。设置到8192之后即使chunk接起来比较长模型也能完整读完。7.5 针对低配机器的资源优化如果你的机器只有8GB内存我推荐一个降级方案。模型换成Qwen2.5-3B Q4embedding用更轻量的all-MiniLM-L6-v2英文或者bge-small-zh中文。这两个embedding模型资源占用极小在CPU上都能跑到不错的性能。然后关闭对话历史摘要模块直接限制历史轮数在4轮以内。这样整体内存占用可以压到5GB以内跑起来还是流畅的。另外建议用ollama ps监控模型的内存占用如果发现模型被频繁卸载重载表现为响应时间忽高忽低大概率是内存不足触发OLLAMA的swap策略这时候加OLLAMA_KEEP_ALIVE的值或者干脆换更小的模型。8. 扩展想法与后续计划做好了基础版之后有几个方向我觉得值得继续深挖。第一个方向是让RAG助手具备时间感知。目前的情感助手只能被动检索历史数据如果加入日记里记录的时间轴它就能主动说出去年的这个时候你好像也在焦虑实习的事情。这种时间上下文能让对话的共情感上一个台阶。第二个方向是增加多模态能力。目前RAG知识库只能处理文本但它也能处理图片和音频。热词里提到的rag知识库能存储图片嘛答案是可以但要走对路子。图片里的信息需要先经过图像理解模型转成文字描述再进入向量库而不是直接把图片塞进去。本地跑一个轻量的视觉模型也可以做到这个我在后续的版本里计划加进去。第三个方向是做一个更聪明的记忆遗忘机制。现在知识库是收进去什么就永远在但人的记忆是会淡忘的。如果给每条记忆加上时间衰减权重旧信息在检索时权重降低新信息权重提高这个小助手会更像人——既记得你的过去又不至于被陈年旧事缠绕。第四个方向是重构情绪识别模块。规则引擎的准确率在现代大模型面前确实不够看但我选择它是因为速度和可解释性。后续可以在规则引擎预筛、大模型复核的方式上做混合识别既保证速度又提升准确率。最后想跟读者朋友们说的是这个项目最大的乐趣不在于把技术栈跑通而在于调整它、塑造它的过程。当我第一次让这个助手基于我过去写的日记回应我关于累的抱怨而它没有给出标准化的鸡汤而是说出了一句你上次说累的时候后来请了三天假去爬山回来状态好多了——那一刻我是真的感受到了技术之外的某种东西。希望你们也在这个项目里找到同样的体验。
返回列表