update with corrent transcriber and leave_tool

This commit is contained in:
Huaaudio 2026-03-21 07:18:14 +01:00
parent e49f65a2be
commit 652bb6bd4c
6 changed files with 77 additions and 0 deletions

View file

@ -246,6 +246,9 @@ func registerSharedTools(
agent.Tools.Register(messageTool)
}
// Always register Voice Leave Tool (it inherently checks if channel == "discord")
agent.Tools.Register(tools.NewVoiceLeaveTool(msgBus))
// Send file tool (outbound media via MediaStore — store injected later by SetMediaStore)
if cfg.Tools.IsToolEnabled("send_file") {
sendFileTool := tools.NewSendFileTool(

View file

@ -10,8 +10,10 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/utils"
)
@ -166,3 +168,19 @@ func (t *GroqTranscriber) doRequest(ctx context.Context, requestBody *bytes.Buff
func (t *GroqTranscriber) Name() string {
return "groq"
}
// DetectTranscriber inspects cfg and returns the appropriate Transcriber, or
// nil if no supported transcription provider is configured.
func DetectTranscriber(cfg *config.Config) Transcriber {
// Direct Groq provider config takes priority.
if key := cfg.Providers.Groq.APIKey; key != "" {
return NewGroqTranscriber(key)
}
// Fall back to any model-list entry that uses the groq/ protocol or is explicitly named groq.
for _, mc := range cfg.ModelList {
if (strings.HasPrefix(mc.Model, "groq/") || mc.ModelName == "groq" || mc.Model == "whisper-large-v3-turbo") && mc.APIKey != "" {
return NewGroqTranscriber(mc.APIKey)
}
}
return nil
}

View file

@ -70,6 +70,7 @@ type AudioChunk struct {
// VoiceControl represents state or commands for voice sessions.
type VoiceControl struct {
SessionID string `json:"session_id"`
ChatID string `json:"chat_id"`
Type string `json:"type"` // "state", "command"
Action string `json:"action"` // "idle", "listening", "start", "stop", "leave"
}

View file

@ -23,6 +23,7 @@ func (c *DiscordChannel) handleVoiceCommand(s *discordgo.Session, m *discordgo.M
logger.InfoCF("discord", "Joining voice channel", map[string]any{"channel": vs.ChannelID})
vc, err := s.ChannelVoiceJoin(c.ctx, m.GuildID, vs.ChannelID, false, false)
vc, err := s.ChannelVoiceJoin(c.ctx, m.GuildID, vs.ChannelID, false, false)
if err != nil {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Failed to join voice channel: %v", err))
return true
@ -34,6 +35,7 @@ func (c *DiscordChannel) handleVoiceCommand(s *discordgo.Session, m *discordgo.M
} else if m.Content == "!vc leave" {
vc, exists := s.VoiceConnections[m.GuildID]
if exists && vc != nil {
vc.Disconnect(c.ctx)
vc.Disconnect(c.ctx)
s.ChannelMessageSend(m.ChannelID, "Left Voice Channel.")
} else {

52
pkg/tools/voice_leave.go Normal file
View file

@ -0,0 +1,52 @@
package tools
import (
"context"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/logger"
)
type VoiceLeaveTool struct {
bus *bus.MessageBus
}
func NewVoiceLeaveTool(mb *bus.MessageBus) *VoiceLeaveTool {
return &VoiceLeaveTool{bus: mb}
}
func (t *VoiceLeaveTool) Name() string {
return "voice_leave"
}
func (t *VoiceLeaveTool) Description() string {
return "Disconnects the bot from the current voice channel. Use this tool when the user says goodbye or explicitly asks you to leave the voice chat."
}
func (t *VoiceLeaveTool) Parameters() map[string]any {
return map[string]any{
"type": "object",
"properties": map[string]any{},
}
}
func (t *VoiceLeaveTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
channel := ToolChannel(ctx)
chatID := ToolChatID(ctx)
if channel != "discord" {
return &ToolResult{ForLLM: "Can only leave voice channels on Discord", IsError: true}
}
t.bus.PublishVoiceControl(ctx, bus.VoiceControl{
ChatID: chatID,
Type: "command",
Action: "leave",
})
logger.InfoCF("agent", "Voice command triggered via tool: leave", map[string]any{"chat_id": chatID})
return &ToolResult{
ForLLM: "Successfully sent disconnect command to voice adapter.",
}
}

View file

@ -243,6 +243,7 @@ func (a *Agent) processUtterance(ctx context.Context, acc *speechAccumulator) {
SenderID: acc.speakerID,
ChatID: acc.chatID,
Content: res.Text + oralPrompt,
Content: res.Text + oralPrompt,
Peer: bus.Peer{Kind: "channel", ID: acc.chatID},
Metadata: map[string]string{
"is_voice": "true",