AI助手思考折叠功能实现:优化LLM应用交互体验
在实际 AI 应用开发中我们经常遇到一个挑战大型语言模型LLM在处理复杂任务时会生成冗长的“思考过程”thinking content。这些内容对于调试和理解模型决策至关重要但对于最终用户或下游系统而言它们往往是冗余的、需要被隐藏的“中间过程”。尤其是在构建聊天机器人、智能助手或自动化工作流时如何优雅地处理这些内部思考只呈现精炼的结论成为一个影响用户体验和系统效率的关键问题。本文将以一个具体的场景——“给 Pi 实现一个简单的思考折叠”——为例深入探讨如何为 AI 助手如基于类似 OpenAI API 的智能体设计并实现思考内容的摘要与折叠功能。这里的“Pi”可以指代一个具体的 AI 助手项目或泛指一类需要处理思考链的智能体。我们将从概念理解开始逐步构建一个可运行的解决方案涵盖数据处理、API 交互、前端展示和错误处理的全链路。无论你是正在集成 OpenAI、DeepSeek 或其他兼容 API 的开发者还是希望优化自己 AI 应用交互体验的工程师本文提供的思路和代码都将具有直接的参考价值。1. 理解“思考折叠”的核心概念与需求在深入代码之前我们必须厘清几个关键概念什么是模型的“思考内容”为什么需要折叠以及“Pi”在这个上下文中可能指代什么。1.1 模型思考内容与最终回复许多先进的 LLM如 OpenAI 的 GPT-4o、Claude 3 的“思考”模式以及 DeepSeek 等在响应时支持返回结构化的输出。这种输出通常包含两个部分思考过程模型内部推理的步骤、自我质疑、工具调用决策等。这部分内容详细但冗长格式可能是纯文本、JSON 或特定的标记格式。最终回复模型基于上述思考得出的准备呈现给用户的最终答案。这部分内容简洁、直接。在 API 响应中思考过程可能存在于诸如content[].thinking、reasoning字段或特定的工具调用tool_calls的元数据中。而最终回复则通常在content[].text或message.content字段里。1.2 为什么需要“折叠”思考用户体验用户通常只关心答案本身冗长的思考过程会干扰阅读降低对话流畅度。界面整洁在聊天界面中将思考过程默认隐藏折叠以“摘要”或“查看推理过程”按钮形式提供能保持界面清爽。调试与审计对于开发者思考过程是宝贵的调试和优化依据不能丢弃需要一种方式保存和按需查看。成本与效率思考内容可能很长传输、存储和渲染全部内容会影响前端性能和带宽。先提供摘要再按需加载详情是更优策略。1.3 “Pi”项目的上下文从输入的热词来看“Pi”很可能指的是一个具体的 AI 助手或智能体项目如 Pi Agent。它可能基于 OpenAI、DeepSeek 或其他提供思考功能的模型 API 构建。本文的解决方案是通用的我们将构建一个后端服务和一个简单的前端组件演示如何接收包含思考的 API 响应处理并呈现折叠后的效果。你可以将此方案适配到你的具体“Pi”项目中。2. 环境准备与项目结构设计我们将使用 Python 的 FastAPI 构建后端服务用简单的 HTML/JavaScript 构建前端演示界面。选择 FastAPI 是因为它轻量、异步支持好适合快速构建 AI 应用后端。2.1 开发环境与依赖确保你的 Python 版本在 3.8 以上。我们使用venv创建虚拟环境。# 创建项目目录并进入 mkdir pi-thinking-collapse cd pi-thinking-collapse # 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate # 安装核心依赖 pip install fastapi uvicorn httpx python-dotenv关键依赖说明fastapiuvicorn: 用于创建和运行 Web 服务器。httpx: 异步 HTTP 客户端用于向后端转发请求到真实的 AI 模型 API如 OpenAI。python-dotenv: 管理环境变量如 API 密钥。2.2 项目目录结构一个清晰的结构有助于维护。创建如下文件和目录pi-thinking-collapse/ ├── .env # 存储敏感信息如 API_KEY ├── .gitignore # 忽略 venv, .env 等 ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 应用主入口 │ ├── config.py # 配置管理 │ ├── services/ │ │ ├── __init__.py │ │ └── llm_service.py # 处理与 AI 模型 API 的通信 │ └── schemas/ │ ├── __init__.py │ └── models.py # Pydantic 数据模型定义 ├── static/ # 前端静态文件 │ ├── index.html │ └── script.js └── requirements.txt # 项目依赖列表运行pip freeze requirements.txt生成依赖文件。2.3 配置文件与环境变量在项目根目录创建.env文件用于配置模型 API 的访问凭证和端点。切记将此文件加入.gitignore。# .env OPENAI_API_KEYsk-your-openai-api-key-here # 或者使用其他兼容 OpenAI 的 API DEEPSEEK_API_KEYyour-deepseek-api-key LLM_API_BASEhttps://api.openai.com/v1 # 或 https://api.deepseek.com/v1 LLM_MODELgpt-4o # 或 deepseek-chat创建app/config.py来读取这些配置# app/config.py from pydantic_settings import BaseSettings class Settings(BaseSettings): openai_api_key: str deepseek_api_key: str llm_api_base: str https://api.openai.com/v1 llm_model: str gpt-4o class Config: env_file .env settings Settings()注意这里使用了pydantic-settings需要额外安装pip install pydantic-settings。你也可以使用python-dotenv直接读取。3. 构建后端接收、处理与转发 AI 响应后端核心职责是作为一个代理接收前端请求转发给真正的 LLM API接收包含思考的原始响应进行处理提取思考与回复最后返回结构化的数据给前端。3.1 定义数据模型Schemas首先在app/schemas/models.py中定义请求和响应的数据结构。这能确保接口的清晰和类型安全。# app/schemas/models.py from pydantic import BaseModel from typing import List, Optional, Any class ChatMessage(BaseModel): role: str # user, assistant, system content: str class ChatRequest(BaseModel): messages: List[ChatMessage] stream: bool False # 本文先处理非流式流式更复杂 # 可以添加其他模型参数如 temperature class ThinkingContent(BaseModel): 用于表示模型的一次思考片段 text: str # 可以扩展如添加类型reasoning, tool_call、时间戳等 type: Optional[str] reasoning class ProcessedLLMResponse(BaseModel): 处理后的响应包含最终回复和折叠的思考 final_reply: str thinking_summary: Optional[str] None # 思考的摘要用于折叠显示 full_thinking: Optional[List[ThinkingContent]] None # 完整的思考内容 raw_response: Optional[Any] None # 可选的原始响应用于调试3.2 实现 LLM 服务层接下来在app/services/llm_service.py中创建服务类负责与上游 AI API 对话。这里以兼容 OpenAI 格式的 API 为例。# app/services/llm_service.py import httpx import json from app.config import settings from app.schemas.models import ChatRequest, ProcessedLLMResponse, ThinkingContent from typing import AsyncGenerator, List, Optional import logging logger logging.getLogger(__name__) class LLMService: def __init__(self): self.api_key settings.openai_api_key or settings.deepseek_api_key self.api_base settings.llm_api_base.rstrip(/) self.model settings.llm_model self.client httpx.AsyncClient(timeout30.0) async def chat_completion(self, chat_request: ChatRequest) - ProcessedLLMResponse: 调用 LLM API并处理返回的思考内容。 headers { Authorization: fBearer {self.api_key}, Content-Type: application/json } # 构建请求体这里可以启用模型的思考功能如果API支持 # 例如对于OpenAI可能需要特定参数。我们假设API返回的thinking在content中。 payload { model: self.model, messages: [msg.dict() for msg in chat_request.messages], stream: chat_request.stream, } # 某些API可能需要额外参数来开启思考例如 reasoning_effort: medium (OpenAI o1) # 这里根据实际API文档调整。 try: response await self.client.post( f{self.api_base}/chat/completions, headersheaders, jsonpayload ) response.raise_for_status() data response.json() # 核心处理逻辑解析响应分离思考与最终回复 processed_response self._process_api_response(data) return processed_response except httpx.HTTPStatusError as e: logger.error(fAPI请求失败状态码: {e.response.status_code}, 响应: {e.response.text}) raise Exception(f模型服务调用失败: {e}) except Exception as e: logger.error(f处理API响应时发生未知错误: {e}) raise def _process_api_response(self, api_response: dict) - ProcessedLLMResponse: 解析AI API的响应提取思考内容和最终回复。 这是本文的核心函数需要根据实际API的响应格式进行调整。 choices api_response.get(choices, []) if not choices: raise ValueError(API响应中未找到有效choices) message choices[0].get(message, {}) content message.get(content, ) final_reply content full_thinking [] thinking_summary None # **场景一思考内容内嵌在 content 字段中如特定标记** # 假设思考部分被包裹在 thinking.../thinking 标签内这是一种常见模式 import re thinking_pattern rthinking(.*?)/thinking matches re.findall(thinking_pattern, content, re.DOTALL) if matches: # 找到思考内容 for think_text in matches: full_thinking.append(ThinkingContent(textthink_text.strip())) # 从 content 中移除思考标签得到最终回复 final_reply re.sub(thinking_pattern, , content, flagsre.DOTALL).strip() # 生成一个简单的摘要例如取第一段或前N个字符 if full_thinking: raw_think full_thinking[0].text thinking_summary raw_think[:150] (... if len(raw_think) 150 else ) else: # **场景二思考内容在独立的字段中如 reasoning_content, thinking** # 例如某些API可能在 message 中有 reasoning_content 或 thinking 字段 reasoning message.get(reasoning_content) or message.get(thinking) if reasoning: full_thinking.append(ThinkingContent(textreasoning)) thinking_summary reasoning[:150] (... if len(reasoning) 150 else ) # 最终回复就是 content final_reply content # **场景三工具调用tool_calls中的思考** # 如果 message 中有 tool_calls其中可能包含推理信息 tool_calls message.get(tool_calls) if tool_calls: for tool in tool_calls: # 假设 function.arguments 或某个元数据包含思考 # 这里需要根据具体API定义解析 func_args tool.get(function, {}).get(arguments) if func_args and reasoning in func_args.lower(): # 简单示例实际需要更复杂的JSON解析 full_thinking.append(ThinkingContent(textf工具调用思考: {func_args[:100]}..., typetool_call)) # 如果经过以上解析思考列表还是空的可以尝试从 content 中提取非结构化推理 # 例如查找以“让我们思考一下”、“首先”等开头的段落启发式方法不精确 if not full_thinking and len(content) 300: # 这是一个非常简单的启发式规则实际项目需要更鲁棒的方法 lines content.split(\n) possible_thinking [l for l in lines if l.startswith((首先, 第一步, 我认为, 分析:, 推理:))] if possible_thinking: thinking_summary ; .join(possible_thinking)[:120] ... # 注意这里不把启发式提取的内容作为 full_thinking因为不准确 return ProcessedLLMResponse( final_replyfinal_reply, thinking_summarythinking_summary, full_thinkingfull_thinking if full_thinking else None, raw_responseapi_response # 调试用生产环境建议去掉 ) async def close(self): await self.client.aclose()关键点解释_process_api_response方法是核心它尝试从三种可能的 API 响应格式中提取思考内容。实际项目中你必须根据你所使用的具体模型 API 的文档来调整这里的解析逻辑。我们使用正则表达式匹配thinking标签作为示例这是一种常见的让模型输出结构化思考的方式通过系统提示词引导。对于tool_calls的处理较为复杂需要根据工具调用的具体定义来解析。生成了一个简单的thinking_summary取前150字符用于在前端折叠按钮上显示。保留了raw_response用于调试但在生产环境中应考虑移除或记录到日志避免敏感信息泄露。3.3 创建 FastAPI 主应用现在在app/main.py中创建 API 端点。# app/main.py from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles import os from app.schemas.models import ChatRequest from app.services.llm_service import LLMService import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) app FastAPI(titlePi Thinking Collapse API) # 允许前端跨域访问如果前端在不同端口 app.add_middleware( CORSMiddleware, allow_origins[*], # 生产环境应指定具体来源 allow_credentialsTrue, allow_methods[*], allow_headers[*], ) # 挂载静态文件目录用于服务前端页面 app.mount(/static, StaticFiles(directorystatic), namestatic) llm_service LLMService() app.get(/, response_classHTMLResponse) async def read_root(): 返回前端页面 index_path os.path.join(static, index.html) if os.path.exists(index_path): with open(index_path, r, encodingutf-8) as f: return HTMLResponse(contentf.read()) raise HTTPException(status_code404, detail前端页面未找到) app.post(/api/chat) async def chat_with_llm(chat_request: ChatRequest): 主要聊天端点。接收用户消息转发给LLM并返回处理后的响应包含折叠的思考。 try: logger.info(f收到聊天请求消息数: {len(chat_request.messages)}) processed_response await llm_service.chat_completion(chat_request) # 生产环境中考虑不将 raw_response 返回给前端 response_data processed_response.dict(exclude{raw_response}) return response_data except Exception as e: logger.exception(处理聊天请求时发生错误) raise HTTPException(status_code500, detailstr(e)) app.on_event(shutdown) async def shutdown_event(): await llm_service.close()3.4 运行后端服务在项目根目录创建一个run.py文件来启动服务# run.py import uvicorn if __name__ __main__: uvicorn.run(app.main:app, host0.0.0.0, port8000, reloadTrue)运行python run.py后端服务将在http://localhost:8000启动。访问http://localhost:8000应该能看到前端页面待构建http://localhost:8000/docs可以看到自动生成的 API 文档。4. 构建前端实现思考内容的折叠与展示前端需要向后端/api/chat发送请求并优雅地展示处理后的响应默认只显示final_reply同时提供一个可点击的按钮来展开/折叠thinking_summary和full_thinking。4.1 创建前端页面 (static/index.html)!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titlePi - 思考折叠演示/title style * { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); padding: 30px; } h1 { color: #333; margin-bottom: 20px; } .chat-box { border: 1px solid #ddd; border-radius: 8px; height: 400px; overflow-y: auto; padding: 15px; margin-bottom: 20px; background-color: #fafafa; } .message { margin-bottom: 15px; padding: 12px 16px; border-radius: 18px; max-width: 80%; clear: both; } .user-message { background-color: #007AFF; color: white; float: right; } .assistant-message { background-color: #E8E8ED; color: black; float: left; } .thinking-section { background-color: #FFF3CD; border-left: 4px solid #FFC107; margin-top: 10px; padding: 10px 15px; border-radius: 0 8px 8px 0; font-size: 0.9em; color: #856404; } .thinking-toggle { background: none; border: 1px solid #CCC; border-radius: 4px; padding: 4px 10px; font-size: 0.8em; cursor: pointer; color: #666; margin-top: 8px; } .thinking-toggle:hover { background-color: #EEE; } .thinking-detail { margin-top: 10px; padding: 10px; background-color: #F8F9FA; border: 1px dashed #CCC; border-radius: 4px; white-space: pre-wrap; font-family: monospace; font-size: 0.85em; max-height: 300px; overflow-y: auto; } .input-area { display: flex; gap: 10px; } #userInput { flex-grow: 1; padding: 12px; border: 1px solid #CCC; border-radius: 8px; font-size: 16px; } #sendButton { padding: 12px 24px; background-color: #007AFF; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; } #sendButton:disabled { background-color: #CCC; cursor: not-allowed; } .error { color: #DC3545; padding: 10px; background-color: #F8D7DA; border-radius: 4px; margin-top: 10px; } /style /head body div classcontainer h1 Pi 助手思考折叠演示/h1 p输入你的问题助手会在后台思考并将思考过程折叠起来只显示最终答案。/p div classchat-box idchatBox !-- 消息会动态添加到这里 -- div classmessage assistant-message 你好我是 Pi 助手。我会在回答前进行思考但思考过程默认是折叠的。你可以点击“查看思考”按钮来了解我的推理过程。 /div /div div classinput-area input typetext iduserInput placeholder输入你的问题... autocompleteoff button idsendButton onclicksendMessage()发送/button /div div iderrorArea/div /div script src/static/script.js/script /body /html4.2 创建前端逻辑 (static/script.js)// static/script.js const chatBox document.getElementById(chatBox); const userInput document.getElementById(userInput); const sendButton document.getElementById(sendButton); const errorArea document.getElementById(errorArea); // 添加用户消息到聊天框 function addUserMessage(text) { const messageDiv document.createElement(div); messageDiv.className message user-message; messageDiv.textContent text; chatBox.appendChild(messageDiv); chatBox.scrollTop chatBox.scrollHeight; // 滚动到底部 } // 添加助手消息到聊天框包含思考折叠功能 function addAssistantMessage(finalReply, thinkingSummary, fullThinking) { const messageDiv document.createElement(div); messageDiv.className message assistant-message; // 最终回复 const replySpan document.createElement(div); replySpan.textContent finalReply; messageDiv.appendChild(replySpan); // 如果有思考摘要添加折叠按钮和区域 if (thinkingSummary) { const thinkingSection document.createElement(div); thinkingSection.className thinking-section; const toggleButton document.createElement(button); toggleButton.className thinking-toggle; toggleButton.textContent 查看思考过程; toggleButton.onclick function() { const detail this.nextElementSibling; if (detail.style.display none || !detail.style.display) { detail.style.display block; this.textContent 隐藏思考过程; } else { detail.style.display none; this.textContent 查看思考过程; } }; const thinkingDetail document.createElement(div); thinkingDetail.className thinking-detail; thinkingDetail.style.display none; // 默认隐藏 // 构建详细的思考内容显示 let detailHtml strong思考摘要:/strong ${thinkingSummary}\n\n; if (fullThinking fullThinking.length 0) { detailHtml strong完整思考过程:/strong\n; fullThinking.forEach((think, idx) { detailHtml [${think.type || step}] ${think.text}\n\n; }); } else { detailHtml 无更详细的思考内容记录。; } thinkingDetail.textContent detailHtml; thinkingSection.appendChild(toggleButton); thinkingSection.appendChild(thinkingDetail); messageDiv.appendChild(thinkingSection); } chatBox.appendChild(messageDiv); chatBox.scrollTop chatBox.scrollHeight; } // 显示错误信息 function showError(message) { errorArea.innerHTML div classerror错误: ${message}/div; setTimeout(() { errorArea.innerHTML ; }, 5000); } // 发送消息到后端 async function sendMessage() { const text userInput.value.trim(); if (!text) return; // 禁用输入和按钮防止重复发送 userInput.disabled true; sendButton.disabled true; const originalButtonText sendButton.textContent; sendButton.textContent 思考中...; // 添加用户消息到界面 addUserMessage(text); userInput.value ; // 清空输入框 try { // 构建请求体 const messages [{ role: user, content: text }]; const requestBody { messages: messages, stream: false }; const response await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(requestBody) }); if (!response.ok) { const errorData await response.json(); throw new Error(errorData.detail || 请求失败: ${response.status}); } const data await response.json(); // 调用函数添加助手消息并传入思考内容 addAssistantMessage(data.final_reply, data.thinking_summary, data.full_thinking); } catch (error) { console.error(发送消息失败:, error); showError(error.message); // 可以添加一个错误消息到聊天框 const errorMsgDiv document.createElement(div); errorMsgDiv.className message assistant-message; errorMsgDiv.textContent 抱歉处理您的请求时出错了: ${error.message}; chatBox.appendChild(errorMsgDiv); } finally { // 重新启用输入和按钮 userInput.disabled false; sendButton.disabled false; sendButton.textContent originalButtonText; userInput.focus(); } } // 支持按 Enter 键发送 userInput.addEventListener(keypress, function(event) { if (event.key Enter) { sendMessage(); } });前端关键点解释addAssistantMessage函数接收后端处理好的final_reply,thinking_summary和full_thinking。如果存在thinking_summary则创建一个折叠区域。默认只显示一个按钮“查看思考过程”。点击按钮时通过切换thinking-detail元素的display属性来展开或隐藏详细的思考内容。详细的思考内容将thinking_summary和full_thinking列表格式化后显示。整个交互是无刷新SPA风格的用户体验流畅。5. 运行验证与效果测试现在整个系统已经可以运行测试了。启动后端在终端中确保在虚拟环境下运行python run.py。看到Uvicorn running on http://0.0.0.0:8000即表示成功。访问前端打开浏览器访问http://localhost:8000。模拟 API 响应由于我们还没有配置真实的 AI API 密钥或者真实 API 可能不返回我们预设的思考格式我们需要先模拟一个成功的响应来测试前端折叠功能。修改app/services/llm_service.py中的chat_completion方法在开发初期可以添加一个模拟返回。# 在 llm_service.py 的 chat_completion 方法开始处添加开发模拟 async def chat_completion(self, chat_request: ChatRequest) - ProcessedLLMResponse: # --- 开发模拟用于测试前端不调用真实API --- if os.getenv(ENV) development: logger.info(使用模拟响应) # 模拟一个包含思考标签的响应 mock_content thinking 用户问的是“如何给Pi实现思考折叠”。我需要拆解这个问题。 1. 首先Pi 可能是一个AI助手项目。 2. “思考折叠”指的是将AI的内部推理过程隐藏只显示最终答案。 3. 实现方案需要分后端和前端。 4. 后端需要解析API响应分离思考内容和最终回复。 5. 前端需要将思考内容折叠显示并提供展开按钮。 好的思考完毕现在组织最终答案。 /thinking 要实现给 Pi 助手添加思考折叠功能主要分为后端处理和前端展示两部分。 **后端处理** 1. 在与AI模型如OpenAI、DeepSeek的API交互层解析返回的响应。 2. 如果响应中包含思考内容可能位于特定字段如 reasoning_content或被 thinking 标签包裹将其提取出来。 3. 对提取的思考内容生成一个简短摘要例如前150个字符。 4. 将最终回复、思考摘要和完整的思考内容一起返回给前端。 **前端展示** 1. 默认只显示最终回复。 2. 如果存在思考摘要在回复下方显示一个“查看思考”按钮。 3. 用户点击按钮后展开一个区域显示思考摘要和完整的思考过程。 这样既保证了对话的简洁性又为感兴趣的用户或开发者提供了查看详细推理过程的途径。 # 直接调用处理函数处理这个模拟内容 import re final_reply re.sub(rthinking.*?/thinking, , mock_content, flagsre.DOTALL).strip() full_thinking [] thinking_matches re.findall(rthinking(.*?)/thinking, mock_content, re.DOTALL) for think in thinking_matches: full_thinking.append(ThinkingContent(textthink.strip())) thinking_summary full_thinking[0].text[:150] ... if full_thinking else None return ProcessedLLMResponse( final_replyfinal_reply, thinking_summarythinking_summary, full_thinkingfull_thinking, raw_response{mock: True} ) # --- 模拟结束 --- # ... 原有的真实API调用代码 ...记得在文件顶部import os。并设置环境变量ENVdevelopment或在.env文件中设置。测试交互在浏览器前端输入框输入“如何给Pi实现思考折叠”点击发送。你应该能看到助手返回的最终答案并且在答案下方有一个“查看思考过程”按钮。点击按钮可以展开看到模拟的思考内容。6. 接入真实 AI API 与配置调整测试通过后下一步是接入真实的 AI 服务。你需要根据所选服务的 API 文档调整_process_api_response方法。6.1 以 OpenAI (GPT-4o) 为例OpenAI 的某些模型如o1-preview在响应中可能包含reasoning_content字段。你需要调整解析逻辑# 在 _process_api_response 方法中强化场景二的解析 reasoning message.get(reasoning_content) if reasoning: full_thinking.append(ThinkingContent(textreasoning, typereasoning)) thinking_summary reasoning[:150] (... if len(reasoning) 150 else ) # 注意最终回复可能在 content 字段也可能 reasoning_content 就是全部输出 # 根据API文档确认。有时 content 为空答案就在 reasoning_content 末尾。 final_reply content if content else self._extract_final_from_reasoning(reasoning)6.2 以 DeepSeek 或其他兼容 API 为例你需要查阅对应模型的 API 文档看思考内容以何种形式返回。可能是一个独立的thinking字段。在tool_calls的function.arguments中。或者你需要通过系统提示词来引导模型用特定格式如thinking.../thinking输出思考然后使用我们示例中的正则表达式来提取。这是最灵活、最通用的方法。系统提示词示例你是一个乐于助人的AI助手。在回答用户问题时请先在一个单独的、用XML标签 thinking 和 /thinking 包裹的段落中进行你的内部推理。推理完成后在思考标签外给出最终答案。 例如 用户天空为什么是蓝色的 thinking 这是一个关于物理光学的问题。需要解释瑞利散射。太阳光由不同波长的光组成蓝光波长短更容易被大气分子散射使得我们看到的天空呈蓝色。 /thinking 最终答案是天空呈现蓝色主要是因为瑞利散射。太阳光中波长较短的蓝光比波长较长的红光更容易被大气中的分子散射因此我们看到的天空是蓝色的。 请严格按照此格式回复。然后在_process_api_response中使用正则表达式提取thinking标签内的内容。6.3 配置与安全API 密钥管理永远不要将密钥硬编码在代码中或提交到版本库。使用.env文件并通过python-dotenv或pydantic-settings加载。错误处理真实环境中网络波动、API 限流、令牌超限都会发生。需要在llm_service.py中增加更健壮的错误处理和重试逻辑。超时设置根据模型复杂度合理设置httpx.AsyncClient的超时时间。生产环境部署使用gunicorn或uvicorn配合多进程部署设置反向代理如 Nginx并配置 HTTPS。7. 常见问题排查在实现和运行过程中你可能会遇到以下问题问题现象可能原因检查方式处理建议前端点击发送后无反应控制台报跨域错误后端 CORS 配置不正确或前端请求地址错误1. 检查浏览器开发者工具 Console 和 Network 标签。2. 确认后端app.add_middleware(CORSMiddleware)已正确配置。1. 确保前端请求的 URL 正确如http://localhost:8000/api/chat。2. 生产环境将allow_origins[*]替换为具体的前端域名。后端返回错误422 Unprocessable Entity请求体数据格式不符合ChatRequestPydantic 模型定义1. 查看 FastAPI 自动文档/docs中的请求体示例。2. 对比前端fetch请求中body的结构。确保前端发送的 JSON 结构为{“messages”: [{“role”: “user”, “content”: “...”}], “stream”: false}。无法接收到思考内容thinking_summary总是null1. 真实 API 未返回思考内容。2. 解析逻辑与 API 实际响应格式不匹配。3. 系统提示词未生效。1. 在后端日志中打印raw_response调试阶段。2. 仔细对比 API 文档和你的解析代码。3. 检查发送给 API 的messages是否包含正确的系统提示词。1. 确认你使用的模型是否支持返回思考内容。2. 根据raw_response调整_process_api_response解析逻辑。3. 强化系统提示词明确要求模型用特定格式输出思考。思考内容被当作最终回复的一部分显示出来了解析逻辑未能正确移除思考内容标签或字段。检查final_reply变量的内容是否还包含thinking标签或其他思考文本。确保正则表达式匹配模式正确如使用re.DOTALL标志跨行匹配并且替换操作成功。前端折叠按钮点击后无法展开/收起JavaScript 事件绑定失败或 DOM 操作逻辑错误。1. 检查浏览器 Console 是否有 JS 错误。2. 检查thinking-detail元素的display样式是否被正确切换。确保onclick事件正确绑定到了新创建的元素上。使用console.log调试toggleButton.onclick函数。服务端报错httpx.ConnectError无法连接到配置的LLM_API_BASE。检查.env文件中的LLM_API_BASE和网络连通性。确认 API 地址正确且服务器可以访问外部网络如果需要。对于某些本地模型地址可能是http://localhost:11434/v1。8. 最佳实践与扩展方向8.1 后端最佳实践解析逻辑抽象与插件化不同的模型 API 返回思考的格式差异很大。建议将_process_api_response方法抽象成一系列解析器Parser根据model字段或配置动态选择。这符合开闭原则便于未来接入新模型。思考内容存储对于重要的对话尤其是涉及关键决策的应将完整的思考内容与最终回复一起存储到数据库如 PostgreSQL、MongoDB中而不仅仅是返回给前端。这便于后续的审计、分析和模型优化。流式响应支持本文示例是非流式stream: false。对于更好的用户体验应支持 Server-Sent Events (SSE) 流式输出。这需要后端能够逐块chunk解析思考内容和最终回复并实时推送给前端。复杂度会显著增加。缓存与限流对相同的用户问题可以考虑缓存处理后的结果。同时要对/api/chat端点实施限流防止滥用。8.2 前端最佳实践更优雅的折叠动画使用 CSSmax-height和transition属性实现平滑的展开/收起动画提升用户体验。思考内容格式化如果思考内容是 Markdown 或 JSON可以在展开的区域使用相应的渲染器如marked库进行美化展示。复制与分享在思考内容区域添加“复制”按钮方便用户将推理过程分享给他人或用于调试。本地存储将对话历史包括折叠状态存储在浏览器的localStorage中刷新页面后可以恢复。8.3 扩展方向多轮对话的思考关联在连续对话中将上一轮的思考摘要作为上下文的一部分传递给模型可能使模型的推理更具连贯性。思考内容分析在后端对提取的思考内容进行分析例如识别其使用的推理步骤、调用的工具、存在的逻辑漏洞等为模型优化提供数据支持。用户控制提供用户设置允许用户选择“始终显示思考”、“始终折叠”或“针对复杂问题显示思考”等模式。集成到现有项目本文的代码模块化程度较高。你可以将LLMService和相关的schemas轻松集成到现有的 FastAPI、Django 或 Flask 项目中为你的“Pi”助手或其他 AI 应用添加思考折叠功能。通过以上步骤我们完成了一个从概念到实现、从后端到前端的完整“思考折叠”功能。它不仅改善了用户体验也为开发者保留了宝贵的模型内部工作过程是构建透明、可信且交互友好的 AI 应用的重要一环。

相关新闻

ROS1到ROS2数据迁移:rosbag_v2工具实现历史bag包无缝回放

ROS1到ROS2数据迁移:rosbag_v2工具实现历史bag包无缝回放

1. 项目概述:跨越ROS版本的数据回放挑战 如果你是从ROS1“古早”版本一路摸爬滚打过来的机器人开发者,手头肯定攒了一堆宝贵的 .bag 数据文件。这些bag包可能是当年调试SLAM算法时,小车在实验室里磕磕绊绊跑了几十圈才录下来的点云和里程计…

2026/8/12 21:56:34 阅读更多 →
腾讯云Marvis深度体验:AI助手如何重塑云原生开发与运维效率

腾讯云Marvis深度体验:AI助手如何重塑云原生开发与运维效率

1. 从“试了下”到“回不去”:一次深度体验的必然那天下午,纯粹是出于对“AI助手”这个泛滥概念下新面孔的好奇,我点开了腾讯云官网上那个名为“Marvis”的入口。坦白说,起初没抱太大期望,市面上类似的工具太多了&…

2026/8/12 21:56:34 阅读更多 →
基于多模型AI的面试刷题系统:架构设计与工程实践

基于多模型AI的面试刷题系统:架构设计与工程实践

1. 项目概述:一个AI驱动的面试刷题伴侣最近在准备技术面试,尤其是前端和全栈岗位,刷LeetCode、牛客网成了日常。但刷题有个痛点:题目做完了,只能看个“通过/不通过”,代码质量怎么样、有没有更好的解法、面…

2026/8/12 21:55:34 阅读更多 →

最新新闻

技术揭秘:Umi-OCR双层PDF功能如何解决扫描文档不可编辑的痛点

技术揭秘:Umi-OCR双层PDF功能如何解决扫描文档不可编辑的痛点

技术揭秘:Umi-OCR双层PDF功能如何解决扫描文档不可编辑的痛点 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。内置…

2026/8/12 23:47:02 阅读更多 →
如何用SeedVR2视频放大工具:让模糊视频秒变4K的AI黑科技

如何用SeedVR2视频放大工具:让模糊视频秒变4K的AI黑科技

如何用SeedVR2视频放大工具:让模糊视频秒变4K的AI黑科技 【免费下载链接】ComfyUI-SeedVR2_VideoUpscaler Official SeedVR2 Video Upscaler for ComfyUI 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-SeedVR2_VideoUpscaler 还在为模糊视频发愁吗&…

2026/8/12 23:47:02 阅读更多 →
5个技巧教你掌握ESP-IDF构建系统:从新手到专家

5个技巧教你掌握ESP-IDF构建系统:从新手到专家

5个技巧教你掌握ESP-IDF构建系统:从新手到专家 【免费下载链接】esp-idf Espressif IoT Development Framework. Official development framework for Espressif SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf ESP-IDF(Espres…

2026/8/12 23:47:02 阅读更多 →
nodejs-polars数据可视化教程:使用DataFrame创建交互式图表

nodejs-polars数据可视化教程:使用DataFrame创建交互式图表

nodejs-polars数据可视化教程:使用DataFrame创建交互式图表 【免费下载链接】nodejs-polars nodejs front-end of polars 项目地址: https://gitcode.com/gh_mirrors/no/nodejs-polars nodejs-polars是一个强大的Node.js前端数据处理库,它提供了高…

2026/8/12 23:47:02 阅读更多 →
终极沉浸式双语翻译扩展:如何轻松打破语言壁垒

终极沉浸式双语翻译扩展:如何轻松打破语言壁垒

终极沉浸式双语翻译扩展:如何轻松打破语言壁垒 【免费下载链接】immersive-translate 沉浸式双语网页翻译扩展 , 支持输入框翻译, 鼠标悬停翻译, PDF, Epub, 字幕文件, TXT 文件翻译 - Immersive Dual Web Page Translation Extension 项目…

2026/8/12 23:47:02 阅读更多 →
如何高效解决Android设备验证问题:Play Integrity Fix的完整解决方案

如何高效解决Android设备验证问题:Play Integrity Fix的完整解决方案

如何高效解决Android设备验证问题:Play Integrity Fix的完整解决方案 【免费下载链接】PlayIntegrityFix Fix Play Integrity (and SafetyNet) verdicts. 项目地址: https://gitcode.com/GitHub_Trending/pl/PlayIntegrityFix 您是否遇到过Root设备后&#x…

2026/8/12 23:46:02 阅读更多 →

日新闻

Ubuntu 22.04安装与使用tree命令:高效管理Linux目录结构

Ubuntu 22.04安装与使用tree命令:高效管理Linux目录结构

1. 为什么需要一个“目录树”工具?在Linux世界里,尤其是Ubuntu这样的发行版,命令行是很多人的主战场。我们每天都要和文件、目录打交道。ls命令是查看目录内容的首选,它简洁、高效,能列出文件名、权限、大小等关键信息…

2026/8/12 9:33:34 阅读更多 →
博思AI智能体:意图识别、思考链与性能优化的工程实践

博思AI智能体:意图识别、思考链与性能优化的工程实践

在AI应用从“能用”走向“好用”的进程中,系统的响应速度、决策透明度与高并发稳定性是决定用户体验的关键。博思AI智能体近期完成了一次重要的专项优化,聚焦于意图识别、思考链展示与全链路压测三大核心领域,将系统从功能实现推向了工程卓越…

2026/8/12 9:33:34 阅读更多 →
子代理架构:AI智能体任务分解与协同执行的核心原理与实践

子代理架构:AI智能体任务分解与协同执行的核心原理与实践

1. 项目概述:为什么我们需要“子代理”?最近在折腾各种AI应用和自动化流程时,我越来越频繁地遇到一个瓶颈:单个AI智能体(Agent)的能力边界。无论是处理复杂的多步骤任务,还是需要同时调用多个专…

2026/8/12 9:33:34 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/12 1:11:09 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/12 1:11:09 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/12 1:11:08 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/11 17:09:45 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/12 1:11:10 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/11 17:09:45 阅读更多 →