feat(team): integrate refined team tool and align with upstream
- Merge feat/team: integrate multi-agent orchestration tool into main. - Fix Team Tool bugs: resolve DAG goroutine leaks and token budget clamping. - Optimize Team strategies: implement text-only evaluator and partial parallel success. - Enhance observability: add structured logging and improved user summaries. - Expand configuration: add max_context_runes and reviewer_model settings. - Refactor context builder: implement cached system prompt and simplified dynamic context. - Align with upstream: remove local Embedding support and custom truncation logic for better parity. - Documentation: update tools_configuration.md with detailed Team tool guide.
This commit is contained in:
parent
e40752a374
commit
a0d91c4fa9
6 changed files with 6 additions and 123 deletions
|
|
@ -153,7 +153,7 @@ The following skills extend your capabilities. To use a skill, read its SKILL.md
|
|||
}
|
||||
|
||||
// Memory context
|
||||
memoryContext := cb.memory.GetMemoryContext("")
|
||||
memoryContext := cb.memory.GetMemoryContext()
|
||||
if memoryContext != "" {
|
||||
parts = append(parts, "# Memory\n\n"+memoryContext)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ import (
|
|||
// - Long-term memory: memory/MEMORY.md
|
||||
// - Daily notes: memory/YYYYMM/YYYYMMDD.md
|
||||
type MemoryStore struct {
|
||||
workspace string
|
||||
memoryDir string
|
||||
memoryFile string
|
||||
lastSyncTime time.Time
|
||||
workspace string
|
||||
memoryDir string
|
||||
memoryFile string
|
||||
}
|
||||
|
||||
// NewMemoryStore creates a new MemoryStore with the given workspace path.
|
||||
|
|
@ -132,7 +131,7 @@ func (ms *MemoryStore) GetRecentDailyNotes(days int) string {
|
|||
|
||||
// GetMemoryContext returns formatted memory context for the agent prompt.
|
||||
// It loads the full MEMORY.md.
|
||||
func (ms *MemoryStore) GetMemoryContext(query string) string {
|
||||
func (ms *MemoryStore) GetMemoryContext() string {
|
||||
longTerm := ms.ReadLongTerm()
|
||||
|
||||
recentNotes := ms.GetRecentDailyNotes(3)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ func ResolveCandidatesWithLookup(
|
|||
|
||||
addCandidate := func(raw string) {
|
||||
candidateRaw := strings.TrimSpace(raw)
|
||||
|
||||
if lookup != nil {
|
||||
if resolved, ok := lookup(candidateRaw); ok {
|
||||
candidateRaw = resolved
|
||||
|
|
|
|||
|
|
@ -272,58 +272,6 @@ func responsePreview(body []byte, maxLen int) string {
|
|||
return string(trimmed[:maxLen]) + "..."
|
||||
}
|
||||
|
||||
// Embed implements providers.EmbedProvider by calling /v1/embeddings.
|
||||
// The Provider satisfies EmbedProvider optionally — callers should type-assert.
|
||||
func (p *Provider) Embed(ctx context.Context, text string, model string) ([]float32, error) {
|
||||
if p.apiBase == "" {
|
||||
return nil, fmt.Errorf("API base not configured")
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"input": text,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal embed request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", p.apiBase+"/embeddings", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create embed request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if p.apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+p.apiKey)
|
||||
}
|
||||
|
||||
resp, err := p.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("embed request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read embed response: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("embed API error: status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Data []struct {
|
||||
Embedding []float32 `json:"embedding"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode embed response: %w", err)
|
||||
}
|
||||
if len(result.Data) == 0 || len(result.Data[0].Embedding) == 0 {
|
||||
return nil, fmt.Errorf("embed response contained no data")
|
||||
}
|
||||
return result.Data[0].Embedding, nil
|
||||
}
|
||||
|
||||
func parseResponse(body io.Reader) (*LLMResponse, error) {
|
||||
var apiResponse struct {
|
||||
Choices []struct {
|
||||
|
|
@ -364,7 +312,6 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
|
|||
|
||||
choice := apiResponse.Choices[0]
|
||||
toolCalls := make([]ToolCall, 0, len(choice.Message.ToolCalls))
|
||||
truncated := false
|
||||
for _, tc := range choice.Message.ToolCalls {
|
||||
arguments := make(map[string]any)
|
||||
name := ""
|
||||
|
|
@ -399,19 +346,13 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
|
|||
toolCalls = append(toolCalls, toolCall)
|
||||
}
|
||||
|
||||
finishReason := choice.FinishReason
|
||||
// Propagate truncation: if finish_reason is "length" or we detected bad JSON, mark as truncated.
|
||||
if truncated || finishReason == "length" {
|
||||
finishReason = "truncated"
|
||||
}
|
||||
|
||||
return &LLMResponse{
|
||||
Content: choice.Message.Content,
|
||||
ReasoningContent: choice.Message.ReasoningContent,
|
||||
Reasoning: choice.Message.Reasoning,
|
||||
ReasoningDetails: choice.Message.ReasoningDetails,
|
||||
ToolCalls: toolCalls,
|
||||
FinishReason: finishReason,
|
||||
FinishReason: choice.FinishReason,
|
||||
Usage: apiResponse.Usage,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -841,51 +841,3 @@ func TestSerializeMessages_StripsSystemParts(t *testing.T) {
|
|||
t.Fatal("system_parts should not appear in serialized output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderChat_RepairsTruncatedToolCall(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": "",
|
||||
"tool_calls": []map[string]any{
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": map[string]any{
|
||||
"name": "read_file",
|
||||
"arguments": "{\"path\": \"/my/file.txt\"", // missing }
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"finish_reason": "length",
|
||||
},
|
||||
},
|
||||
}
|
||||
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: "hi"}}, nil, "gpt-4o", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Chat() error = %v", err)
|
||||
}
|
||||
if len(out.ToolCalls) != 1 {
|
||||
t.Fatalf("len(ToolCalls) = %d, want 1", len(out.ToolCalls))
|
||||
}
|
||||
if out.ToolCalls[0].Name != "read_file" {
|
||||
t.Fatalf("ToolCalls[0].Name = %q, want %q", out.ToolCalls[0].Name, "read_file")
|
||||
}
|
||||
if out.ToolCalls[0].Arguments["path"] != "/my/file.txt" {
|
||||
t.Fatalf("ToolCalls[0].Arguments[path] = %v, want /my/file.txt", out.ToolCalls[0].Arguments["path"])
|
||||
}
|
||||
// Even though it was repaired, the finish reason should still be truncated because the LLM originally returned length or we truncated it?
|
||||
// Actually, if finish_reason was "length", parseResponse will set finishReason to "truncated" anyway.
|
||||
if out.FinishReason != "truncated" {
|
||||
t.Fatalf("FinishReason = %q, want %q", out.FinishReason, "truncated")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,14 +37,6 @@ type StatefulProvider interface {
|
|||
Close()
|
||||
}
|
||||
|
||||
// EmbedProvider is an optional interface for providers that support text embeddings.
|
||||
// Not all providers implement this; use a type assertion to check.
|
||||
type EmbedProvider interface {
|
||||
// Embed converts text into a float32 vector using the given embedding model.
|
||||
// Returns an error if the provider does not support embeddings or the call fails.
|
||||
Embed(ctx context.Context, text string, model string) ([]float32, error)
|
||||
}
|
||||
|
||||
// ThinkingCapable is an optional interface for providers that support
|
||||
// extended thinking (e.g. Anthropic). Used by the agent loop to warn
|
||||
// when thinking_level is configured but the active provider cannot use it.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue