Files
HxClaw/taolun.md

387 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# hxclaw 讨论记录
## 知识点汇总
### 1. hxclaw 是什么?
- hxclaw 是基于 picoclaw 的 CLI 增强工具
- 提供流式输出(替代原有的批量输出)
- 提供 Markdown 终端渲染(提升阅读体验)
- 定位: picoclaw 的变体,面向需要更好终端交互体验的用户
### 2. 为什么采用独立二进制而非插件?
- picoclaw 目前没有正式的插件系统
- 工具扩展通过 ToolRegistry 注册(如 spi.go, i2c.go
- 技能通过 SKILL.md 文件加载
- MCP 通过配置连接外部服务器
- 最小侵入方式:独立二进制 + 复用核心库
### 3. 如何复用 picoclaw 功能?
- 使用 Go 的 replace 机制在 go.mod 中声明依赖
- 开发时 replace 指向本地 picoclaw 目录
- 发布时 replace 指向 GitHub 具体版本(如 v0.2.4
- hxclaw 只需导入pkg/agent, pkg/providers, pkg/config, pkg/bus
### 4. hxclaw 架构设计
```
hxclaw/
├── cmd/hxclaw/ # CLI 入口(自己实现)
├── go.mod # 依赖配置
└── pkg/ # 空目录,全量复用 picoclaw
```
- 不依赖pkg/channels不需要消息通道、pkg/gateway不需要 HTTP 服务、web/(不需要网页)
- 依赖pkg/agent核心逻辑、pkg/providersLLM 调用、pkg/config配置加载
### 5. 流式输出原理
- picoclaw 的 providers 已支持 StreamingProvider 接口
- 接口定义ChatStream(ctx, messages, tools, model, options, onChunk)
- onChunk 是回调函数,每个 token 生成时调用
- CLI 层需要判断 provider 是否实现 StreamingProvider然后选择调用
### 6. Markdown 终端渲染
- 使用 charmbracelet 家族库
- lipgloss终端样式
- glow代码高亮
- 流程Markdown → ANSI 转义序列 → 终端显示
### 7. 部署方式
- 独立二进制 hxclaw与 picoclaw 二进制共存于同一目录
- 用户使用 `hxclaw` 命令调用增强版 CLI
- 配置文件复用 picoclaw 的 config.json位于 ~/.picoclaw/config.json
### 8. 版本同步策略
- 关键版本跟进(功能大版本更新时)
- 不需要每次 picoclaw 升级都同步
- 依赖版本在 go.mod 中声明,更新时修改 replace 目标版本即可
### 9. AgentRegistry 的正确使用方式
- `AgentRegistry` 负责管理多个 Agent 实例
- `GetDefaultAgent()` 获取默认的 Agent 实例
- `AgentInstance` 包含:
- `Provider` - LLM 提供者
- `ContextBuilder` - 消息构建器
- `Tools` - 工具注册表
- `Model` - 模型名称
- 注意:`AgentRegistry` 没有 `BuildMessages` 方法,该方法属于 `ContextBuilder`
### 10. ToolDefinitions 的获取方式
- 通过 `agentInstance.Tools.ToProviderDefs()` 获取
- 返回 `[]providers.ToolDefinition` 格式
- 该方法将工具注册表中的工具转换为 provider 可用的格式
### 11. 流式输出的实现问题与解决方案
#### 问题 1onChunk 回调接收累积文本
picoclaw 的 `StreamingProvider` 接口定义:
```go
onChunk func(accumulated string)
```
注释明确说明:"onChunk receives the accumulated text so far (not individual deltas)"。
这意味着每次回调时参数是完整的累积文本(如 "你好" → "你好!再次" → "你好!再次见到"),而不是增量。
#### 问题 2直接打印导致重复输出
如果直接打印 token
```go
func(token string) {
fmt.Print(token) // 打印累积文本!
}
```
会导致输出:
```
你好
你好!再次
你好!再次见到
...
```
#### 解决方案 1跟踪已打印长度
使用 `printedLen` 跟踪已打印的字符位置,只打印新增部分:
```go
var printedLen int
func(accumulated string) {
if len(accumulated) > printedLen {
fmt.Print(accumulated[printedLen:])
printedLen = len(accumulated)
}
}
```
#### 问题 3尝试使用 uilive 库
尝试使用 `github.com/gosuri/uilive` 库实现同行流动效果,但发现该库会覆盖每一行,只显示最后一行内容,不符合需求。
#### 最终解决方案:直接 Print + Sync
```go
fmt.Print(accumulated[printedLen:])
os.Stdout.Sync()
```
这样:
1. 字符串自然累积增长
2. 终端自动处理换行(满一行自动 wrap
3. 保留所有历史输出
4. 每次刷新缓冲区确保立即显示
这正是 ollama 等工具的流式输出效果。
---
### 12. 使用 bubbletea v2 的 spinner 组件实现加载动画
#### 需求分析
用户希望在使用流式输出时,显示加载动画:
- 用户输入后显示 "思考中... ⠋"
- 第一个 token 返回后显示 "思考完成."
- 流式输出完成后添加空行分隔
#### 技术选型
使用 `charm.land/bubbles/v2/spinner` 组件,这是 bubbletea v2 官方提供的 spinner 组件。
#### 实现方案
创建独立的 Spinner 结构体,在独立 goroutine 中运行动画:
```go
type Spinner struct {
text string
state SpinnerState
spinner spinner.Model
stopCh chan struct{}
doneCh chan struct{}
}
```
关键点:
- 使用 `spinner.MiniDot` 动画样式
- 独立 goroutine 使用 ticker 驱动动画帧切换
- 使用 `\r` 回车符在同一行刷新动画
- Stop 时输出 "思考完成."
#### 官方示例参考
```go
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case spinner.TickMsg:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
}
```
关键spinner.Tick() 返回的 TickMsg 需要传给 spinner.Update(),并使用返回值更新 spinner model。
#### 注意事项
1. spinner model 更新必须使用返回值:
```go
s.spinner, _ = s.spinner.Update(msg) // 正确
s.spinner.Update(msg) // 错误!动画不会动
```
2. 动画位置:动画在前,文字在后:
```go
fmt.Printf("\r%s %s", s.spinner.View(), s.text) // ⠋ 思考中...
```
3. 换行控制:
- "思考完成." 后需要两个换行符(一个换行 + 一个空行)
- 流式输出完成后也需要空行分隔
---
### 13. 重绘残留问题与新流程
#### 问题描述
之前的流程:
1. 流式实时打印 token边收边打
2. 完成后 Markdown 重绘
3. 问题:重绘有残留
#### 解决方案:等待完整响应后输出
改进后的流程:
1. AI 返回完整数据 ← 等待时间
2. Markdown 转译
3. 模拟流式输出(从配置读取速度)
效果更好,无残留问题。
#### 配置化
使用 `project.config.yml` 统一管理配置:
```yaml
streaming:
line_delay_ms: 1000 # 每行输出后的延迟(毫秒)
last_line_delay_ms: 600 # 最后一行延迟(毫秒)
markdown:
glamour_style: dark
wrap_width: 0 # 自动获取终端宽度
ui:
logo: "🦐"
user_prefix: "👀 " # 用户输入前缀
```
---
### 14. 按行延迟输出的实现
#### 核心逻辑
```go
func outputLineByLine(text string) {
lines := strings.Split(text, "\n")
totalLines := len(lines)
cfg := internal.GetProjectConfig()
lineDelay := time.Duration(cfg.Streaming.LineDelayMs) * time.Millisecond
lastLineDelay := time.Duration(cfg.Streaming.LastLineDelayMs) * time.Millisecond
for i, line := range lines {
if line == "" {
fmt.Println()
continue
}
fmt.Println(line)
if i < totalLines-1 {
time.Sleep(lineDelay)
} else {
time.Sleep(lastLineDelay)
}
}
fmt.Println()
}
```
特点:
- 空行直接跳过
- 每行输出后延迟可配置
- 最后一行延迟可单独配置
---
### 15. 工具调用结果显示问题
#### 问题
使用 `ChatStream` 时,工具调用结果不显示。
#### 原因分析
1. 工具调用结果不在流式响应中返回,而是通过 `bus.PublishOutbound()` 单独发送
2. `ChatStream` 的 `onChunk` 回调只处理文本内容,不处理工具调用
3. 工具调用在 `runTurn` 循环中执行,结果通过消息总线发送
#### 解决方案
回退使用 `ProcessDirect`,因为它会正确处理:
- 工具调用流程
- 工具结果显示
- Markdown 渲染和按行输出
---
### 16. 状态栏优化
#### 改动内容
- 图标:``
- 图标颜色:`#f0c75e`
- 文字颜色:`#2b2e32`
- 内容:只显示耗时,如 `▣ 耗时: 2.3s`
#### 之前 vs 之后
- 之前:`▣ Tokens: 120 · 耗时: 2.3s · 总Tokens: 350`
- 之后:`▣ 耗时: 2.3s`
---
### 17. 项目配置文件详解
#### project.config.yml 结构
```yaml
# hxclaw 项目配置文件
# 项目级配置,会覆盖用户配置
# 模拟流式输出配置
streaming:
line_delay_ms: 1000 # 每行输出后的延迟(毫秒)
last_line_delay_ms: 600 # 最后一行延迟(毫秒)
# Markdown 渲染配置
markdown:
theme: dark # 渲染主题dark, light, dracula, tokyo-night 等
line_width: 0 # 自动换行宽度0=自动获取终端宽度)
# UI 配置
ui:
logo: "🦐" # Logo
user_icon: "👀 " # 用户输入提示符
```
#### 配置加载优先级
1. 用户配置 `~/.config/hxclaw/config.yml`
2. 环境变量 `HXCLAW_CONFIG` 指定路径
3. 项目根目录 `project.config.yml`
4. 代码中的默认值
#### 代码实现
```go
// internal/config.go
type ProjectConfig struct {
Streaming StreamingConfig `yaml:"streaming"`
Markdown MarkdownConfig `yaml:"markdown"`
UI UIConfig `yaml:"ui"`
}
func getConfigPath() string {
if path := os.Getenv("HXCLAW_CONFIG"); path != "" {
return path
}
return filepath.Join(".", "project.config.yml")
}
```
---
### 18. 行业经验参考
#### CLI 动画最佳实践
- 帧率75ms/帧(约 13fps- GitHub Copilot CLI
- Spinner 动画70-120ms - ora 库
- AI 流式输出30-80ms/字符或行
- 总动画时长:控制在 3 秒内 - Copilot CLI 原则
#### 关键结论
- 人眼需要约 30-50ms 才能感知单次视觉变化
- 空白字符不应逐个输出,应批量处理
- 终端宽度 100% 时 Markdown 渲染会显著增加行数和字符数