Fix: openrouter func ctx resp
This commit is contained in:
35
bot.go
35
bot.go
@@ -44,6 +44,7 @@ var (
|
|||||||
ragger *rag.RAG
|
ragger *rag.RAG
|
||||||
chunkParser ChunkParser
|
chunkParser ChunkParser
|
||||||
lastToolCall *models.FuncCall
|
lastToolCall *models.FuncCall
|
||||||
|
lastToolCallID string // Store the ID of the most recent tool call
|
||||||
//nolint:unused // TTS_ENABLED conditionally uses this
|
//nolint:unused // TTS_ENABLED conditionally uses this
|
||||||
orator extra.Orator
|
orator extra.Orator
|
||||||
asr extra.STT
|
asr extra.STT
|
||||||
@@ -290,6 +291,8 @@ func sendMsgToLLM(body io.Reader) {
|
|||||||
openAIToolChan <- chunk.ToolChunk
|
openAIToolChan <- chunk.ToolChunk
|
||||||
if chunk.FuncName != "" {
|
if chunk.FuncName != "" {
|
||||||
lastToolCall.Name = chunk.FuncName
|
lastToolCall.Name = chunk.FuncName
|
||||||
|
// Store the tool call ID for the response
|
||||||
|
lastToolCallID = chunk.ToolID
|
||||||
}
|
}
|
||||||
interrupt:
|
interrupt:
|
||||||
if interruptResp { // read bytes, so it would not get into beginning of the next req
|
if interruptResp { // read bytes, so it would not get into beginning of the next req
|
||||||
@@ -492,14 +495,40 @@ func findCall(msg, toolCall string, tv *tview.TextView) {
|
|||||||
f, ok := fnMap[fc.Name]
|
f, ok := fnMap[fc.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
m := fc.Name + " is not implemented"
|
m := fc.Name + " is not implemented"
|
||||||
chatRound(m, cfg.ToolRole, tv, false, false)
|
// Create tool response message with the proper tool_call_id
|
||||||
|
toolResponseMsg := models.RoleMsg{
|
||||||
|
Role: cfg.ToolRole,
|
||||||
|
Content: m,
|
||||||
|
ToolCallID: lastToolCallID, // Use the stored tool call ID
|
||||||
|
}
|
||||||
|
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||||
|
// Clear the stored tool call ID after using it
|
||||||
|
lastToolCallID = ""
|
||||||
|
|
||||||
|
// Trigger the assistant to continue processing with the new tool response
|
||||||
|
// by calling chatRound with empty content to continue the assistant's response
|
||||||
|
chatRound("", cfg.AssistantRole, tv, false, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp := f(fc.Args)
|
resp := f(fc.Args)
|
||||||
toolMsg := fmt.Sprintf("tool response: %+v", string(resp))
|
toolMsg := string(resp) // Remove the "tool response: " prefix and %+v formatting
|
||||||
fmt.Fprintf(tv, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n",
|
fmt.Fprintf(tv, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n",
|
||||||
"\n", len(chatBody.Messages), cfg.ToolRole, toolMsg)
|
"\n", len(chatBody.Messages), cfg.ToolRole, toolMsg)
|
||||||
chatRound(toolMsg, cfg.ToolRole, tv, false, false)
|
|
||||||
|
// Create tool response message with the proper tool_call_id
|
||||||
|
toolResponseMsg := models.RoleMsg{
|
||||||
|
Role: cfg.ToolRole,
|
||||||
|
Content: toolMsg,
|
||||||
|
ToolCallID: lastToolCallID, // Use the stored tool call ID
|
||||||
|
}
|
||||||
|
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||||
|
|
||||||
|
// Clear the stored tool call ID after using it
|
||||||
|
lastToolCallID = ""
|
||||||
|
|
||||||
|
// Trigger the assistant to continue processing with the new tool response
|
||||||
|
// by calling chatRound with empty content to continue the assistant's response
|
||||||
|
chatRound("", cfg.AssistantRole, tv, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatToTextSlice(showSys bool) []string {
|
func chatToTextSlice(showSys bool) []string {
|
||||||
|
|||||||
14
llm.go
14
llm.go
@@ -160,11 +160,14 @@ func (op OpenAIer) ParseChunk(data []byte) (*models.TextChunk, error) {
|
|||||||
Chunk: llmchunk.Choices[len(llmchunk.Choices)-1].Delta.Content,
|
Chunk: llmchunk.Choices[len(llmchunk.Choices)-1].Delta.Content,
|
||||||
}
|
}
|
||||||
if len(llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls) > 0 {
|
if len(llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls) > 0 {
|
||||||
resp.ToolChunk = llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls[0].Function.Arguments
|
toolCall := llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls[0]
|
||||||
fname := llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls[0].Function.Name
|
resp.ToolChunk = toolCall.Function.Arguments
|
||||||
|
fname := toolCall.Function.Name
|
||||||
if fname != "" {
|
if fname != "" {
|
||||||
resp.FuncName = fname
|
resp.FuncName = fname
|
||||||
}
|
}
|
||||||
|
// Capture the tool call ID if available
|
||||||
|
resp.ToolID = toolCall.ID
|
||||||
}
|
}
|
||||||
if llmchunk.Choices[len(llmchunk.Choices)-1].FinishReason == "stop" {
|
if llmchunk.Choices[len(llmchunk.Choices)-1].FinishReason == "stop" {
|
||||||
if resp.Chunk != "" {
|
if resp.Chunk != "" {
|
||||||
@@ -471,11 +474,14 @@ func (or OpenRouterChat) ParseChunk(data []byte) (*models.TextChunk, error) {
|
|||||||
|
|
||||||
// Handle tool calls similar to OpenAIer
|
// Handle tool calls similar to OpenAIer
|
||||||
if len(llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls) > 0 {
|
if len(llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls) > 0 {
|
||||||
resp.ToolChunk = llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls[0].Function.Arguments
|
toolCall := llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls[0]
|
||||||
fname := llmchunk.Choices[len(llmchunk.Choices)-1].Delta.ToolCalls[0].Function.Name
|
resp.ToolChunk = toolCall.Function.Arguments
|
||||||
|
fname := toolCall.Function.Name
|
||||||
if fname != "" {
|
if fname != "" {
|
||||||
resp.FuncName = fname
|
resp.FuncName = fname
|
||||||
}
|
}
|
||||||
|
// Capture the tool call ID if available
|
||||||
|
resp.ToolID = toolCall.ID
|
||||||
}
|
}
|
||||||
if resp.ToolChunk != "" {
|
if resp.ToolChunk != "" {
|
||||||
resp.ToolResp = true
|
resp.ToolResp = true
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FuncCall struct {
|
type FuncCall struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Args map[string]string `json:"args"`
|
Args map[string]string `json:"args"`
|
||||||
}
|
}
|
||||||
@@ -39,6 +40,7 @@ type ToolDeltaFunc struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ToolDeltaResp struct {
|
type ToolDeltaResp struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
Function ToolDeltaFunc `json:"function"`
|
Function ToolDeltaFunc `json:"function"`
|
||||||
}
|
}
|
||||||
@@ -70,6 +72,7 @@ type TextChunk struct {
|
|||||||
Finished bool
|
Finished bool
|
||||||
ToolResp bool
|
ToolResp bool
|
||||||
FuncName string
|
FuncName string
|
||||||
|
ToolID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TextContentPart struct {
|
type TextContentPart struct {
|
||||||
@@ -86,10 +89,11 @@ type ImageContentPart struct {
|
|||||||
|
|
||||||
// RoleMsg represents a message with content that can be either a simple string or structured content parts
|
// RoleMsg represents a message with content that can be either a simple string or structured content parts
|
||||||
type RoleMsg struct {
|
type RoleMsg struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"-"`
|
Content string `json:"-"`
|
||||||
ContentParts []interface{} `json:"-"`
|
ContentParts []interface{} `json:"-"`
|
||||||
hasContentParts bool // Flag to indicate which content type to marshal
|
ToolCallID string `json:"tool_call_id,omitempty"` // For tool response messages
|
||||||
|
hasContentParts bool // Flag to indicate which content type to marshal
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements custom JSON marshaling for RoleMsg
|
// MarshalJSON implements custom JSON marshaling for RoleMsg
|
||||||
@@ -97,21 +101,25 @@ func (m RoleMsg) MarshalJSON() ([]byte, error) {
|
|||||||
if m.hasContentParts {
|
if m.hasContentParts {
|
||||||
// Use structured content format
|
// Use structured content format
|
||||||
aux := struct {
|
aux := struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content []interface{} `json:"content"`
|
Content []interface{} `json:"content"`
|
||||||
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
}{
|
}{
|
||||||
Role: m.Role,
|
Role: m.Role,
|
||||||
Content: m.ContentParts,
|
Content: m.ContentParts,
|
||||||
|
ToolCallID: m.ToolCallID,
|
||||||
}
|
}
|
||||||
return json.Marshal(aux)
|
return json.Marshal(aux)
|
||||||
} else {
|
} else {
|
||||||
// Use simple content format
|
// Use simple content format
|
||||||
aux := struct {
|
aux := struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
}{
|
}{
|
||||||
Role: m.Role,
|
Role: m.Role,
|
||||||
Content: m.Content,
|
Content: m.Content,
|
||||||
|
ToolCallID: m.ToolCallID,
|
||||||
}
|
}
|
||||||
return json.Marshal(aux)
|
return json.Marshal(aux)
|
||||||
}
|
}
|
||||||
@@ -121,26 +129,30 @@ func (m RoleMsg) MarshalJSON() ([]byte, error) {
|
|||||||
func (m *RoleMsg) UnmarshalJSON(data []byte) error {
|
func (m *RoleMsg) UnmarshalJSON(data []byte) error {
|
||||||
// First, try to unmarshal as structured content format
|
// First, try to unmarshal as structured content format
|
||||||
var structured struct {
|
var structured struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content []interface{} `json:"content"`
|
Content []interface{} `json:"content"`
|
||||||
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &structured); err == nil && len(structured.Content) > 0 {
|
if err := json.Unmarshal(data, &structured); err == nil && len(structured.Content) > 0 {
|
||||||
m.Role = structured.Role
|
m.Role = structured.Role
|
||||||
m.ContentParts = structured.Content
|
m.ContentParts = structured.Content
|
||||||
|
m.ToolCallID = structured.ToolCallID
|
||||||
m.hasContentParts = true
|
m.hasContentParts = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, unmarshal as simple content format
|
// Otherwise, unmarshal as simple content format
|
||||||
var simple struct {
|
var simple struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &simple); err != nil {
|
if err := json.Unmarshal(data, &simple); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.Role = simple.Role
|
m.Role = simple.Role
|
||||||
m.Content = simple.Content
|
m.Content = simple.Content
|
||||||
|
m.ToolCallID = simple.ToolCallID
|
||||||
m.hasContentParts = false
|
m.hasContentParts = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user