
你是否遇到过这样的场景当你与一个AI助手进行复杂对话时它输出的内容冗长、重复甚至包含了大量你并不关心的内部“思考”过程尤其是在使用一些新兴的、强调“思考过程透明化”的AI Agent时这个问题尤为突出。用户真正需要的往往是那个经过深思熟虑后的、简洁明了的最终答案而不是一个需要手动筛选的“思维草稿本”。最近围绕“Pi”这个关键词的讨论热度很高从“Pi Agent”到各种集成方案社区都在探索如何让AI更高效地工作。本文要探讨的正是基于这个痛点的一个具体实践为类似Pi这样的AI对话体实现一个“思考折叠”功能。这并非一个庞大的开源项目而是一个精巧的工程思路和实现方案。简单来说“思考折叠”的核心目标是让AI在输出时能够自动将其冗长的推理链、内部计算过程“折叠”或“摘要”起来只向用户呈现清晰、直接的结论或回答同时保留让用户“展开”查看详细思考过程的能力。这就像代码编辑器中的代码折叠功能既保持了界面的整洁又不丢失任何细节。本文将从一个开发者的视角完整拆解这个功能的实现思路。你将了解到为什么“思考折叠”对提升AI交互体验至关重要——不只是为了美观。如何设计一个轻量级、可插拔的“思考折叠”处理器——从原理到架构。使用Python和FastAPI构建一个可运行的示例服务——包含完整代码。如何将其集成到你的AI应用前端如Web聊天界面——提供前端交互示例。处理过程中的常见陷阱与最佳实践——比如如何准确识别“思考”与“回答”的边界。无论你是在开发自己的AI助手还是希望优化现有基于大语言模型LLM的应用这个思路都能为你提供直接的参考价值。我们开始吧。1. 这篇文章真正要解决的问题从“过程透明”到“结果友好”当前许多先进的AI模型和Agent框架例如一些类“CoT”链式思考的实现倾向于输出完整的推理过程。这对于调试、理解模型行为和教育目的非常有价值。然而在最终用户交互场景中这带来了显著的体验问题信息过载用户可能需要滚动很久才能找到最终答案。干扰判断内部的、可能不完善的推理步骤可能会干扰用户对最终答案可信度的判断。响应缓慢传输和渲染大量文本影响前端性能。“思考折叠”要解决的正是在保留AI“思考过程”这一有价值特性的前提下优化最终用户的消费体验。它不是一个简单的字符串截取而是一个智能的内容结构化过程识别自动从AI的原始输出中区分出“内部思考”Let‘s think step by step...,I need to calculate...和“最终回答”Therefore, the answer is...,所以最终的结果是...。转换将识别出的“内部思考”部分进行摘要或标记并“折叠”起来。呈现前端只显示“最终回答”和一个小小的“展开思考过程”按钮。交互用户可以根据需要点击按钮查看完整的、原始的思考链条。这背后的核心判断是对于大多数交互用户要的是“答案”而不是“过程”但对于需要验证或学习的场景“过程”必须触手可及。我们的实现就是要平衡这两点。2. 核心概念与设计原理在开始编码前我们需要明确几个关键概念和设计选择原始响应AI模型如GPT、Claude或本地模型直接生成的、包含完整思考步骤的文本。结构化响应经过我们处理器处理后的数据通常是一个JSON对象包含final_answer和thinking_process或folded_thoughts等字段。折叠策略摘要式折叠将冗长的思考过程总结成一两句话。这需要额外的摘要模型或规则成本较高。标记式折叠仅识别思考部分的起止位置并将其标记为“可折叠区域”。这是本文采用的主流方法因为它轻量、保真。触发词识别如何让AI在输出时主动标记思考部分有两种方式后处理识别在AI输出后通过规则关键词匹配或轻量模型文本分类来识别。优点对AI提示词无侵入。缺点识别可能不准。提示词约束在给AI的提示词中严格要求其使用特定标记如think.../think包裹思考内容。优点识别准确率100%。缺点需要模型遵循指令且改变了原始提示。本文将演示后处理识别基于规则的方法因为它更通用不依赖特定模型的指令跟随能力。在实际生产中可以结合两种方式。3. 环境准备与项目结构我们将使用Python作为后端语言FastAPI构建Web服务。前端使用简单的HTML/JavaScript进行演示。环境要求Python 3.8pip 包管理工具项目结构pi-thought-fold-demo/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 应用主入口 │ ├── processors.py # 思考折叠处理器核心逻辑 │ └── schemas.py # Pydantic 数据模型 ├── static/ │ └── index.html # 前端演示页面 ├── requirements.txt └── README.md安装依赖创建requirements.txt文件fastapi0.104.1 uvicorn[standard]0.24.0 pydantic2.5.0通过pip安装pip install -r requirements.txt4. 核心流程拆解后处理识别方案我们的后端处理管道将遵循以下步骤这个流程清晰地将一个“聪明”的想法转化为了可执行的代码逻辑接收原始响应从AI服务接口获取完整的响应文本。应用识别规则使用预定义的正则表达式或关键词列表尝试匹配“思考过程”的典型模式。提取与分割根据匹配到的位置将文本分割为“思考部分”和“最终回答部分”。如果未匹配到则将整个文本视为“最终回答”。构建结构化数据将分割后的部分组装成一个结构化的字典或JSON对象。返回结构化响应将结构化的数据返回给前端。前端渲染与交互前端根据结构化数据默认隐藏思考部分并提供交互按钮。这个流程的关键在于第2步识别规则的设计。规则太简单会漏判太复杂会难以维护。我们从基础规则开始。5. 完整后端实现FastAPI服务与处理器5.1 定义数据模型schemas.py首先我们定义输入输出的数据格式这能确保接口的清晰和健壮。# app/schemas.py from pydantic import BaseModel from typing import Optional class AIResponse(BaseModel): 从AI服务接收到的原始响应模型 raw_text: str # 可以扩展其他字段如model_name, session_id等 model_name: Optional[str] None class FoldedResponse(BaseModel): 处理后的、可折叠的响应模型 final_answer: str thinking_process: Optional[str] None # 被折叠的原始思考文本 has_thoughts: bool False # 标记是否有可折叠的思考过程5.2 实现思考折叠处理器processors.py这是核心逻辑所在。我们实现一个基于正则表达式的规则处理器。# app/processors.py import re from typing import Tuple from .schemas import FoldedResponse class ThoughtFolder: 思考折叠处理器 # 定义常见的“思考开始”触发词英文和中文 # 这是一个基础规则集可以根据你的AI输出特点进行扩充和调整 THINKING_START_PATTERNS [ r让我们一步一步地思考, # Let‘s think step by step r首先, r首先, r我需要计算, r我需要计算, r思考过程, rthink, r\[思考\], # 如果提示词中约定了标记 rReasoning:, rI need to, rLet‘s think, rStep 1:, ] # 编译成正则表达式忽略大小写 THINKING_START_REGEX re.compile( ( |.join(THINKING_START_PATTERNS) ), re.IGNORECASE ) # 定义可能的“思考结束/答案开始”模式 ANSWER_START_PATTERNS [ r所以, r因此, r最终答案是, r答案是, r综上所述, ranswer, r\[答案\], rTherefore,, rThus,, rThe answer is, rFinal answer:, r\n\n, # 双换行也常用来分隔思考与答案 ] ANSWER_START_REGEX re.compile( ( |.join(ANSWER_START_PATTERNS) ) ) classmethod def fold(cls, raw_text: str) - FoldedResponse: 核心折叠方法。 尝试从原始文本中分离思考过程和最终答案。 if not raw_text: return FoldedResponse(final_answer, thinking_processNone, has_thoughtsFalse) # 查找思考开始的位置 start_match cls.THINKING_START_REGEX.search(raw_text) if not start_match: # 没有找到思考开始的标志整个文本作为最终答案 return FoldedResponse( final_answerraw_text.strip(), thinking_processNone, has_thoughtsFalse ) thinking_start_idx start_match.start() # 提取思考开始之前的部分可能是问题或空 prefix raw_text[:thinking_start_idx] # 在思考开始之后的部分查找答案开始的位置 text_after_thought raw_text[thinking_start_idx:] answer_match cls.ANSWER_START_REGEX.search(text_after_thought) if answer_match: # 找到了答案开始的标志 answer_start_idx_in_sub answer_match.start() thinking_process text_after_thought[:answer_start_idx_in_sub].strip() final_answer text_after_thought[answer_start_idx_in_sub:].strip() else: # 没有找到明确的答案开始标志尝试用一些启发式方法 # 例如将最后一个“所以”、“因此”之后的内容作为答案 # 这里简化处理将思考开始后的所有内容视为思考没有明确答案 # 更复杂的实现可以引入句子分割和分类 thinking_process text_after_thought.strip() final_answer (答案可能包含在思考中请展开查看完整内容。) # 清理最终答案如果它是以触发词开头去掉触发词使其更简洁 for pattern in cls.ANSWER_START_PATTERNS: if final_answer.startswith(pattern): final_answer final_answer[len(pattern):].strip() break # 如果最终答案太短或为空而思考过程很长可以调整 if not final_answer or len(final_answer) 10 and len(thinking_process) 50: final_answer thinking_process[-150:] ... # 取思考尾部作为预览 thinking_process thinking_process return FoldedResponse( final_answerfinal_answer, thinking_processthinking_process if thinking_process else None, has_thoughtsbool(thinking_process) )5.3 构建FastAPI主应用main.py现在我们将处理器包装成一个简单的Web API。# app/main.py from fastapi import FastAPI from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from .schemas import AIResponse, FoldedResponse from .processors import ThoughtFolder app FastAPI(titlePi Thought Folding API, description一个简单的AI思考折叠服务) # 挂载静态文件目录用于前端页面 app.mount(/static, StaticFiles(directorystatic), namestatic) app.get(/, response_classHTMLResponse) async def read_index(): 返回前端演示页面 with open(static/index.html, r, encodingutf-8) as f: return HTMLResponse(contentf.read()) app.post(/fold, response_modelFoldedResponse) async def fold_thoughts(ai_response: AIResponse): 接收AI原始响应返回折叠后的结构化响应。 这是核心API端点。 folded_resp ThoughtFolder.fold(ai_response.raw_text) return folded_resp if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)6. 前端交互实现一个简单的演示页面前端需要做两件事1) 调用我们的折叠API2) 实现展开/折叠的交互。我们创建一个简单的HTML页面。!-- static/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titlePi 思考折叠演示/title style body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 1rem; } .container { border: 1px solid #ccc; border-radius: 8px; padding: 1.5rem; } textarea { width: 100%; height: 150px; margin-bottom: 1rem; padding: 0.5rem; } button { padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background: #0056b3; } #result { margin-top: 2rem; } .final-answer { background-color: #f8f9fa; padding: 1rem; border-left: 4px solid #28a745; margin-bottom: 1rem; } .thinking-process { background-color: #fff3cd; padding: 1rem; border-left: 4px solid #ffc107; display: none; } /* 默认隐藏 */ .thinking-process.visible { display: block; } .toggle-btn { background: #6c757d; margin-top: 0.5rem; } .toggle-btn:hover { background: #545b62; } /style /head body div classcontainer h1Pi 思考折叠功能演示/h1 p粘贴一段包含思考过程的AI响应文本点击处理查看折叠效果。/p label foraiInputAI原始响应/label textarea idaiInput placeholder例如让我们一步一步地思考。首先用户问的是法国的首都。法国的首都是巴黎。这是一个常识。所以最终答案是巴黎。 让我们一步一步地思考。首先用户问的是法国的首都。根据地理知识法国的首都是巴黎。巴黎位于法国北部是法国的政治、经济、文化中心。因此答案非常明确。所以最终答案是巴黎。/textarea button onclickprocessText()处理并折叠思考/button div idresult !-- 结果将动态插入到这里 -- /div /div script async function processText() { const rawText document.getElementById(aiInput).value.trim(); if (!rawText) { alert(请输入一些文本); return; } const payload { raw_text: rawText }; try { const response await fetch(/fold, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data await response.json(); displayResult(data); } catch (error) { console.error(Error:, error); document.getElementById(result).innerHTML p stylecolor:red;请求失败: ${error.message}/p; } } function displayResult(data) { const resultDiv document.getElementById(result); let html h3处理结果/h3; // 显示最终答案 html div classfinal-answerstrong最终答案/strongp${escapeHtml(data.final_answer)}/p/div; // 如果有思考过程显示折叠区域和切换按钮 if (data.has_thoughts data.thinking_process) { html button classtoggle-btn onclicktoggleThoughts(this)展开思考过程/button div classthinking-process idthoughtsContent strong思考过程/strongp${escapeHtml(data.thinking_process)}/p /div ; } else { html pem未检测到明显的可折叠思考过程。/em/p; } resultDiv.innerHTML html; } function toggleThoughts(button) { const thoughtsDiv document.getElementById(thoughtsContent); thoughtsDiv.classList.toggle(visible); button.textContent thoughtsDiv.classList.contains(visible) ? 收起思考过程 : 展开思考过程; } // 简单的HTML转义防止XSS function escapeHtml(text) { const div document.createElement(div); div.textContent text; return div.innerHTML; } /script /body /html7. 运行结果与效果验证现在让我们启动服务并测试整个流程。第一步启动后端服务在项目根目录下运行uvicorn app.main:app --reload --host 0.0.0.0 --port 8000看到类似Uvicorn running on http://0.0.0.0:8000的输出说明服务启动成功。第二步打开前端页面在浏览器中访问http://localhost:8000。你将看到演示页面。第三步测试功能在文本框中粘贴或输入一段模拟的AI响应例如让我们一步一步地思考。首先用户问的是法国的首都。根据地理知识法国的首都是巴黎。巴黎位于法国北部是法国的政治、经济、文化中心。因此答案非常明确。所以最终答案是巴黎。点击“处理并折叠思考”按钮。预期结果页面上方“最终答案”区域会清晰地显示“巴黎”。下方会出现一个“展开思考过程”的按钮。点击该按钮被折叠的详细思考过程会展开显示出来。验证成功的关键点API接口/fold返回了正确的JSON结构包含final_answer和thinking_process。前端正确解析了JSON并实现了折叠/展开的交互。对于没有明显思考标记的文本如直接说“巴黎是法国首都”处理器能将其全部识别为最终答案且不显示折叠按钮。8. 常见问题与排查思路在实际集成和使用中你可能会遇到以下问题问题现象可能原因排查方式解决方案服务启动失败提示模块找不到依赖未安装或Python路径问题1. 检查requirements.txt是否存在。2. 运行pip list查看 fastapi, uvicorn 是否安装。3. 检查运行目录是否正确。1. 在项目根目录执行pip install -r requirements.txt。2. 确保在app目录的上级目录启动服务。访问localhost:8000显示404或空白页静态文件路径错误或未挂载1. 检查static/index.html文件是否存在。2. 查看main.py中StaticFiles挂载的目录是否正确。1. 确保项目结构符合上文描述。2. 检查app.mount(/static, StaticFiles(directorystatic), namestatic)中的directory参数是否为static相对路径。点击“处理”按钮后前端无反应或报错前端JavaScript错误或API调用失败1. 打开浏览器开发者工具F12查看“控制台”(Console)有无红色报错。2. 查看“网络”(Network)标签页对/fold的POST请求状态码是否为200。1. 检查processText函数中的fetch URL是否正确应为/fold。2. 检查后端服务是否正在运行。3. 查看后端日志是否有错误。思考过程识别不准确该折叠的没折叠不该折叠的折叠了正则表达式规则不匹配你的AI输出模式1. 将出错的AI原始响应打印到后端日志。2. 分析其文本结构找出“思考”与“回答”的边界词。1. 修改app/processors.py中的THINKING_START_PATTERNS和ANSWER_START_PATTERNS列表添加或调整匹配模式。2. 考虑使用更复杂的NLP方法如文本分类进行识别。处理中文文本时规则失效正则表达式可能对中文标点或空格处理不当检查模式中是否包含了中英文全半角符号。在模式中同时添加中文和英文的常见表述例如同时添加r“所以”和r“Therefore,”。确保使用re.IGNORECASE或 Unicode 匹配。9. 最佳实践与进阶建议将“思考折叠”投入生产环境需要考虑更多工程化细节规则引擎 vs. 轻量模型初期/简单场景使用本文的规则引擎快速验证。定期收集识别错误的样本迭代优化规则。复杂/高要求场景训练一个微小的文本分类模型如基于BERT的小模型来更准确地判断句子属于“思考”还是“回答”。可以将规则引擎作为兜底方案。提示词工程协同最可靠的方式是在调用AI模型的提示词Prompt中明确要求其使用特定格式输出例如请按以下格式回答thinking你的逐步推理过程放在这里/thinkinganswer最终答案放在这里/answer这样后处理就变成了简单的XML/HTML标签解析准确率极高。这需要你所用的AI模型具有良好的指令跟随能力。性能与缓存折叠处理是CPU密集型操作尤其是使用复杂正则或模型时。对于高并发场景可以考虑对处理结果进行缓存例如以原始文本的哈希值为Key。将处理器部署为独立的、可横向扩展的微服务。前端体验优化动画为展开/折叠添加平滑的过渡动画。部分展开不一定全部隐藏可以展示思考过程的前一两行作为“预览”。多种视图提供“仅答案”、“答案思考”、“完整原始响应”等多种视图模式让用户选择。安全与过滤在处理用户提供的或AI返回的文本时务必做好HTML转义如前端示例中的escapeHtml函数防止XSS攻击。对于思考过程中可能出现的敏感信息可以考虑在后端处理器中添加过滤逻辑。“思考折叠”是一个小而美的功能它背后体现的是以用户体验为中心的设计思想。通过本文的实践你不仅获得了一个可运行的工具更重要的是掌握了一种处理AI输出、优化人机交互的通用思路。你可以将这个处理器轻松集成到你的聊天机器人、智能客服或任何基于LLM的应用中立即提升产品的专业感和易用性。下一步你可以尝试将规则引擎升级为机器学习模型或者将其封装成一个独立的Python包方便在其他项目中复用。