Files
HxClaw/taolun.md
titor d42c70f5ff feat: 实现流式输出功能 v0.1.0
- 创建 hxclaw 项目,基于 picoclaw 的 CLI 增强工具
- 实现流式输出,使用 fmt.Print + os.Stdout.Sync() 实时刷新
- 解决 onChunk 回调累积文本导致的重复输出问题
- 使用 strings.Builder 收集完整响应并保存到 session
- 添加讨论记录和更新日志文档
2026-04-11 22:32:43 +08:00

141 lines
4.2 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 等工具的流式输出效果。