Changeset 0.7.2 (#40)
This commit is contained in:
@@ -36,13 +36,32 @@ func (p *OpenAIProvider) ChatCompletion(ctx context.Context, cfg ProviderConfig,
|
||||
return nil, fmt.Errorf("no choices in response")
|
||||
}
|
||||
|
||||
return &CompletionResponse{
|
||||
result := &CompletionResponse{
|
||||
Content: resp.Choices[0].Message.Content,
|
||||
Model: resp.Model,
|
||||
FinishReason: resp.Choices[0].FinishReason,
|
||||
InputTokens: resp.Usage.PromptTokens,
|
||||
OutputTokens: resp.Usage.CompletionTokens,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Extract tool calls if present
|
||||
if len(resp.Choices[0].Message.ToolCalls) > 0 {
|
||||
for _, tc := range resp.Choices[0].Message.ToolCalls {
|
||||
result.ToolCalls = append(result.ToolCalls, ToolCall{
|
||||
ID: tc.ID,
|
||||
Type: tc.Type,
|
||||
Function: FunctionCall{
|
||||
Name: tc.Function.Name,
|
||||
Arguments: tc.Function.Arguments,
|
||||
},
|
||||
})
|
||||
}
|
||||
if result.FinishReason == "" {
|
||||
result.FinishReason = "tool_calls"
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ── Stream Completion ───────────────────────
|
||||
@@ -61,6 +80,9 @@ func (p *OpenAIProvider) StreamCompletion(ctx context.Context, cfg ProviderConfi
|
||||
defer close(ch)
|
||||
defer body.Close()
|
||||
|
||||
// Accumulate tool calls across stream chunks
|
||||
toolCallMap := map[int]*ToolCall{} // index → accumulated call
|
||||
|
||||
scanner := bufio.NewScanner(body)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
@@ -84,13 +106,45 @@ func (p *OpenAIProvider) StreamCompletion(ctx context.Context, cfg ProviderConfi
|
||||
continue
|
||||
}
|
||||
|
||||
ev := StreamEvent{
|
||||
Delta: chunk.Choices[0].Delta.Content,
|
||||
Model: chunk.Model,
|
||||
FinishReason: chunk.Choices[0].FinishReason,
|
||||
choice := chunk.Choices[0]
|
||||
|
||||
// Accumulate tool call deltas
|
||||
for _, tc := range choice.Delta.ToolCalls {
|
||||
idx := 0
|
||||
if tc.Index != nil {
|
||||
idx = *tc.Index
|
||||
}
|
||||
|
||||
if existing, ok := toolCallMap[idx]; ok {
|
||||
// Append arguments fragment
|
||||
existing.Function.Arguments += tc.Function.Arguments
|
||||
} else {
|
||||
// First chunk for this tool — has ID and name
|
||||
toolCallMap[idx] = &ToolCall{
|
||||
ID: tc.ID,
|
||||
Type: "function",
|
||||
Function: FunctionCall{
|
||||
Name: tc.Function.Name,
|
||||
Arguments: tc.Function.Arguments,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ev := StreamEvent{
|
||||
Delta: choice.Delta.Content,
|
||||
Model: chunk.Model,
|
||||
FinishReason: choice.FinishReason,
|
||||
}
|
||||
|
||||
if ev.FinishReason != "" {
|
||||
ev.Done = true
|
||||
// Attach accumulated tool calls on final event
|
||||
if ev.FinishReason == "tool_calls" && len(toolCallMap) > 0 {
|
||||
for _, tc := range toolCallMap {
|
||||
ev.ToolCalls = append(ev.ToolCalls, *tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ch <- ev
|
||||
@@ -178,11 +232,46 @@ func (p *OpenAIProvider) doRequest(ctx context.Context, cfg ProviderConfig, req
|
||||
oaiReq.TopP = req.TopP
|
||||
}
|
||||
|
||||
// Convert tools
|
||||
for _, t := range req.Tools {
|
||||
oaiReq.Tools = append(oaiReq.Tools, openaiToolDef{
|
||||
Type: t.Type,
|
||||
Function: openaiToolDefFunction{
|
||||
Name: t.Function.Name,
|
||||
Description: t.Function.Description,
|
||||
Parameters: t.Function.Parameters,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Convert messages — handle all roles including tool
|
||||
for _, m := range req.Messages {
|
||||
oaiReq.Messages = append(oaiReq.Messages, openaiMessage{
|
||||
oaiMsg := openaiMessage{
|
||||
Role: m.Role,
|
||||
Content: m.Content,
|
||||
})
|
||||
}
|
||||
|
||||
// Assistant messages with tool calls
|
||||
if len(m.ToolCalls) > 0 {
|
||||
for _, tc := range m.ToolCalls {
|
||||
oaiMsg.ToolCalls = append(oaiMsg.ToolCalls, openaiToolCall{
|
||||
ID: tc.ID,
|
||||
Type: tc.Type,
|
||||
Function: openaiToolCallFunction{
|
||||
Name: tc.Function.Name,
|
||||
Arguments: tc.Function.Arguments,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tool result messages
|
||||
if m.Role == "tool" {
|
||||
oaiMsg.ToolCallID = m.ToolCallID
|
||||
oaiMsg.Name = m.Name
|
||||
}
|
||||
|
||||
oaiReq.Messages = append(oaiReq.Messages, oaiMsg)
|
||||
}
|
||||
|
||||
body, err := json.Marshal(oaiReq)
|
||||
@@ -220,8 +309,34 @@ func (p *OpenAIProvider) doRequest(ctx context.Context, cfg ProviderConfig, req
|
||||
// ── OpenAI Wire Types ───────────────────────
|
||||
|
||||
type openaiMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ToolCalls []openaiToolCall `json:"tool_calls,omitempty"` // assistant requesting tools
|
||||
ToolCallID string `json:"tool_call_id,omitempty"` // role=tool result
|
||||
Name string `json:"name,omitempty"` // tool name for role=tool
|
||||
}
|
||||
|
||||
type openaiToolCallFunction struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Arguments string `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type openaiToolCall struct {
|
||||
Index *int `json:"index,omitempty"` // present in streaming deltas
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"` // "function"
|
||||
Function openaiToolCallFunction `json:"function"`
|
||||
}
|
||||
|
||||
type openaiToolDefFunction struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parameters json.RawMessage `json:"parameters"`
|
||||
}
|
||||
|
||||
type openaiToolDef struct {
|
||||
Type string `json:"type"` // "function"
|
||||
Function openaiToolDefFunction `json:"function"`
|
||||
}
|
||||
|
||||
type openaiChatRequest struct {
|
||||
@@ -231,6 +346,7 @@ type openaiChatRequest struct {
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
Stream bool `json:"stream"`
|
||||
Tools []openaiToolDef `json:"tools,omitempty"`
|
||||
}
|
||||
|
||||
type openaiChatResponse struct {
|
||||
@@ -249,7 +365,8 @@ type openaiStreamChunk struct {
|
||||
Model string `json:"model"`
|
||||
Choices []struct {
|
||||
Delta struct {
|
||||
Content string `json:"content"`
|
||||
Content string `json:"content"`
|
||||
ToolCalls []openaiToolCall `json:"tool_calls,omitempty"`
|
||||
} `json:"delta"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
} `json:"choices"`
|
||||
|
||||
Reference in New Issue
Block a user