跳到主要内容
Phi-3-mini-128k-instruct Chainlit 插件开发:思维链可视化与 Token 统计 | 极客日志
Python AI 算法
Phi-3-mini-128k-instruct Chainlit 插件开发:思维链可视化与 Token 统计 介绍如何为基于 vLLM 部署的 Phi-3-mini-128k-instruct 模型开发 Chainlit 插件,实现思维链可视化与 Token 用量统计。内容涵盖环境配置、插件结构搭建、思维链解析器与可视化组件实现、Token 计数器与统计面板开发、应用集成及配置管理。通过拦截模型输出、解析推理步骤及量化 Token 消耗,提升 AI 对话透明度与成本可控性,并提供实际部署指南与扩展建议。
DevStack 发布于 2026/4/6 更新于 2026/5/21 30 浏览Phi-3-mini-128k-instruct Chainlit 插件开发:思维链可视化与 Token 统计
本文介绍如何为基于 vLLM 部署的 Phi-3-mini-128k-instruct 模型开发两个实用的 Chainlit 插件:思维链可视化面板 和Token 用量统计面板 。通过这两个插件,不仅能'看见'模型的推理路径,还能精确掌握每次对话的成本。
1. 项目准备与环境确认
在开始插件开发之前,确保基础环境已经就绪。已成功部署 Phi-3-mini-128k-instruct 模型,并且能够通过 Chainlit 前端正常调用。
1.1 验证模型服务状态
打开终端,使用 Webshell 检查模型服务日志,确认部署成功。
cat /root/workspace/llm.log
如果看到类似下面的输出,说明模型服务正在正常运行:
INFO 04-10 14:30:15 llm_engine.py:73] Initializing an LLM engine with config: model="/root/workspace/models/Phi-3-mini-128k-instruct", tokenizer="/root/workspace/models/Phi-3-mini-128k-instruct", ... INFO 04-10 14:30:20 llm_engine.py:210] # GPU blocks: 496, # CPU blocks: 512 INFO 04-10 14:30:22 model_runner.py:96] Loading model weights took 4.85 GB INFO 04-10 14:30:25 llm_engine.py:287] LLM engine is ready.
1.2 测试基础 Chainlit 功能
打开 Chainlit 前端界面,进行简单的提问测试,确保基础调用流程畅通无阻。
输入一个问题,比如'请用中文解释什么是机器学习',你应该能正常收到模型的回复。这个基础功能是我们后续插件开发的基石。
2. 理解插件开发的核心思路
在动手写代码之前,先搞清楚两个关键问题:思维链可视化 和Token 统计 到底要做什么,以及 Chainlit 插件是怎么工作的。
2.1 思维链可视化:让 AI'思考'可见
大语言模型在生成回答时,内部其实有一个复杂的推理过程。虽然我们无法直接看到神经元如何激活,但可以通过一些技术手段,让模型的'思考步骤'展现出来。
我们的实现思路是 :
拦截模型输出 :在模型生成文本的过程中,不仅获取最终答案,还要捕获中间的关键推理步骤。
解析思维结构 :将模型的'内心独白'解析成清晰的步骤,比如'第一步:理解问题'、'第二步:回忆相关知识'、'第三步:组织答案'。
可视化呈现 :在 Chainlit 界面中,以流程图、步骤列表或时间线的形式,将这些思考过程展示给用户。
2.2 Token 用量统计:量化对话成本
每次与 AI 对话都会消耗计算资源,而 Token 是衡量这种消耗的基本单位。了解 Token 用量,对于成本控制、性能优化都至关重要。
我们需要统计哪些数据 :
输入 Token 数 :你提问用了多少 Token
输出 Token 数 :模型回答用了多少 Token
总 Token 数 :本次对话的总消耗
预估成本 :如果知道每个 Token 的价格,还能估算出本次对话的费用
2.3 Chainlit 插件工作机制
Chainlit 提供了灵活的插件系统,允许我们在不修改核心代码的情况下,扩展界面功能。插件主要通过以下方式工作:
:在对话流中插入自定义的 UI 组件
自定义消息元素
侧边栏面板 :在界面侧边添加新的信息面板
回调函数 :在特定事件发生时执行自定义逻辑
状态管理 :在会话中保存和读取自定义数据
3. 开发思维链可视化插件 这个插件会在模型生成回答时,自动分析并展示其推理过程。我们分步骤来实现。
3.1 创建插件基础结构 首先,在你的 Chainlit 项目目录中创建插件文件夹和文件。
mkdir -p plugins/thought_chain
touch plugins/thought_chain/__init__.py
touch plugins/thought_chain/visualizer.py
touch plugins/thought_chain/utils.py
3.2 实现思维链解析器 编辑 plugins/thought_chain/utils.py,添加思维链解析的核心逻辑。
import re
from typing import List , Dict , Any
import json
class ThoughtChainParser :
"""思维链解析器,用于从模型输出中提取推理步骤"""
def __init__ (self ):
self .patterns = {
'step_by_step' : r'(?:步骤|Step)\s*\d+[::]\s*(.*?)(?=(?:步骤|Step)\s*\d+[::]|$)' ,
'reasoning' : r'(?:思考 | 推理|Reasoning)[::]\s*(.*?)(?=(?:因此 | 所以|Thus|Therefore)|$)' ,
'bullet_points' : r'[•\-*]\s*(.*?)(?=\n[•\-*]|\n\n|$)' ,
'numbered_list' : r'\d+\.\s*(.*?)(?=\n\d+\.|\n\n|$)'
}
def parse (self, text: str ) -> List [Dict [str , Any ]]:
"""
从文本中解析思维链
Args:
text: 模型生成的文本
Returns:
思维步骤列表,每个步骤包含内容和类型
"""
steps = []
for pattern_name, pattern in self .patterns.items():
matches = re.findall(pattern, text, re.DOTALL)
if matches:
for i, match in enumerate (matches):
step_text = match .strip()
if step_text and len (step_text) > 10 :
steps.append({
'step' : len (steps) + 1 ,
'content' : step_text,
'type' : pattern_name,
'confidence' : 0.8
})
if not steps:
sentences = re.split(r'[。!?.!?]\s*' , text)
for i, sentence in enumerate (sentences):
sentence = sentence.strip()
if sentence and len (sentence) > 15 :
steps.append({
'step' : i + 1 ,
'content' : sentence,
'type' : 'sentence' ,
'confidence' : 0.5
})
return steps
def extract_final_answer (self, text: str , steps: List [Dict ] ) -> str :
"""
从文本中提取最终答案
Args:
text: 完整文本
steps: 解析出的思维步骤
Returns:
最终答案文本
"""
if not steps:
return text
last_step_content = steps[-1 ]['content' ]
last_step_pos = text.rfind(last_step_content)
if last_step_pos != -1 :
answer_start = last_step_pos + len (last_step_content)
final_answer = text[answer_start:].strip()
prefixes = ['因此' , '所以' , '综上所述' , '答案是' , 'Answer:' , 'Thus' , 'Therefore' ]
for prefix in prefixes:
if final_answer.startswith(prefix):
final_answer = final_answer[len (prefix):].strip()
return final_answer if final_answer else "(答案已包含在推理过程中)"
return text
3.3 创建可视化组件 编辑 plugins/thought_chain/visualizer.py,实现 Chainlit 的可视化组件。
import chainlit as cl
from typing import List , Dict , Any
import json
from .utils import ThoughtChainParser
class ThoughtChainVisualizer :
"""思维链可视化组件"""
def __init__ (self ):
self .parser = ThoughtChainParser()
async def visualize (self, message: cl.Message ):
"""
可视化消息中的思维链
Args:
message: Chainlit 消息对象
"""
text = message.content
steps = self .parser.parse(text)
final_answer = self .parser.extract_final_answer(text, steps)
if steps:
await self ._create_thought_chain_panel(steps, final_answer)
else :
await self ._create_simple_analysis(text)
async def _create_thought_chain_panel (self, steps: List [Dict ], final_answer: str ):
"""创建思维链可视化面板"""
elements = []
elements.append(
cl.Text(
name="thought_chain_title" ,
content="## 🤔 模型思考过程" ,
display="side"
)
)
for step in steps:
step_content = f"**步骤 {step['step' ]} ** ({step['type' ]} )\n\n{step['content' ]} "
elements.append(
cl.Text(
name=f"step_{step['step' ]} " ,
content=step_content,
display="side"
)
)
if final_answer:
elements.append(
cl.Text(
name="final_answer" ,
content=f"## ✅ 最终答案\n\n{final_answer} " ,
display="side"
)
)
analysis = self ._create_analysis_summary(steps)
elements.append(
cl.Text(
name="analysis_summary" ,
content=f"## 📊 分析摘要\n\n{analysis} " ,
display="side"
)
)
for element in elements:
await element.send()
async def _create_simple_analysis (self, text: str ):
"""创建简单分析面板"""
analysis = self ._analyze_text_structure(text)
await cl.Text(
name="simple_analysis" ,
content=f"## 📝 回答结构分析\n\n{analysis} " ,
display="side"
).send()
def _create_analysis_summary (self, steps: List [Dict ] ) -> str :
"""创建分析摘要"""
total_steps = len (steps)
step_types = {}
for step in steps:
step_type = step['type' ]
step_types[step_type] = step_types.get(step_type, 0 ) + 1
summary = f"- **总推理步骤**: {total_steps} 步\n"
for step_type, count in step_types.items():
type_name = {
'step_by_step' : '分步推理' ,
'reasoning' : '逻辑推理' ,
'bullet_points' : '要点列举' ,
'numbered_list' : '编号列表' ,
'sentence' : '句子分析'
}.get(step_type, step_type)
summary += f"- **{type_name} **: {count} 步\n"
avg_confidence = sum (step['confidence' ] for step in steps) / total_steps
summary += f"- **推理清晰度**: {avg_confidence:.1 %} \n"
return summary
def _analyze_text_structure (self, text: str ) -> str :
"""分析文本结构"""
sentences = [s.strip() for s in re.split(r'[。!?.!?]\s*' , text) if s.strip()]
words = len ('' .join(text.split()))
analysis = f"- **总句子数**: {len (sentences)} \n"
analysis += f"- **总字数**: {words} 字\n"
analysis += f"- **平均句长**: {words/len (sentences):.1 f} 字/句\n"
if any (keyword in text.lower() for keyword in ['首先' , '其次' , '然后' , '最后' ]):
analysis += "- **文本类型**: 顺序说明\n"
elif any (keyword in text.lower() for keyword in ['因为' , '所以' , '因此' , '由于' ]):
analysis += "- **文本类型**: 因果论证\n"
elif text.count('\n' ) > 3 or '•' in text or '-' in text:
analysis += "- **文本类型**: 列表说明\n"
else :
analysis += "- **文本类型**: 一般叙述\n"
return analysis
3.4 集成到 Chainlit 应用 现在我们需要将思维链可视化插件集成到主应用中。编辑你的 app.py 或主应用文件。
import chainlit as cl
from chainlit import run_sync
from plugins.thought_chain.visualizer import ThoughtChainVisualizer
import asyncio
thought_visualizer = ThoughtChainVisualizer()
@cl.on_message
async def main (message: cl.Message ):
"""处理用户消息"""
await cl.Message(
content=f"用户提问:{message.content} " ,
author="User"
).send()
simulated_response = "让我思考一下这个问题。首先,我需要理解什么是机器学习。机器学习是人工智能的一个分支,它使计算机能够从数据中学习并做出决策,而无需显式编程。接下来,我可以从几个关键方面来解释:1. 核心思想:让机器从经验中学习 2. 主要类型:监督学习、无监督学习、强化学习 3. 常见应用:图像识别、语音识别、推荐系统 具体来说,监督学习就像有老师指导,我们给机器带有标签的数据进行训练;无监督学习则是让机器自己发现数据中的模式;强化学习则是通过试错来学习最佳策略。因此,简单来说,机器学习就是让计算机像人类一样从经验中学习,并不断改进其性能的技术。"
msg = cl.Message(content="" )
await msg.send()
for chunk in simulated_response.split():
await msg.stream_token(chunk + " " )
await asyncio.sleep(0.05 )
msg.content = simulated_response
await msg.update()
await thought_visualizer.visualize(msg)
@cl.on_chat_start
async def start_chat ():
"""聊天开始时的初始化"""
welcome_msg = cl.Message(
content="欢迎使用 Phi-3-mini 智能助手!我已启用思维链可视化功能,您可以看到模型的推理过程。" ,
author="Assistant"
)
await welcome_msg.send()
await cl.Sidebar(
name="thought_chain_sidebar" ,
content="## 思维链可视化面板\n\n模型的思考过程将在这里显示。" ,
size="large"
).send()
if __name__ == "__main__" :
cl.run()
4. 开发 Token 用量统计插件 接下来,我们开发第二个插件:Token 用量统计面板。这个插件会实时统计并展示每次对话的 Token 消耗。
4.1 创建 Token 统计插件结构 mkdir -p plugins/token_stats
touch plugins/token_stats/__init__.py
touch plugins/token_stats/counter.py
touch plugins/token_stats/panel.py
4.2 实现 Token 计数器 编辑 plugins/token_stats/counter.py,实现 Token 统计的核心逻辑。
import tiktoken
from typing import Dict , Any , Optional
import time
from dataclasses import dataclass
from datetime import datetime
@dataclass
class TokenStats :
"""Token 统计数据结构"""
input_tokens: int = 0
output_tokens: int = 0
total_tokens: int = 0
start_time: Optional [float ] = None
end_time: Optional [float ] = None
cost_estimate: float = 0.0
@property
def duration (self ) -> float :
"""计算处理时长(秒)"""
if self .start_time and self .end_time:
return self .end_time - self .start_time
return 0.0
@property
def tokens_per_second (self ) -> float :
"""计算 Token 生成速度"""
if self .duration > 0 and self .output_tokens > 0 :
return self .output_tokens / self .duration
return 0.0
class TokenCounter :
"""Token 计数器"""
def __init__ (self, model_name: str = "gpt-3.5-turbo" ):
"""
初始化 Token 计数器
Args:
model_name: 模型名称,用于选择编码器
"""
try :
self .encoder = tiktoken.encoding_for_model(model_name)
except :
self .encoder = tiktoken.get_encoding("cl100k_base" )
self .pricing = {
"input" : 0.0015 / 1000 ,
"output" : 0.0020 / 1000 ,
}
def count_tokens (self, text: str ) -> int :
"""
计算文本的 Token 数量
Args:
text: 输入文本
Returns:
Token 数量
"""
if not text:
return 0
try :
tokens = self .encoder.encode(text)
return len (tokens)
except :
chinese_chars = sum (1 for c in text if '\u4e00' <= c <= '\u9fff' )
english_chars = len (text) - chinese_chars
return int (chinese_chars / 2 + english_chars / 4 )
def calculate_cost (self, input_tokens: int , output_tokens: int ) -> float :
"""
计算预估成本
Args:
input_tokens: 输入 Token 数
output_tokens: 输出 Token 数
Returns:
预估成本(美元)
"""
input_cost = input_tokens * self .pricing["input" ]
output_cost = output_tokens * self .pricing["output" ]
return input_cost + output_cost
def create_stats (self, input_text: str , output_text: str , start_time: Optional [float ] = None , end_time: Optional [float ] = None ) -> TokenStats:
"""
创建完整的 Token 统计
Args:
input_text: 输入文本
output_text: 输出文本
start_time: 开始时间戳
end_time: 结束时间戳
Returns:
TokenStats 对象
"""
input_tokens = self .count_tokens(input_text)
output_tokens = self .count_tokens(output_text)
total_tokens = input_tokens + output_tokens
stats = TokenStats(
input_tokens=input_tokens,
output_tokens=output_tokens,
total_tokens=total_tokens,
start_time=start_time,
end_time=end_time or time.time(),
cost_estimate=self .calculate_cost(input_tokens, output_tokens)
)
return stats
def format_stats (self, stats: TokenStats ) -> Dict [str , Any ]:
"""
格式化统计信息
Args:
stats: TokenStats 对象
Returns:
格式化后的统计信息字典
"""
return {
"输入 Token 数" : f"{stats.input_tokens:,} " ,
"输出 Token 数" : f"{stats.output_tokens:,} " ,
"总 Token 数" : f"{stats.total_tokens:,} " ,
"处理时间" : f"{stats.duration:.2 f} 秒" ,
"生成速度" : f"{stats.tokens_per_second:.1 f} Token/秒" ,
"预估成本" : f"${stats.cost_estimate:.6 f} " ,
"时间戳" : datetime.now().strftime("%Y-%m-%d %H:%M:%S" )
}
4.3 创建统计面板组件 编辑 plugins/token_stats/panel.py,实现 Chainlit 的统计面板。
import chainlit as cl
from typing import Dict , Any , List
import json
from datetime import datetime
from .counter import TokenCounter, TokenStats
class TokenStatsPanel :
"""Token 统计面板"""
def __init__ (self ):
self .counter = TokenCounter()
self .session_stats: List [Dict [str , Any ]] = []
async def update_panel (self, input_text: str , output_text: str , start_time: float , end_time: float ):
"""
更新统计面板
Args:
input_text: 用户输入
output_text: 模型输出
start_time: 开始时间戳
end_time: 结束时间戳
"""
stats = self .counter.create_stats(
input_text=input_text,
output_text=output_text,
start_time=start_time,
end_time=end_time
)
formatted_stats = self .counter.format_stats(stats)
self .session_stats.append({
"timestamp" : datetime.now().isoformat(),
"stats" : formatted_stats,
"input_preview" : input_text[:100 ] + ("..." if len (input_text) > 100 else "" ),
"output_preview" : output_text[:100 ] + ("..." if len (output_text) > 100 else "" )
})
await self ._render_panel(formatted_stats)
await self ._update_history_panel()
async def _render_panel (self, stats: Dict [str , Any ] ):
"""渲染统计面板"""
stats_content = "## 📊 本次对话统计\n\n"
for key, value in stats.items():
if key != "时间戳" :
stats_content += f"**{key} **: {value} \n\n"
total_tokens = int (stats["总 Token 数" ].replace("," , "" ))
max_tokens = 128000
if total_tokens > 0 :
percentage = min (100 , (total_tokens / max_tokens) * 100 )
bar_length = 20
filled = int (bar_length * percentage / 100 )
bar = "█" * filled + "░" * (bar_length - filled)
stats_content += f"**上下文使用率**: {percentage:.1 f} %\n"
stats_content += f"`[{bar} ]`\n\n"
stats_content += f"*统计时间:{stats.get('时间戳' , 'N/A' )} *"
await cl.Sidebar(
name="token_stats" ,
content=stats_content,
size="medium"
).send()
async def _update_history_panel (self ):
"""更新历史记录面板"""
if len (self .session_stats) <= 1 :
return
history_content = "## 📈 历史统计\n\n"
recent_stats = self .session_stats[-5 :]
for i, record in enumerate (recent_stats):
history_content += f"### 对话 {len (self.session_stats) - len (recent_stats) + i + 1 } \n"
history_content += f"**时间**: {record['timestamp' ][11 :19 ]} \n"
history_content += f"**输入**: {record['input_preview' ]} \n"
history_content += f"**输出**: {record['output_preview' ]} \n"
history_content += f"**总 Token**: {record['stats' ]['总 Token 数' ]} \n"
history_content += "---\n\n"
total_input = sum (int (s['stats' ]['输入 Token 数' ].replace("," , "" )) for s in self .session_stats)
total_output = sum (int (s['stats' ]['输出 Token 数' ].replace("," , "" )) for s in self .session_stats)
total_cost = sum (float (s['stats' ]['预估成本' ].replace("$" , "" )) for s in self .session_stats)
history_content += "### 累计统计\n"
history_content += f"**总对话数**: {len (self.session_stats)} \n"
history_content += f"**累计输入 Token**: {total_input:,} \n"
history_content += f"**累计输出 Token**: {total_output:,} \n"
history_content += f"**累计预估成本**: ${total_cost:.6 f} \n"
await cl.Sidebar(
name="token_history" ,
content=history_content,
size="large"
).send()
async def show_initial_panel (self ):
"""显示初始面板"""
initial_content = "## 📊 Token 用量统计面板\n本面板将实时显示:\n- ✅ 每次对话的 Token 消耗\n- ✅ 处理时间和生成速度\n- ✅ 上下文使用率\n- ✅ 预估成本统计\n- 📈 历史记录追踪\n等待第一次对话开始..."
await cl.Sidebar(
name="token_stats" ,
content=initial_content,
size="medium"
).send()
4.4 集成 Token 统计到主应用 import chainlit as cl
from chainlit import run_sync
from plugins.thought_chain.visualizer import ThoughtChainVisualizer
from plugins.token_stats.panel import TokenStatsPanel
import asyncio
import time
thought_visualizer = ThoughtChainVisualizer()
token_panel = TokenStatsPanel()
@cl.on_message
async def main (message: cl.Message ):
"""处理用户消息"""
start_time = time.time()
user_msg = cl.Message(
content=f"用户提问:{message.content} " ,
author="User"
)
await user_msg.send()
simulated_response = "让我思考一下这个问题。首先,我需要理解什么是机器学习。机器学习是人工智能的一个分支,它使计算机能够从数据中学习并做出决策,而无需显式编程。接下来,我可以从几个关键方面来解释:1. 核心思想:让机器从经验中学习 2. 主要类型:监督学习、无监督学习、强化学习 3. 常见应用:图像识别、语音识别、推荐系统 具体来说,监督学习就像有老师指导,我们给机器带有标签的数据进行训练;无监督学习则是让机器自己发现数据中的模式;强化学习则是通过试错来学习最佳策略。因此,简单来说,机器学习就是让计算机像人类一样从经验中学习,并不断改进其性能的技术。"
await asyncio.sleep(0.5 )
msg = cl.Message(content="" )
await msg.send()
for chunk in simulated_response.split():
await msg.stream_token(chunk + " " )
await asyncio.sleep(0.05 )
msg.content = simulated_response
await msg.update()
end_time = time.time()
await token_panel.update_panel(
input_text=message.content,
output_text=simulated_response,
start_time=start_time,
end_time=end_time
)
await thought_visualizer.visualize(msg)
@cl.on_chat_start
async def start_chat ():
"""聊天开始时的初始化"""
welcome_msg = cl.Message(
content="欢迎使用 Phi-3-mini 智能助手!\n我已启用以下增强功能:\n🔍 **思维链可视化** - 查看模型的推理过程\n📊 **Token 用量统计** - 实时监控资源消耗\n请开始提问吧!" ,
author="Assistant"
)
await welcome_msg.send()
await cl.Sidebar(
name="thought_chain_sidebar" ,
content="## 思维链可视化面板\n\n模型的思考过程将在这里显示。" ,
size="large"
).send()
await token_panel.show_initial_panel()
if __name__ == "__main__" :
cl.run()
5. 插件配置与高级功能 现在我们已经完成了两个核心插件的开发,接下来让我们添加一些配置选项和高级功能,让插件更加实用。
5.1 创建配置文件
import os
from dataclasses import dataclass
from typing import Optional
@dataclass
class PluginConfig :
"""插件配置类"""
thought_chain_enabled: bool = True
thought_chain_min_confidence: float = 0.3
thought_chain_max_steps: int = 10
token_stats_enabled: bool = True
token_cost_per_input: float = 0.0015 / 1000
token_cost_per_output: float = 0.0020 / 1000
show_cost_estimation: bool = True
sidebar_position: str = "right"
auto_collapse: bool = False
@classmethod
def from_env (cls ):
"""从环境变量加载配置"""
return cls(
thought_chain_enabled=os.getenv("THOUGHT_CHAIN_ENABLED" , "true" ).lower() == "true" ,
thought_chain_min_confidence=float (os.getenv("THOUGHT_CHAIN_MIN_CONFIDENCE" , "0.3" )),
thought_chain_max_steps=int (os.getenv("THOUGHT_CHAIN_MAX_STEPS" , "10" )),
token_stats_enabled=os.getenv("TOKEN_STATS_ENABLED" , "true" ).lower() == "true" ,
token_cost_per_input=float (os.getenv("TOKEN_COST_INPUT" , "0.0000015" )),
token_cost_per_output=float (os.getenv("TOKEN_COST_OUTPUT" , "0.0000020" )),
show_cost_estimation=os.getenv("SHOW_COST_ESTIMATION" , "true" ).lower() == "true" ,
sidebar_position=os.getenv("SIDEBAR_POSITION" , "right" ),
auto_collapse=os.getenv("AUTO_COLLAPSE" , "false" ).lower() == "true"
)
config = PluginConfig.from_env()
5.2 添加设置界面 让我们添加一个简单的设置界面,允许用户动态调整插件配置。
import chainlit as cl
from typing import Dict , Any
import json
class SettingsPanel :
"""设置面板"""
def __init__ (self, config ):
self .config = config
async def show_settings (self ):
"""显示设置面板"""
settings_content = "## ⚙️ 插件设置\n### 思维链可视化\n- **启用/禁用**: 控制是否显示思维链\n- **置信度阈值**: 过滤低置信度的推理步骤\n- **最大步骤数**: 限制显示的步骤数量\n### Token 统计\n- **启用/禁用**: 控制是否显示 Token 统计\n- **成本估算**: 显示/隐藏成本估算\n- **单价设置**: 自定义 Token 单价\n### 显示设置\n- **侧边栏位置**: 左右切换\n- **自动折叠**: 自动折叠非活动面板"
settings_elements = [
cl.Checkbox(id ="thought_chain_enabled" , label="启用思维链可视化" , initial=self .config.thought_chain_enabled),
cl.Slider(id ="thought_chain_confidence" , label="置信度阈值" , initial=self .config.thought_chain_min_confidence, min =0 , max =1 , step=0.1 , marks={0 : "0" , 0.5 : "0.5" , 1 : "1" }),
cl.NumberInput(id ="thought_chain_max_steps" , label="最大显示步骤" , initial=self .config.thought_chain_max_steps, min =1 , max =20 ),
cl.Divider(),
cl.Checkbox(id ="token_stats_enabled" , label="启用 Token 统计" , initial=self .config.token_stats_enabled),
cl.Checkbox(id ="show_cost_estimation" , label="显示成本估算" , initial=self .config.show_cost_estimation),
cl.Button(name="save_settings" , label="保存设置" , variant="primary" )
]
await cl.Sidebar(
name="settings_panel" ,
content=settings_content,
elements=settings_elements,
size="medium"
).send()
async def handle_settings_change (self, settings: Dict [str , Any ] ):
"""处理设置变更"""
if "thought_chain_enabled" in settings:
self .config.thought_chain_enabled = settings["thought_chain_enabled" ]
if "thought_chain_confidence" in settings:
self .config.thought_chain_min_confidence = settings["thought_chain_confidence" ]
if "thought_chain_max_steps" in settings:
self .config.thought_chain_max_steps = settings["thought_chain_max_steps" ]
if "token_stats_enabled" in settings:
self .config.token_stats_enabled = settings["token_stats_enabled" ]
if "show_cost_estimation" in settings:
self .config.show_cost_estimation = settings["show_cost_estimation" ]
await cl.Message(
content="✅ 设置已更新!" ,
author="System"
).send()
5.3 更新主应用集成所有功能
import chainlit as cl
import asyncio
import time
from config import config
from plugins.thought_chain.visualizer import ThoughtChainVisualizer
from plugins.token_stats.panel import TokenStatsPanel
from plugins.settings.panel import SettingsPanel
thought_visualizer = ThoughtChainVisualizer()
token_panel = TokenStatsPanel()
settings_panel = SettingsPanel(config)
@cl.on_message
async def main (message: cl.Message ):
"""处理用户消息"""
start_time = time.time()
user_msg = cl.Message(
content=f"用户提问:{message.content} " ,
author="User"
)
await user_msg.send()
try :
response = await call_phi3_model(message.content)
except Exception as e:
response = f"抱歉,调用模型时出现错误:{str (e)} "
msg = cl.Message(content="" )
await msg.send()
for chunk in response.split():
await msg.stream_token(chunk + " " )
await asyncio.sleep(0.03 )
msg.content = response
await msg.update()
end_time = time.time()
if config.token_stats_enabled:
await token_panel.update_panel(
input_text=message.content,
output_text=response,
start_time=start_time,
end_time=end_time
)
if config.thought_chain_enabled:
await thought_visualizer.visualize(msg)
async def call_phi3_model (prompt: str ) -> str :
"""
调用 Phi-3 模型的函数
实际部署时需要替换为真实的模型调用代码
"""
"""
import requests
import json
url = "http://localhost:8000/v1/completions"
headers = {"Content-Type": "application/json"}
data = {
"model": "Phi-3-mini-128k-instruct",
"prompt": prompt,
"max_tokens": 500,
"temperature": 0.7
}
response = requests.post(url, headers=headers, json=data)
result = response.json()
return result["choices"][0]["text"]
"""
return f"""这是一个模拟的 Phi-3 模型回复。\n对于您的问题"{prompt} ",我的思考过程如下:\n1. 首先,我需要理解问题的核心要点。您询问的是关于{prompt.split()[0 ] if prompt.split() else "某个主题" } 的内容。\n2. 接下来,我会回忆相关的知识体系。根据我的训练数据,这个话题涉及多个方面。\n3. 然后,我会组织回答的结构。一个好的回答应该包括:定义、关键特点、实际应用和注意事项。\n4. 最后,我会用清晰易懂的语言呈现答案。\n基于以上思考,我的回答是:这是一个非常重要且有趣的话题。在实际应用中,需要考虑多个因素,包括技术实现、成本效益和用户体验。建议进一步深入研究具体案例以获得更深入的理解。"""
@cl.on_chat_start
async def start_chat ():
"""聊天开始时的初始化"""
welcome_msg = cl.Message(
content="🤖 欢迎使用 Phi-3-mini 智能助手增强版!\n已加载功能:\n🔍 思维链可视化 - 查看模型推理过程\n📊 Token 用量统计 - 实时监控资源消耗\n⚙️ 可配置设置 - 个性化您的体验\n输入 /settings 打开设置面板\n输入 /help 查看帮助信息" ,
author="Assistant"
)
await welcome_msg.send()
await cl.Sidebar(
name="main_sidebar" ,
content="## 功能面板\n\n选择要查看的面板:" ,
elements=[
cl.Button(name="show_thought" , label="思维链可视化" , variant="primary" ),
cl.Button(name="show_stats" , label="Token 统计" , variant="primary" ),
cl.Button(name="show_settings" , label="设置" , variant="secondary" )
],
size="medium"
).send()
@cl.on_action("show_thought" )
async def on_show_thought ():
"""显示思维链面板"""
await cl.Sidebar(
name="thought_panel" ,
content="## 思维链可视化\n\n模型的思考过程将在这里显示。" ,
size="large"
).send()
@cl.on_action("show_stats" )
async def on_show_stats ():
"""显示统计面板"""
await token_panel.show_initial_panel()
@cl.on_action("show_settings" )
async def on_show_settings ():
"""显示设置面板"""
await settings_panel.show_settings()
@cl.on_chat_resume
async def on_chat_resume ():
"""聊天恢复时的处理"""
await cl.Message(
content="会话已恢复,所有插件功能已重新加载。" ,
author="System"
).send()
if __name__ == "__main__" :
cl.run()
6. 部署与使用指南 现在我们已经完成了所有插件的开发,接下来看看如何部署和使用这个增强版的 Chainlit 应用。
6.1 项目结构整理 phi3-chainlit-enhanced/
├── app.py
├── config.py
├── requirements.txt
├── plugins/
│ ├── __init__.py
│ ├── thought_chain/
│ │ ├── __init__.py
│ │ ├── visualizer.py
│ │ └── utils.py
│ ├── token_stats/
│ │ ├── __init__.py
│ │ ├── counter.py
│ │ └── panel.py
│ └── settings/
│ ├── __init__.py
│ └── panel.py
└── README.md
6.2 安装依赖 chainlit>=1.0.0
tiktoken>=0.5.0
requests>=2.31.0
pip install -r requirements.txt
6.3 环境配置
export THOUGHT_CHAIN_ENABLED=true
export TOKEN_STATS_ENABLED=true
export THOUGHT_CHAIN_MIN_CONFIDENCE=0.3
export THOUGHT_CHAIN_MAX_STEPS=10
export TOKEN_COST_INPUT=0.0000015
export TOKEN_COST_OUTPUT=0.0000020
export SIDEBAR_POSITION=right
export AUTO_COLLAPSE=false
6.4 运行应用 chainlit run app.py --port 7860
6.5 使用说明
访问应用 :打开浏览器,访问 http://localhost:7860
开始对话 :在输入框中提问,模型会生成回答
查看思维链 :在右侧侧边栏查看模型的推理过程
监控 Token 使用 :实时查看 Token 消耗和成本估算
调整设置 :点击设置按钮调整插件参数
6.6 实际集成 Phi-3 模型 在实际部署中,你需要将模拟的 call_phi3_model 函数替换为真实的 vLLM API 调用:
import requests
import json
async def call_phi3_model_real (prompt: str ) -> str :
"""实际调用 vLLM 部署的 Phi-3 模型"""
url = "http://localhost:8000/v1/completions"
headers = {
"Content-Type" : "application/json"
}
data = {
"model" : "Phi-3-mini-128k-instruct" ,
"prompt" : prompt,
"max_tokens" : 1024 ,
"temperature" : 0.7 ,
"top_p" : 0.9 ,
"stream" : True
}
try :
response = requests.post(url, headers=headers, json=data, stream=True )
full_response = ""
for line in response.iter_lines():
if line:
line = line.decode('utf-8' )
if line.startswith('data: ' ):
json_str = line[6 :]
if json_str != '[DONE]' :
try :
chunk = json.loads(json_str)
token = chunk['choices' ][0 ]['text' ]
full_response += token
except json.JSONDecodeError:
continue
return full_response
except Exception as e:
return f"调用模型时出错:{str (e)} "
7. 总结 通过本文的教程,我们成功为 Phi-3-mini-128k-instruct 模型开发了两个实用的 Chainlit 插件:思维链可视化面板和 Token 用量统计面板。这两个插件极大地增强了 AI 对话的透明度和可控性。
7.1 主要成果回顾
实时查看模型的推理步骤和思考过程
理解模型是如何从问题推导出答案的
分析回答的结构和质量
提高对话的可解释性和可信度
精确监控每次对话的资源消耗
了解输入和输出的 Token 分布
估算对话成本,便于预算管理
追踪历史使用情况,优化使用策略
7.2 核心价值
提升透明度 :让 AI 的'黑盒'决策过程变得可见
优化成本 :帮助用户更好地管理和控制使用成本
改善体验 :提供更丰富、更有价值的信息展示
促进理解 :帮助用户更好地理解模型的能力和局限
7.3 扩展可能性
更多可视化类型 :添加图表、流程图等更丰富的可视化形式
性能监控 :添加响应时间、内存使用等性能指标
质量评估 :自动评估回答的相关性、准确性和完整性
批量处理 :支持批量提问和批量分析
导出功能 :支持将对话记录和统计数据导出为报告
7.4 实际应用建议
根据需求调整 :根据具体应用场景调整插件的显示内容和详细程度
性能考虑 :在资源受限的环境中,可以关闭部分高开销功能
用户教育 :向最终用户解释这些可视化信息的含义和价值
持续优化 :根据用户反馈不断改进插件功能和用户体验
通过这两个插件,我们不仅增强了 Phi-3 模型的使用体验,也为其他大语言模型的 Chainlit 集成提供了可复用的模式。希望这个教程能帮助你更好地理解和利用大语言模型,让 AI 对话变得更加透明、可控和高效。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online