fix: avoid duplicate summarization token limits

This commit is contained in:
Ed1s0nZ committed 2026-09-23 18:02:00 +08:00
1 parent a89f21b49e
commit bbfcb87103
5 files changed
+65 -15

No files matched your search

+2 -2
View File
@@ -62,7 +62,7 @@ const (
DefaultLatestUserMessageMaxRunes = 48000
DefaultLatestUserMessageHeadRunes = 24000
DefaultLatestUserMessageTailRunes = 24000
DefaultSummarizationOutputReserveTokens = 8192
DefaultSummarizationOutputReserveTokens = 40960
)
// ProjectConfig 项目黑板(跨对话共享事实)配置。
@@ -277,7 +277,7 @@ type MultiAgentEinoMiddlewareConfig struct {
ReductionSubAgents bool `yaml:"reduction_sub_agents,omitempty" json:"reduction_sub_agents,omitempty"` // also attach to sub-agents
// SummarizationTriggerRatio controls summarization trigger threshold as max_total_tokens * ratio (default 0.8).
SummarizationTriggerRatio float64 `yaml:"summarization_trigger_ratio,omitempty" json:"summarization_trigger_ratio,omitempty"`
// SummarizationOutputReserveTokens reserves completion headroom for the summarization model call (default 8192).
// SummarizationOutputReserveTokens reserves completion headroom for the summarization model call (default 40960).
SummarizationOutputReserveTokens int `yaml:"summarization_output_reserve_tokens,omitempty" json:"summarization_output_reserve_tokens,omitempty"`
// SummarizationEmitInternalEvents controls middleware internal event emission (default true).
SummarizationEmitInternalEvents *bool `yaml:"summarization_emit_internal_events,omitempty" json:"summarization_emit_internal_events,omitempty"`
+9 -4
View File
@@ -301,9 +301,13 @@ func newEinoSummarizationModelOptions(outputReserve int, modelName, kind string,
if strings.TrimSpace(kind) != "" && kind != "classic" {
label = "eino " + kind + " summarization generate request"
}
return []model.Option{
model.WithMaxTokens(outputReserve),
einoopenai.WithMaxCompletionTokens(outputReserve),
opts := make([]model.Option, 0, 4)
if oa != nil && isEinoAgenticClaudeProvider(oa.Provider) {
opts = append(opts, model.WithMaxTokens(outputReserve))
} else {
opts = append(opts, einoopenai.WithMaxCompletionTokens(outputReserve))
}
opts = append(opts,
einoopenai.WithExtraHeader(map[string]string{
copenai.SummarizationRequestHeader: "1",
}),
@@ -317,7 +321,8 @@ func newEinoSummarizationModelOptions(outputReserve int, modelName, kind string,
}
return stripReasoningFromSummarizationPayload(rawBody, oa)
}),
}
)
return opts
}
// summarizationInputBudgetOpts controls spill/truncation behavior when a round alone exceeds budget.
@@ -260,12 +260,13 @@ func TestClaudeSummaryLargeBudgetStreamsThroughNativeSDK(t *testing.T) {
}))
defer server.Close()
factory := newEinoAgenticChatModelFactory(server.Client(), nil, nil)
native, err := factory(ctx, config.OpenAIConfig{Provider: "claude", APIKey: "test-key", BaseURL: server.URL, Model: "claude-sonnet-4-20250514"}, einoModelModeNormal)
oa := config.OpenAIConfig{Provider: "claude", APIKey: "test-key", BaseURL: server.URL, Model: "claude-sonnet-4-20250514"}
native, err := factory(ctx, oa, einoModelModeNormal)
if err != nil {
t.Fatal(err)
}
input := EinoMessagesToAgentic([]*schema.Message{schema.UserMessage("summarize history")})
opts := newEinoSummarizationModelOptions(64000, "claude-sonnet-4-20250514", "agentic", nil, nil)
opts := newEinoSummarizationModelOptions(64000, "claude-sonnet-4-20250514", "agentic", &oa, nil)
if _, err = native.Generate(ctx, input, opts...); err == nil || !strings.Contains(err.Error(), "streaming is required") {
t.Fatalf("expected original SDK rejection, got %v", err)
}
@@ -1,12 +1,18 @@
package multiagent
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"cyberstrike-ai/internal/config"
einoopenai "github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
)
func TestStripReasoningFromSummarizationPayload(t *testing.T) {
@@ -93,14 +99,52 @@ func TestStripReasoningFromSummarizationPayloadHonorsOpenAICompatProfileForNonDe
}
}
func TestEinoSummarizationModelOptionsSetCommonMaxTokens(t *testing.T) {
const outputReserve = 4096
func TestEinoSummarizationModelOptionsSetOnlyMaxCompletionTokens(t *testing.T) {
const outputReserve = 40960
opts := newEinoSummarizationModelOptions(outputReserve, "minimax-m3", "agentic", nil, nil)
common := model.GetCommonOptions(nil, opts...)
if common == nil || common.MaxTokens == nil {
t.Fatal("expected summarization options to set common max_tokens")
if common != nil && common.MaxTokens != nil {
t.Fatalf("common max_tokens = %d, want unset", *common.MaxTokens)
}
if *common.MaxTokens != outputReserve {
t.Fatalf("max_tokens = %d, want %d", *common.MaxTokens, outputReserve)
bodyCh := make(chan map[string]any, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Errorf("decode request body: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
bodyCh <- body
w.Header().Set("Content-Type", "text/event-stream")
w.Write([]byte("data: {\"id\":\"test\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"summary\"},\"finish_reason\":null}]}\n\n"))
w.Write([]byte("data: {\"id\":\"test\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n"))
w.Write([]byte("data: [DONE]\n\n"))
}))
defer server.Close()
chatModel, err := einoopenai.NewChatModel(context.Background(), &einoopenai.ChatModelConfig{
APIKey: "test-key",
BaseURL: server.URL,
Model: "gpt-4o",
HTTPClient: server.Client(),
})
if err != nil {
t.Fatal(err)
}
out, err := newNonEmptySummaryChatModel(chatModel).Generate(context.Background(), []*schema.Message{schema.UserMessage("summarize")}, opts...)
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(out.Content) != "summary" {
t.Fatalf("summary content = %q", out.Content)
}
body := <-bodyCh
if _, ok := body["max_tokens"]; ok {
t.Fatalf("request contained max_tokens: %#v", body)
}
if got, ok := body["max_completion_tokens"].(float64); !ok || int(got) != outputReserve {
t.Fatalf("max_completion_tokens = %#v, want %d", body["max_completion_tokens"], outputReserve)
}
}