Merge branch 'main' into dev_features

This commit is contained in:
Orlando Chen 2026-02-24 17:05:05 +09:00
commit 454d12f5c6
6 changed files with 68 additions and 16 deletions

View file

@ -12,6 +12,9 @@
<br> <br>
<a href="https://picoclaw.io"><img src="https://img.shields.io/badge/Website-picoclaw.io-blue?style=flat&logo=google-chrome&logoColor=white" alt="Website"></a> <a href="https://picoclaw.io"><img src="https://img.shields.io/badge/Website-picoclaw.io-blue?style=flat&logo=google-chrome&logoColor=white" alt="Website"></a>
<a href="https://x.com/SipeedIO"><img src="https://img.shields.io/badge/X_(Twitter)-SipeedIO-black?style=flat&logo=x&logoColor=white" alt="Twitter"></a> <a href="https://x.com/SipeedIO"><img src="https://img.shields.io/badge/X_(Twitter)-SipeedIO-black?style=flat&logo=x&logoColor=white" alt="Twitter"></a>
<br>
<a href="./assets/wechat.png"><img src="https://img.shields.io/badge/WeChat-Group-41d56b?style=flat&logo=wechat&logoColor=white"></a>
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
</p> </p>
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **English** [中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **English**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View file

@ -698,8 +698,9 @@ func (al *AgentLoop) runLLMIteration(
// Build assistant message with tool calls // Build assistant message with tool calls
assistantMsg := providers.Message{ assistantMsg := providers.Message{
Role: "assistant", Role: "assistant",
Content: response.Content, Content: response.Content,
ReasoningContent: response.ReasoningContent,
} }
for _, tc := range normalizedToolCalls { for _, tc := range normalizedToolCalls {
argumentsJSON, _ := json.Marshal(tc.Arguments) argumentsJSON, _ := json.Marshal(tc.Arguments)

View file

@ -148,8 +148,9 @@ func parseResponse(body []byte) (*LLMResponse, error) {
var apiResponse struct { var apiResponse struct {
Choices []struct { Choices []struct {
Message struct { Message struct {
Content string `json:"content"` Content string `json:"content"`
ToolCalls []struct { ReasoningContent string `json:"reasoning_content"`
ToolCalls []struct {
ID string `json:"id"` ID string `json:"id"`
Type string `json:"type"` Type string `json:"type"`
Function *struct { Function *struct {
@ -221,10 +222,11 @@ func parseResponse(body []byte) (*LLMResponse, error) {
} }
return &LLMResponse{ return &LLMResponse{
Content: choice.Message.Content, Content: choice.Message.Content,
ToolCalls: toolCalls, ReasoningContent: choice.Message.ReasoningContent,
FinishReason: choice.FinishReason, ToolCalls: toolCalls,
Usage: apiResponse.Usage, FinishReason: choice.FinishReason,
Usage: apiResponse.Usage,
}, nil }, nil
} }

View file

@ -101,6 +101,50 @@ func TestProviderChat_ParsesToolCalls(t *testing.T) {
} }
} }
func TestProviderChat_ParsesReasoningContent(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := map[string]any{
"choices": []map[string]any{
{
"message": map[string]any{
"content": "The answer is 2",
"reasoning_content": "Let me think step by step... 1+1=2",
"tool_calls": []map[string]any{
{
"id": "call_1",
"type": "function",
"function": map[string]any{
"name": "calculator",
"arguments": "{\"expr\":\"1+1\"}",
},
},
},
},
"finish_reason": "tool_calls",
},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
p := NewProvider("key", server.URL, "")
out, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "1+1=?"}}, nil, "kimi-k2.5", nil)
if err != nil {
t.Fatalf("Chat() error = %v", err)
}
if out.ReasoningContent != "Let me think step by step... 1+1=2" {
t.Fatalf("ReasoningContent = %q, want %q", out.ReasoningContent, "Let me think step by step... 1+1=2")
}
if out.Content != "The answer is 2" {
t.Fatalf("Content = %q, want %q", out.Content, "The answer is 2")
}
if len(out.ToolCalls) != 1 {
t.Fatalf("len(ToolCalls) = %d, want 1", len(out.ToolCalls))
}
}
func TestProviderChat_HTTPError(t *testing.T) { func TestProviderChat_HTTPError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad request", http.StatusBadRequest) http.Error(w, "bad request", http.StatusBadRequest)

View file

@ -25,10 +25,11 @@ type FunctionCall struct {
} }
type LLMResponse struct { type LLMResponse struct {
Content string `json:"content"` Content string `json:"content"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"` ReasoningContent string `json:"reasoning_content,omitempty"`
FinishReason string `json:"finish_reason"` ToolCalls []ToolCall `json:"tool_calls,omitempty"`
Usage *UsageInfo `json:"usage,omitempty"` FinishReason string `json:"finish_reason"`
Usage *UsageInfo `json:"usage,omitempty"`
} }
type UsageInfo struct { type UsageInfo struct {
@ -38,10 +39,11 @@ type UsageInfo struct {
} }
type Message struct { type Message struct {
Role string `json:"role"` Role string `json:"role"`
Content string `json:"content"` Content string `json:"content"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"` ReasoningContent string `json:"reasoning_content,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
} }
type ToolDefinition struct { type ToolDefinition struct {