refactor: Remove Telegram message chunking and simplify reasoning content handling.
This commit is contained in:
parent
b07ac8081e
commit
08c6b84f9f
4 changed files with 87 additions and 162 deletions
|
|
@ -79,25 +79,6 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const telegramMaxMessageLength = 4096
|
|
||||||
|
|
||||||
|
|
||||||
func chunkString(s string, size int) []string {
|
|
||||||
if size <= 0 || s == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
runes := []rune(s)
|
|
||||||
chunks := make([]string, 0, (len(runes)+size-1)/size)
|
|
||||||
|
|
||||||
for i := 0; i < len(runes); i += size {
|
|
||||||
end := min(i + size, len(runes))
|
|
||||||
chunks = append(chunks, string(runes[i:end]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerSharedTools registers tools that are shared across all agents (web, message, spawn).
|
// registerSharedTools registers tools that are shared across all agents (web, message, spawn).
|
||||||
func registerSharedTools(
|
func registerSharedTools(
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
|
|
@ -486,7 +467,7 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (al *AgentLoop) targetReasoningChannelID(channelName string) (chatID string) {
|
func (al *AgentLoop) targetReasoningChannelID(channelName string) (chatID string) {
|
||||||
var channels = al.cfg.Channels
|
channels := al.cfg.Channels
|
||||||
|
|
||||||
switch channelName {
|
switch channelName {
|
||||||
case "telegram":
|
case "telegram":
|
||||||
|
|
@ -517,24 +498,16 @@ func (al *AgentLoop) targetReasoningChannelID(channelName string) (chatID string
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (al *AgentLoop) handleReasoning(reasoningContent, channelName, channelID string) {
|
func (al *AgentLoop) handleReasoning(reasoningContent, channelName, channelID string) {
|
||||||
if reasoningContent == "" || channelName == "" || channelID == "" {
|
if reasoningContent == "" || channelName == "" || channelID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := []string{reasoningContent}
|
|
||||||
if channelName == "telegram" {
|
|
||||||
messages = chunkString(reasoningContent, telegramMaxMessageLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, message := range messages {
|
|
||||||
al.bus.PublishOutbound(bus.OutboundMessage{
|
al.bus.PublishOutbound(bus.OutboundMessage{
|
||||||
Channel: channelName,
|
Channel: channelName,
|
||||||
ChatID: channelID,
|
ChatID: channelID,
|
||||||
Content: message,
|
Content: reasoningContent,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// runLLMIteration executes the LLM call loop with tool handling.
|
// runLLMIteration executes the LLM call loop with tool handling.
|
||||||
|
|
@ -661,7 +634,7 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
return "", iteration, fmt.Errorf("LLM call failed after retries: %w", err)
|
return "", iteration, fmt.Errorf("LLM call failed after retries: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go al.handleReasoning(response.Reasoning, opts.Channel , al.targetReasoningChannelID(opts.Channel))
|
go al.handleReasoning(response.Reasoning, opts.Channel, al.targetReasoningChannelID(opts.Channel))
|
||||||
// Log LLM response details
|
// Log LLM response details
|
||||||
logger.InfoCF("agent", "LLM response",
|
logger.InfoCF("agent", "LLM response",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
|
@ -633,37 +632,6 @@ func TestAgentLoop_ContextExhaustionRetry(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChunkString(t *testing.T) {
|
|
||||||
t.Run("returns nil for invalid size or empty input", func(t *testing.T) {
|
|
||||||
if got := chunkString("", 10); got != nil {
|
|
||||||
t.Fatalf("expected nil for empty input, got %v", got)
|
|
||||||
}
|
|
||||||
if got := chunkString("abc", 0); got != nil {
|
|
||||||
t.Fatalf("expected nil for size=0, got %v", got)
|
|
||||||
}
|
|
||||||
if got := chunkString("abc", -1); got != nil {
|
|
||||||
t.Fatalf("expected nil for size<0, got %v", got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("chunks by rune count and preserves utf8 validity", func(t *testing.T) {
|
|
||||||
in := "ab😀cd界x"
|
|
||||||
got := chunkString(in, 3)
|
|
||||||
want := []string{"ab😀", "cd界", "x"}
|
|
||||||
if len(got) != len(want) {
|
|
||||||
t.Fatalf("chunk count mismatch: got %d want %d (%v)", len(got), len(want), got)
|
|
||||||
}
|
|
||||||
for i := range want {
|
|
||||||
if got[i] != want[i] {
|
|
||||||
t.Fatalf("chunk[%d] mismatch: got %q want %q", i, got[i], want[i])
|
|
||||||
}
|
|
||||||
if !utf8.ValidString(got[i]) {
|
|
||||||
t.Fatalf("chunk[%d] is not valid utf8: %q", i, got[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTargetReasoningChannelID_AllChannels(t *testing.T) {
|
func TestTargetReasoningChannelID_AllChannels(t *testing.T) {
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -774,42 +742,26 @@ func TestHandleReasoning(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("chunks telegram messages", func(t *testing.T) {
|
t.Run("publishes one message for telegram", func(t *testing.T) {
|
||||||
al, msgBus := newLoop(t)
|
al, msgBus := newLoop(t)
|
||||||
large := make([]rune, telegramMaxMessageLength+5)
|
reasoning := "hello telegram reasoning"
|
||||||
for i := range large {
|
al.handleReasoning(reasoning, "telegram", "tg-chat")
|
||||||
large[i] = '界'
|
|
||||||
}
|
|
||||||
largeReasoning := string(large)
|
|
||||||
al.handleReasoning(largeReasoning, "telegram", "tg-chat")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
msg1, ok := msgBus.SubscribeOutbound(ctx)
|
msg, ok := msgBus.SubscribeOutbound(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("expected first outbound message")
|
t.Fatal("expected outbound message")
|
||||||
}
|
|
||||||
msg2, ok := msgBus.SubscribeOutbound(ctx)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("expected second outbound message")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg1.Channel != "telegram" || msg2.Channel != "telegram" {
|
if msg.Channel != "telegram" {
|
||||||
t.Fatalf("expected telegram channel messages, got %+v and %+v", msg1, msg2)
|
t.Fatalf("expected telegram channel message, got %+v", msg)
|
||||||
}
|
}
|
||||||
if msg1.ChatID != "tg-chat" || msg2.ChatID != "tg-chat" {
|
if msg.ChatID != "tg-chat" {
|
||||||
t.Fatalf("expected chatID tg-chat, got %+v and %+v", msg1, msg2)
|
t.Fatalf("expected chatID tg-chat, got %+v", msg)
|
||||||
}
|
}
|
||||||
|
if msg.Content != reasoning {
|
||||||
gotCombined := msg1.Content + msg2.Content
|
t.Fatalf("content mismatch: got %q want %q", msg.Content, reasoning)
|
||||||
if gotCombined != largeReasoning {
|
|
||||||
t.Fatalf("chunked content mismatch: got len=%d want len=%d", len(gotCombined), len(largeReasoning))
|
|
||||||
}
|
|
||||||
if len([]rune(msg1.Content)) != telegramMaxMessageLength {
|
|
||||||
t.Fatalf("first chunk rune length = %d, want %d", len([]rune(msg1.Content)), telegramMaxMessageLength)
|
|
||||||
}
|
|
||||||
if len([]rune(msg2.Content)) != 5 {
|
|
||||||
t.Fatalf("second chunk rune length = %d, want 5", len([]rune(msg2.Content)))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue