
1. 项目概述从“黑盒”到“白盒”的AI对话体验最近在折腾AI应用开发的朋友可能都绕不开一个核心痛点我们调用的大模型API返回的往往是一个“最终答案”。模型内部是如何一步步推理出这个结论的它考虑了哪些因素又排除了哪些可能性这个过程对我们来说完全是个“黑盒”。这对于调试、教学、或者构建需要高度可靠性和可解释性的AI Agent来说是个巨大的障碍。“手撸AI对话助手带上思考过程”这个项目就是为了捅破这层窗户纸。它的目标不是简单地封装一个ChatGPT的API调用而是要实现一个能将其内部“思考链条”完整暴露出来的对话系统。你可以把它理解为一个“透明”的AI助手它不仅告诉你答案还把它“解题”的草稿纸一并给你看。这在当前AI应用开发特别是需要构建复杂工作流、进行逻辑验证或教育演示的场景下价值巨大。无论是想深入理解大模型推理逻辑的开发者还是希望AI助手能提供更可信、可追溯回答的终端用户这个项目都能提供一个清晰的实现路径和深度定制的可能。2. 核心设计思路拆解“思考”的两种主流路径要实现思考过程的可视化核心在于如何引导和捕获模型的中问推理步骤。目前主流有两种设计范式它们各有优劣适用于不同的场景。2.1 路径一提示工程驱动链式思考CoT这是最经典也最易于上手的方法。其核心思想不依赖于复杂的工程框架而是通过精心设计的提示词Prompt强制要求模型按步骤输出。实现原理我们在给模型的系统指令System Prompt和用户问题User Prompt中明确要求模型以特定的格式进行输出。例如我们可以规定模型必须按以下结构回复[思考过程] 1. 首先我需要理解用户的问题是关于... 2. 接着我需要查找或回忆相关知识比如... 3. 然后我对比了A方案和B方案的优缺点... 4. 基于以上分析我的判断是... [最终答案] 因此我的建议是...通过这种强制的输出格式模型会在生成最终答案前先“自言自语”地完成一套推理步骤。这种方法的好处是零依赖只需要一个能接受文本输入输出的大模型API如GPT-4、Claude、国产深度求索等即可开发成本极低。实操要点与局限提示词设计是关键你需要反复调试提示词确保模型能稳定地遵循你设定的格式。对于复杂任务可能需要引入“少样本示例”Few-Shot Learning在提示词中给出几个按格式回答的例子效果会好很多。思考过程可能“造假”需要明确一点这种方式展示的“思考过程”是模型根据指令“生成”的文本它可能并不完全反映模型内部真实的计算路径更像是一种“事后解释”。但对于大多数需要逻辑展示的应用来说这已经足够。输出解析是必要步骤后端收到模型的回复后需要用正则表达式或简单的字符串分割方法将[思考过程]和[最终答案]两部分解析出来再分别呈现给前端。2.2 路径二框架赋能AI Agent与规划执行框架当任务变得非常复杂涉及多步骤、多工具调用时简单的链式思考提示可能不够用。这时就需要引入AI Agent的概念使用专门的框架来管理“思考”过程。实现原理以LangChain、LlamaIndex等框架为例它们提供了“Agent”和“Tools”的抽象。你可以定义一个Agent助手并为它配备一系列Tools工具如计算器、搜索引擎API、数据库查询等。当用户提出复杂问题时框架会驱动Agent执行一个“规划-执行-观察”的循环规划Agent根据当前目标和历史决定下一步该做什么调用哪个工具或直接给出答案。执行调用相应的Tool并传入参数。观察获取Tool的执行结果。循环结合观察结果再次规划直到任务完成。在这个过程中框架会完整地记录下每一步的“规划”即思考“我现在应该去用计算器算一下”、“执行”和“观察”。这个记录就是最天然、最结构化的“思考过程”。方案选型考量选择提示工程如果你的需求是让对话助手对单一问题给出带有推理步骤的回答且不希望引入额外框架依赖那么提示工程是最快、最直接的方案。它轻量、灵活适合快速验证想法或集成到现有简单系统中。选择Agent框架如果你的目标是构建一个能处理复杂任务如“帮我查一下今天北京的天气然后根据天气推荐室内外活动并估算一下活动预算”、需要自主调用外部API或知识的智能体那么就必须使用Agent框架。它能提供真正具有行动力的“思考”和“执行”过程可解释性更强但架构也更复杂。对于本项目“手撸”的定位我们将重点深入第一种方案因为它更能体现从零构建的“手撸”精神并能覆盖最广泛的场景。第二种方案更多是框架的使用我们会在高级部分探讨如何将其思考过程进行定制化展示。3. 手把手实现基于提示工程的透明对话助手我们以一个“决策助手”为例构建一个能展示思考过程的对话系统。技术栈选择最常见的Python FastAPI后端 任意大模型API如OpenAI、智谱AI、DeepSeek等 简单前端如HTML/JS或Streamlit。3.1 后端核心提示词设计与API封装后端的核心任务是接收用户问题拼接带有强制思考格式的提示词调用大模型API然后解析返回结果。第一步设计系统提示词这是决定思考过程质量的核心。一个好的系统提示词需要明确身份、规则和格式。system_prompt 你是一个专业的决策分析助手。你的任务是帮助用户分析问题并做出决策。 你必须严格按照以下格式输出你的回答 [思考过程] 请在这里逐步写下你的分析推理逻辑。例如 1. 澄清问题确认用户的核心需求是什么。 2. 因素分析列出影响决策的所有关键因素。 3. 信息评估评估现有信息的充分性指出缺失部分。 4. 方案推演基于因素推导出可能的解决方案。 5. 优劣对比分析每个方案的优点和风险。 思考步骤可根据具体问题调整但必须清晰分点 [最终答案] 请在这里给出清晰、直接、基于以上分析的最终建议或答案。 格式要求最终答案必须简洁不超过200字。 记住无论如何你的输出必须包含且仅包含“[思考过程]”和“[最终答案]”这两个部分。 第二步构建请求与解析响应我们以OpenAI API兼容格式为例展示核心代码逻辑。import openai import re class ThinkingChatAssistant: def __init__(self, api_key, modelgpt-3.5-turbo): self.client openai.OpenAI(api_keyapi_key) self.model model self.system_prompt system_prompt # 上述定义的系统提示词 def get_response(self, user_query): # 构建消息列表 messages [ {role: system, content: self.system_prompt}, {role: user, content: user_query} ] try: response self.client.chat.completions.create( modelself.model, messagesmessages, temperature0.7, # 适当温度保证一定创造性但不过于随机 max_tokens1500 # 预留足够token给思考过程 ) full_response response.choices[0].message.content # 解析思考过程和最终答案 thought_process, final_answer self._parse_response(full_response) return { thought_process: thought_process, final_answer: final_answer, raw_response: full_response # 备用用于调试 } except Exception as e: return {error: str(e)} def _parse_response(self, text): # 使用正则表达式匹配两部分内容 thought_pattern r\[思考过程\]\s*(.*?)\s*(?\[最终答案\]|$) answer_pattern r\[最终答案\]\s*(.*) thought_match re.search(thought_pattern, text, re.DOTALL) answer_match re.search(answer_pattern, text, re.DOTALL) thought thought_match.group(1).strip() if thought_match else 未能解析出思考过程。 answer answer_match.group(1).strip() if answer_match else 未能解析出最终答案。 return thought, answer关键参数解析temperature设置为0.7是一个平衡点。太低如0.2会导致思考过程过于模板化、枯燥太高如1.0可能导致思考过程散漫甚至不遵循格式。对于需要严谨推理的决策类问题建议在0.5-0.8之间调整。max_tokens必须设置足够大。思考过程会消耗大量token。如果设置过小模型输出会被截断导致解析失败。根据问题复杂度通常需要1024以上复杂问题可设为2000或更高。3.2 前端展示让思考过程一目了然后端的API返回结构化的数据后前端需要以友好的方式展示。我们可以用简单的HTML/JS或Streamlit快速实现。Streamlit示例极简import streamlit as st from backend import ThinkingChatAssistant # 导入我们刚才写的类 st.title( 透明思考AI助手) # 初始化助手 if assistant not in st.session_state: st.session_state.assistant ThinkingChatAssistant(api_keyyour_api_key) user_input st.chat_input(请输入您的问题...) if user_input: with st.spinner(AI正在思考中...): result st.session_state.assistant.get_response(user_input) if error not in result: # 展示思考过程用扩展框收纳保持界面整洁 with st.expander( 查看AI的思考过程, expandedFalse): st.markdown(result[thought_process]) # 突出展示最终答案 st.success( **最终答案**) st.markdown(result[final_answer]) else: st.error(f出错了{result[error]})这样用户提问后可以直接看到清晰的最终答案同时可以选择展开查看详细的思考步骤体验非常好。3.3 高级技巧让思考更可控、更深入基础的链式思考有时会流于表面。以下是几个提升思考质量的技巧1. 分阶段提示Two-Stage Prompting 不让模型一次性输出全部而是分两步走。第一步只要求它输出思考过程大纲或草稿第二步将用户问题和这个思考过程一起提交要求它给出最终答案。这能迫使模型进行更深入的“元思考”有时能产生更严谨的推理。实现上相当于进行两次API调用。2. 动态Few-Shot示例 根据用户问题的类型从示例库中动态选择最相关的几个示例思考过程答案插入到提示词中。例如用户问编程问题就插入编程推理示例问商业分析就插入商业分析示例。这能极大提高模型在特定领域遵循格式和推理模式的能力。3. 思考过程的后处理与评分 你可以引入一个“验证”步骤。用另一个更轻量的模型或同一模型的另一个调用对生成的思考过程进行逻辑一致性、完整性的评分甚至让其提出改进意见。这可以形成一个自我优化的循环虽然会增加成本但对于高可靠性应用是值得的。4. 避坑指南与常见问题排查在实际“手撸”过程中你会遇到一些典型问题。这里记录下我的踩坑实录和解决方案。4.1 问题一模型不遵循输出格式这是最常见的问题。你明确要求了格式但模型返回的文本可能没有“【思考过程】”标签或者把两部分混在一起。排查与解决检查系统提示词的权威性确保系统提示词是消息列表的第一条并且角色role是system。system角色的指令对模型约束力最强。强化格式描述在提示词中使用“必须”、“严格”、“只能”等强约束词汇并明确说明“不要输出任何其他内容”。可以尝试用XML标签如thought_process和/thought_process来包裹内容有时模型对XML标签更敏感。使用JSON格式要求这是目前最稳定的方法之一。要求模型直接返回一个JSON对象例如{thought_process: ..., final_answer: ...}。大多数新一代模型对JSON格式的理解和遵循能力非常强。OpenAI的API还支持response_format{ type: json_object }参数来强制JSON输出。降低Temperature尝试将temperature暂时调到0.1或0.2让模型输出更确定、更守规矩待格式稳定后再调回。4.2 问题二思考过程过于简略或空洞模型可能只用一句话敷衍思考过程如“用户需要X所以我提供了Y”。排查与解决在系统提示词中细化思考步骤不要只说“写出思考过程”。像我们之前的示例一样给出一个具体的思考框架如“1. 澄清问题 2. 因素分析...”。模型会倾向于模仿这个结构。在用户提问中加以引导对于特别复杂的问题可以在用户问题后追加一句“请务必详细拆解你的推理步骤每一步都解释清楚。”切换更强模型GPT-3.5-Turbo有时会在复杂推理上偷懒。如果条件允许换用GPT-4、Claude 3 Opus或DeepSeek-V2等更强模型它们的推理步骤通常更详尽、更高质量。设置最小长度惩罚某些API支持min_tokens或通过logit_bias等技术手段间接鼓励生成长文本但这属于高级技巧需谨慎使用。4.3 问题三解析函数失败正则表达式可能因为模型输出的细微变化如用了全角冒号“”和半角“:”或换行符差异而匹配失败。排查与解决增强解析鲁棒性编写更宽容的正则表达式。例如匹配“思考过程”时允许标签前后有空格允许使用中文或英文括号。# 更鲁棒的正则表达式 thought_pattern r\[?思考过程\]?\s*[:]?\s*(.*?)\s*(?\[?最终答案\]?\s*[:]|$)添加降级逻辑如果正则解析失败尝试用简单的字符串查找如find(“[思考过程]”)和分割如split(“[最终答案]”)。如果还失败则返回原始响应并标记解析异常而不是直接崩溃。记录原始响应务必像我们示例代码中那样保留raw_response。当解析失败时查看原始输出能帮你快速调整提示词或解析逻辑。4.4 关于成本与延迟的考量展示思考过程意味着模型要生成更多文本这会带来两方面影响成本消耗的Token数通常是普通问答的2-5倍API调用成本相应增加。需要对使用场景做权衡是否值得为“透明”付费。延迟生成更长的文本需要更多时间用户等待时间会变长。在前端设计时一定要有加载状态提示如“AI正在思考中…”的动画。一个优化策略是提供“简洁模式”和“详细模式”的开关。默认使用简洁模式不展示或展示简要思考当用户需要深究或调试时再手动开启详细模式。这样既能控制成本又能满足核心需求。5. 从“思考”到“行动”对接AI Agent框架如果你不满足于文本层面的思考展示希望助手能真正调用工具、执行动作那么将上述思路与LangChain等框架结合是自然的选择。5.1 捕获LangChain Agent的思考轨迹LangChain的Agent在执行过程中会生成完整的AgentAction和AgentFinish对象序列这就是最真实的“思考”记录。from langchain.agents import initialize_agent, AgentType from langchain.llms import OpenAI from langchain.tools import Tool # 假设我们有一个计算器和搜索工具 def calculator(query): # ... 实现计算逻辑 return result def search_web(query): # ... 实现搜索逻辑 return summary tools [ Tool(nameCalculator, funccalculator, description用于数学计算), Tool(nameWebSearch, funcsearch_web, description用于搜索最新信息), ] llm OpenAI(temperature0) agent initialize_agent(tools, llm, agentAgentType.ZERO_SHOT_REACT_DESCRIPTION, verboseTrue) # 关键verboseTrue 会在控制台打印出每一步的思考Thought、行动Action和观察Observation result agent.run(某明星的年龄加上20年后是多少岁)verboseTrue模式下控制台会输出Thought: 我需要先查出这位明星的年龄然后用计算器加上20。 Action: WebSearch Action Input: 某明星 年龄 Observation: 某明星今年30岁。 Thought: 我现在有了年龄需要计算3020。 Action: Calculator Action Input: 3020 Observation: 50 Thought: 我现在知道答案了。 Final Answer: 某明星的年龄加上20年后是50岁。你的任务就是捕获并解析这个输出流。LangChain提供了回调Callbacks机制你可以定义一个CustomCallbackHandler在on_agent_action和on_agent_finish等方法中将这些Thought、Action、Observation实时地推送到你的前端界面。这样用户就能看到一个真正在“思考-行动-观察”循环的智能体。5.2 构建自定义的“透明”Agent Executor对于更深入的需求你可以完全自己控制执行循环。伪代码如下class TransparentAgent: def run(self, query): thoughts [] # 用于记录所有思考步骤 while not task_finished: # 1. 规划让LLM根据当前状态思考下一步 thought llm.generate_thought(current_state, query) thoughts.append({step: think, content: thought}) # 2. 决策解析thought决定是调用工具还是结束 if should_call_tool(thought): tool_name, tool_input parse_tool_call(thought) thoughts.append({step: action, tool: tool_name, input: tool_input}) # 3. 执行 observation call_tool(tool_name, tool_input) thoughts.append({step: observation, content: observation}) # 更新状态 current_state observation else: final_answer parse_final_answer(thought) thoughts.append({step: answer, content: final_answer}) break return thoughts, final_answer这样你不仅拥有了思考过程还拥有了一个完全可控、每一步都可审计的执行流水线。这对于开发需要高度可靠性的AI应用如金融分析、法律咨询辅助至关重要。6. 安全、伦理与体验的边界在让AI展示思考过程的同时我们也必须设立一些边界。避免信息过载对于简单问题展示冗长的思考过程反而会干扰用户。需要设计智能的折叠/展开UI或让模型自我判断思考过程的必要长度。过滤敏感内容思考过程中模型可能会“想”出一些不恰当、偏见性或敏感的内容。在将思考过程呈现给用户前有必要进行一层内容安全过滤可以使用专门的 moderation API或设置关键词过滤规则。解释的局限性必须向用户说明所展示的“思考过程”是模型生成的文本解释而非其神经网络内部的实际信号传递。这有助于管理用户预期避免对AI产生不切实际的信任。性能监控记录每次交互的思考步骤长度、耗时和token消耗用于优化提示词和成本控制。手撸一个带思考过程的AI对话助手更像是在与大模型合作时主动为自己安装了一个“调试器”。它剥开了AI神秘的外衣让交互过程变得可追溯、可讨论、可改进。无论是用于教育演示、复杂任务分解还是提升AI应用本身的可靠性和用户信任度这项技术都提供了一个极具价值的切入点。从简单的提示词工程到复杂的Agent框架集成其核心思想一以贯之让不可见的计算变为可见的推理。