AI回答太冗长?我设计了三段式流式显示让信息层次分明

我是张大鹏,做了十多年人工智能,带过不少项目。说实话,最难的不是让AI生成正确的答案,是让答案以正确的方式呈现给用户 。最近Claude 3.7推出了extended thinking模式,OpenAI的o系列也在做类似的事情------让AI的推理过程可见。但我们在如意Agent里做这件事的时候,发现单纯的"显示思考过程"不够,用户真正需要的是信息分层


一、传统AI输出的痛点

用过ChatGPT或Claude的都知道,AI回答问题的典型流程是:

  1. 你提了一个复杂问题
  2. 屏幕开始滚动,AI滔滔不绝
  3. 你盯着屏幕看了30秒,还没看到重点
  4. 最后发现答案藏在第8段,前面全是推理过程

这种"一镜到底"的输出方式,在简单问答时没问题。但在多轮工具调用、复杂推理的场景下,体验很糟糕:

场景 问题 用户感受
工具调用 AI先思考用哪个工具,再调用,再分析结果,再决定下一步 "到底在干嘛?进度多少了?"
长推理 1000字的思考过程,最后结论只有50字 "前面这么多字,就为了说这么点事?"
多轮对话 历史上下文混在一起,新信息被淹没 "好像之前说过这个,是不是重复了?"

我们的核心需求很明确:让思考过程可见但不干扰,让结论突出但不突兀,让详细回答有序但不冗长。

二、三段式流式显示设计

基于上面的分析,我们设计了 thinking / summary / answer 三段式流式显示。

2.1 三段定义

段落 内容 显示策略 目的
thinking AI的推理过程、工具调用分析、策略调整 可折叠,默认收起 满足好奇心,不干扰阅读
summary 极简单行概括(<30字),包含关键结论 独立高亮显示 快速获取核心信息
answer 详细回答、代码示例、数据表格 主体展示 获取完整信息

2.2 消息协议设计

后端流式输出的消息格式:

python 复制代码
class MessageBlock(BaseModel):
    type: Literal["thinking", "summary", "answer"]
    content: str
    is_complete: bool = False

WebSocket推送时,每个block独立发送:

json 复制代码
{"type": "thinking", "content": "正在分析项目结构...", "is_complete": false}
{"type": "thinking", "content": "发现3个相关模块,优先检查handler层", "is_complete": true}
{"type": "summary", "content": "问题根源在HTTP流重试逻辑,建议检查第47行", "is_complete": true}
{"type": "answer", "content": "详细分析如下...", "is_complete": false}

关键点:

  • type字段让前端知道当前收到的是什么内容
  • is_complete字段标记段落是否结束,用于UI状态更新
  • 独立推送保证即使thinking很长,summary和answer也能及时到达

2.3 前端渲染策略

终端版(Rich)的实现:

python 复制代码
from rich.live import Live
from rich.panel import Panel
from rich.markdown import Markdown
from rich.layout import Layout

class StreamingDisplay:
    def __init__(self, console):
        self.console = console
        self.thinking = ""
        self.summary = ""
        self.answer = ""
        self.show_thinking = False  # 默认折叠
        
    def update(self, block: MessageBlock):
        if block.type == "thinking":
            self.thinking = block.content
        elif block.type == "summary":
            self.summary = block.content
        elif block.type == "answer":
            self.answer += block.content
        
        self._render()
    
    def _render(self):
        layout = Layout()
        
        # summary 始终显示在最上方
        if self.summary:
            layout.split_column(
                Layout(Panel(f"💡 {self.summary}", style="bold cyan")), 
                Layout(self._build_main_content())
            )
        else:
            layout = self._build_main_content()
            
        self.live.update(layout)
    
    def _build_main_content(self):
        # thinking 可折叠
        if self.show_thinking and self.thinking:
            thinking_panel = Panel(
                self.thinking, 
                title="🔍 思考过程", 
                style="dim"
            )
        else:
            thinking_panel = ""
            
        # answer 主体
        answer_panel = Panel(Markdown(self.answer), title="如意Agent")
        
        return Layout(thinking_panel + answer_panel)

Web版(Vue3)的实现更简单,用v-if控制折叠:

vue 复制代码
<template>
  <div class="message-blocks">
    <!-- summary 始终显示 -->
    <div v-if="summary" class="summary-block">
      💡 {{ summary }}
    </div>
    
    <!-- thinking 可折叠 -->
    <div v-if="thinking" class="thinking-section">
      <button @click="toggleThinking">
        {{ showThinking ? '收起' : '查看思考过程' }}
      </button>
      <div v-show="showThinking" class="thinking-block">
        {{ thinking }}
      </div>
    </div>
    
    <!-- answer 主体 -->
    <div class="answer-block" v-html="renderedAnswer"></div>
  </div>
</template>

三、后端解析实现

三段式的核心难点在解析:LLM返回的是纯文本流,如何从中提取出thinking、summary、answer?

3.1 标签约定

我们在system prompt中要求LLM按固定格式输出:

复制代码
<thinking>
分析当前情况...
决定使用哪个工具...
</thinking>

<summary>
极简单行概括(<30字)
</summary>

详细回答...
代码示例...

这个约定有几个好处:

  • 结构化:标签明确,解析简单
  • 向后兼容:不支持标签的LLM,内容会全部落入answer
  • 可扩展:未来增加新段落类型,只需加新标签

3.2 流式解析器

解析器需要处理流式输入,不能等全部内容到达再解析:

python 复制代码
import re
from typing import Generator

class StreamParser:
    def __init__(self):
        self.buffer = ""
        self.current_block = None
        
    def parse(self, text_stream: Generator[str, None, None]) -> Generator[MessageBlock, None, None]:
        """流式解析文本,实时产出MessageBlock"""
        
        for chunk in text_stream:
            self.buffer += chunk
            
            # 检查是否完整捕获了一个标签块
            for block_type, pattern in [
                ("thinking", r"<thinking>([
相关推荐
搬砖的小码农_Sky32 分钟前
AI Agent:macOS Sequoia 部署 OpenClaw 完整教程
人工智能·macos·ai·人机交互
无心水1 小时前
【Harness:设计规范】15、Harness 成熟度模型(H0-H3):你的 AI 智能体在第几层
人工智能·设计规范·openclaw·养龙虾·harness·hermes·honcho
Raink老师7 小时前
【AI面试临阵磨枪-79】实时数据 RAG:订单、商家、物流、天气、动态库存
人工智能·面试·职场和发展
脑极体8 小时前
点亮星河AI+鸿蒙,一座艺术场馆的日神觉醒
人工智能·华为·harmonyos
Cosolar8 小时前
Chroma向量库面试学习指南
数据库·人工智能·面试·职场和发展·数据库架构
BUG指挥官8 小时前
Claude Code的自动化编程
人工智能
意图共鸣8 小时前
意图共鸣科技《认知智能白皮书》——感知与执行分离:认知架构(CA)如何重塑大模型底层结构
人工智能·架构
等一个人的@8 小时前
让数据自己开口:数睿通智库新增智能问数模块
人工智能·自然语言处理
ZGi.ai8 小时前
人工审查节点:让自动化工作流多一步人工把关
运维·人工智能·自动化·人机协同·智能体工作流·人工审查
王莎莎-MinerU9 小时前
MinerU 深度技术解析:从架构原理到生产部署的全面指南
css·人工智能·自然语言处理·架构·ocr·个人开发