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
|
// Memory context
|
||||||
memoryContext := cb.memory.GetMemoryContext("")
|
memoryContext := cb.memory.GetMemoryContext()
|
||||||
if memoryContext != "" {
|
if memoryContext != "" {
|
||||||
parts = append(parts, "# Memory\n\n"+memoryContext)
|
parts = append(parts, "# Memory\n\n"+memoryContext)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ type MemoryStore struct {
|
||||||
workspace string
|
workspace string
|
||||||
memoryDir string
|
memoryDir string
|
||||||
memoryFile string
|
memoryFile string
|
||||||
lastSyncTime time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemoryStore creates a new MemoryStore with the given workspace path.
|
// 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.
|
// GetMemoryContext returns formatted memory context for the agent prompt.
|
||||||
// It loads the full MEMORY.md.
|
// It loads the full MEMORY.md.
|
||||||
func (ms *MemoryStore) GetMemoryContext(query string) string {
|
func (ms *MemoryStore) GetMemoryContext() string {
|
||||||
longTerm := ms.ReadLongTerm()
|
longTerm := ms.ReadLongTerm()
|
||||||
|
|
||||||
recentNotes := ms.GetRecentDailyNotes(3)
|
recentNotes := ms.GetRecentDailyNotes(3)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ func ResolveCandidatesWithLookup(
|
||||||
|
|
||||||
addCandidate := func(raw string) {
|
addCandidate := func(raw string) {
|
||||||
candidateRaw := strings.TrimSpace(raw)
|
candidateRaw := strings.TrimSpace(raw)
|
||||||
|
|
||||||
if lookup != nil {
|
if lookup != nil {
|
||||||
if resolved, ok := lookup(candidateRaw); ok {
|
if resolved, ok := lookup(candidateRaw); ok {
|
||||||
candidateRaw = resolved
|
candidateRaw = resolved
|
||||||
|
|
|
||||||
|
|
@ -272,58 +272,6 @@ func responsePreview(body []byte, maxLen int) string {
|
||||||
return string(trimmed[:maxLen]) + "..."
|
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) {
|
func parseResponse(body io.Reader) (*LLMResponse, error) {
|
||||||
var apiResponse struct {
|
var apiResponse struct {
|
||||||
Choices []struct {
|
Choices []struct {
|
||||||
|
|
@ -364,7 +312,6 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
|
||||||
|
|
||||||
choice := apiResponse.Choices[0]
|
choice := apiResponse.Choices[0]
|
||||||
toolCalls := make([]ToolCall, 0, len(choice.Message.ToolCalls))
|
toolCalls := make([]ToolCall, 0, len(choice.Message.ToolCalls))
|
||||||
truncated := false
|
|
||||||
for _, tc := range choice.Message.ToolCalls {
|
for _, tc := range choice.Message.ToolCalls {
|
||||||
arguments := make(map[string]any)
|
arguments := make(map[string]any)
|
||||||
name := ""
|
name := ""
|
||||||
|
|
@ -399,19 +346,13 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
|
||||||
toolCalls = append(toolCalls, toolCall)
|
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{
|
return &LLMResponse{
|
||||||
Content: choice.Message.Content,
|
Content: choice.Message.Content,
|
||||||
ReasoningContent: choice.Message.ReasoningContent,
|
ReasoningContent: choice.Message.ReasoningContent,
|
||||||
Reasoning: choice.Message.Reasoning,
|
Reasoning: choice.Message.Reasoning,
|
||||||
ReasoningDetails: choice.Message.ReasoningDetails,
|
ReasoningDetails: choice.Message.ReasoningDetails,
|
||||||
ToolCalls: toolCalls,
|
ToolCalls: toolCalls,
|
||||||
FinishReason: finishReason,
|
FinishReason: choice.FinishReason,
|
||||||
Usage: apiResponse.Usage,
|
Usage: apiResponse.Usage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -841,51 +841,3 @@ func TestSerializeMessages_StripsSystemParts(t *testing.T) {
|
||||||
t.Fatal("system_parts should not appear in serialized output")
|
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()
|
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
|
// ThinkingCapable is an optional interface for providers that support
|
||||||
// extended thinking (e.g. Anthropic). Used by the agent loop to warn
|
// extended thinking (e.g. Anthropic). Used by the agent loop to warn
|
||||||
// when thinking_level is configured but the active provider cannot use it.
|
// when thinking_level is configured but the active provider cannot use it.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue