feat(adk): add agent_with_summarization based on agent_middleware (#135)

drew/english
IPender 6 months ago committed by GitHub
parent d41b497bdc
commit 85592f893f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,118 @@
/*
* Copyright 2025 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"context"
"log"
"time"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino-examples/adk/common/model"
"github.com/cloudwego/eino-examples/adk/common/prints"
"github.com/cloudwego/eino-examples/adk/common/trace"
"github.com/cloudwego/eino-examples/adk/intro/agent_with_summarization/summarization"
"github.com/cloudwego/eino-examples/internal/logs"
)
const (
summaryMaxTokensBefore = 10 * 1024
summaryMaxTokensRecent = 2 * 1024
agentMaxIterations = 30
)
func main() {
ctx := context.Background()
a, err := newAgent(ctx)
if err != nil {
logs.Fatalf("create agent failed, err=%v", err)
}
traceCloseFn, startSpanFn := trace.AppendCozeLoopCallbackIfConfigured(ctx)
defer traceCloseFn(ctx)
runner := adk.NewRunner(ctx, adk.RunnerConfig{
EnableStreaming: true, // you can disable streaming here
Agent: a,
})
query := `Write a very long report on the history of artificial intelligence.`
ctx, endSpanFn := startSpanFn(ctx, "Agent", query)
iter := runner.Query(ctx, query)
var lastMessage adk.Message
for {
event, ok := iter.Next()
if !ok {
break
}
if event.Err != nil {
log.Fatal(event.Err)
}
prints.Event(event)
if event.Output != nil {
lastMessage, _, err = adk.GetMessage(event)
}
}
endSpanFn(ctx, lastMessage)
// wait for all span to be ended
time.Sleep(10 * time.Second)
}
func newAgent(ctx context.Context) (adk.Agent, error) {
sumMW, err := summarization.New(ctx, &summarization.Config{
Model: model.NewChatModel(),
MaxTokensBeforeSummary: summaryMaxTokensBefore,
MaxTokensForRecentMessages: summaryMaxTokensRecent,
})
if err != nil {
return nil, err
}
a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "main_agent",
Description: "A long-form report assistant",
Instruction: `You are a long-form report writer working in ReAct mode.
Think step by step, call tools to expand content by repeating paragraphs, then synthesize a cohesive response.
one time call one tool, do not call multiple tools in one turn.
Each tool call should indicate the call number. After 20 tool calls, produce a final summary.`,
Model: model.NewChatModel(),
Middlewares: []adk.AgentMiddleware{sumMW},
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{
NewRepeatSectionsTool(),
},
},
},
MaxIterations: agentMaxIterations,
})
if err != nil {
return nil, err
}
return a, nil
}

@ -0,0 +1,77 @@
/*
* Copyright 2025 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package summarization
const DefaultMaxTokensBeforeSummary = 128 * 1024
const DefaultMaxTokensForRecentMessages = 25 * 1024 // 20% of DefaultMaxTokensBeforeSummary
const PromptOfSummary = `<role>
Conversation Summarization Assistant for Multi-turn LLM Agent
</role>
<primary_objective>
Summarize the older portion of the conversation history into a concise, accurate, and information-rich context summary.
The summary must preserve essential reasoning, actions, outcomes, and lessons learned,
allowing the agent to continue reasoning seamlessly without re-accessing the raw conversation data.
</primary_objective>
<contextual_goals>
- Include major progress, decisions made, reasoning steps, intermediate or final results, and lessons (both successes and failures).
- Emphasize failed attempts, misunderstandings, and improvements or adjustments that followed.
- Exclude irrelevant details, casual talk, and redundant confirmations.
- Maintain consistency with the current System Prompt and the users long-term goals.
</contextual_goals>
<instructions>
1. You will receive five tagged sections:
- The **system_prompt tag** provides the current System Prompt (for reference only, do not summarize).
- The **user_messages tag** contains early or persistent user instructions, preferences, and goals. Use it to maintain alignment with the user's long-term intent(for reference only, do not summarize).
- The **previous_summary tag** contains the existing long-term summary, if available.
- The **older_messages tag** includes earlier conversation messages to be summarized.
- The **recent_messages tag** contains the most recent conversation window (for reference only, do not summarize).
2. Your task:
- Merge the content from the previous_summary tag and the older_messages tag into a new refined long-term summary.
- When summarizing, integrate the key takeaways, decisions, lessons, and relevant state information.
- Use the user_messages tag to ensure the summary preserves the user's persistent intent and constraints (ignore transient chit-chat).
- Use the recent_messages tag only to maintain temporal and contextual continuity across turns.
3. Output requirements:
- Respond **only** with the updated long-term summary that replaces the older conversation history.
- Do **not** include any extra headers, XML tags, or meta explanations in your output.
</instructions>
<messages>
<system_prompt>
{system_prompt}
</system_prompt>
<user_messages>
{user_messages}
</user_messages>
<previous_summary>
{previous_summary}
</previous_summary>
<older_messages>
{older_messages}
</older_messages>
<recent_messages>
{recent_messages}
</recent_messages>
</messages>`

@ -0,0 +1,403 @@
/*
* Copyright 2025 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package summarization
import (
"context"
"fmt"
"strings"
"github.com/pkoukk/tiktoken-go"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/components/prompt"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
)
type TokenCounter func(ctx context.Context, msgs []adk.Message) (tokenNum []int64, err error)
// Config defines parameters for the conversation summarization middleware.
// It controls when summarization is triggered and how much recent context is retained.
// Required: Model. Optional: SystemPrompt, Counter, and token budgets.
type Config struct {
// MaxTokensBeforeSummary is the max token threshold to trigger summarization based on total context
// (system prompt + history). Uses DefaultMaxTokensBeforeSummary when <= 0.
MaxTokensBeforeSummary int
// MaxTokensForRecentMessages is the max token budget reserved for recent messages after summarization.
// Uses DefaultMaxTokensForRecentMessages when <= 0.
MaxTokensForRecentMessages int
// Counter custom token counter.
// Optional
Counter TokenCounter
// Model used to generate the summary. Must be provided.
// Required.
Model model.BaseChatModel
// SystemPrompt is the system prompt for the summarizer.
// Optional. If empty, PromptOfSummary is used.
SystemPrompt string
}
// New creates an AgentMiddleware that compacts long conversation history
// into a single summary message when the token threshold is exceeded.
// The summarizer chain is: ChatTemplate(SystemPrompt) -> ChatModel(Model).
// It applies defaults for token budgets and allows a custom Counter.
func New(ctx context.Context, cfg *Config) (adk.AgentMiddleware, error) {
if cfg == nil {
return adk.AgentMiddleware{}, fmt.Errorf("config is nil")
}
systemPrompt := cfg.SystemPrompt
if systemPrompt == "" {
systemPrompt = PromptOfSummary
}
maxBefore := DefaultMaxTokensBeforeSummary
if cfg.MaxTokensBeforeSummary > 0 {
maxBefore = cfg.MaxTokensBeforeSummary
}
maxRecent := DefaultMaxTokensForRecentMessages
if cfg.MaxTokensForRecentMessages > 0 {
maxRecent = cfg.MaxTokensForRecentMessages
}
tpl := prompt.FromMessages(schema.FString,
schema.SystemMessage(systemPrompt),
schema.UserMessage("summarize 'older_messages': "))
summarizer, err := compose.NewChain[map[string]any, *schema.Message]().
AppendChatTemplate(tpl).
AppendChatModel(cfg.Model).
Compile(ctx, compose.WithGraphName("Summarizer"))
if err != nil {
return adk.AgentMiddleware{}, fmt.Errorf("compile summarizer failed, err=%w", err)
}
sm := &summaryMiddleware{
counter: defaultCounterToken,
maxBefore: maxBefore,
maxRecent: maxRecent,
summarizer: summarizer,
}
if cfg.Counter != nil {
sm.counter = cfg.Counter
}
return adk.AgentMiddleware{BeforeChatModel: sm.BeforeModel}, nil
}
const summaryMessageFlag = "_agent_middleware_summary_message"
type summaryMiddleware struct {
counter TokenCounter
maxBefore int
maxRecent int
summarizer compose.Runnable[map[string]any, *schema.Message]
}
func (s *summaryMiddleware) BeforeModel(ctx context.Context, state *adk.ChatModelAgentState) (err error) {
if state == nil || len(state.Messages) == 0 {
return nil
}
messages := state.Messages
msgsToken, err := s.counter(ctx, messages)
if err != nil {
return fmt.Errorf("count token failed, err=%w", err)
}
if len(messages) != len(msgsToken) {
return fmt.Errorf("token count mismatch, msgNum=%d, tokenCountNum=%d", len(messages), len(msgsToken))
}
var total int64
for _, t := range msgsToken {
total += t
}
// Trigger summarization only when exceeding threshold
if total <= int64(s.maxBefore) {
return nil
}
// Build blocks with user-messages, summary-message, tool-call pairings
type block struct {
msgs []*schema.Message
tokens int64
}
idx := 0
systemBlock := block{}
if idx < len(messages) {
m := messages[idx]
if m != nil && m.Role == schema.System {
systemBlock.msgs = append(systemBlock.msgs, m)
systemBlock.tokens += msgsToken[idx]
idx++
}
}
userBlock := block{}
for idx < len(messages) {
m := messages[idx]
if m == nil {
idx++
continue
}
if m.Role != schema.User {
break
}
userBlock.msgs = append(userBlock.msgs, m)
userBlock.tokens += msgsToken[idx]
idx++
}
summaryBlock := block{}
if idx < len(messages) {
m := messages[idx]
if m != nil && m.Role == schema.Assistant {
if _, ok := m.Extra[summaryMessageFlag]; ok {
summaryBlock.msgs = append(summaryBlock.msgs, m)
summaryBlock.tokens += msgsToken[idx]
idx++
}
}
}
toolBlocks := make([]block, 0)
for i := idx; i < len(messages); i++ {
m := messages[i]
if m == nil {
continue
}
if m.Role == schema.Assistant && len(m.ToolCalls) > 0 {
b := block{msgs: []*schema.Message{m}, tokens: msgsToken[i]}
// Collect subsequent tool messages matching any tool call id
callIDs := make(map[string]struct{}, len(m.ToolCalls))
for _, tc := range m.ToolCalls {
callIDs[tc.ID] = struct{}{}
}
j := i + 1
for j < len(messages) {
nm := messages[j]
if nm == nil || nm.Role != schema.Tool {
break
}
// Match by ToolCallID when available; if empty, include but keep boundary
if nm.ToolCallID == "" {
b.msgs = append(b.msgs, nm)
b.tokens += msgsToken[j]
} else {
if _, ok := callIDs[nm.ToolCallID]; !ok {
// Tool message not belonging to this assistant call -> end pairing
break
}
b.msgs = append(b.msgs, nm)
b.tokens += msgsToken[j]
}
j++
}
toolBlocks = append(toolBlocks, b)
i = j - 1
continue
}
toolBlocks = append(toolBlocks, block{msgs: []*schema.Message{m}, tokens: msgsToken[i]})
}
// Split into recent and older within token budget, from newest to oldest
var recentBlocks []block
var olderBlocks []block
var recentTokens int64
for i := len(toolBlocks) - 1; i >= 0; i-- {
b := toolBlocks[i]
if recentTokens+b.tokens > int64(s.maxRecent) {
olderBlocks = append([]block{b}, olderBlocks...)
continue
}
recentBlocks = append([]block{b}, recentBlocks...)
recentTokens += b.tokens
}
joinBlocks := func(bs []block) string {
var sb strings.Builder
for _, b := range bs {
for _, m := range b.msgs {
sb.WriteString(renderMsg(m))
sb.WriteString("\n")
}
}
return sb.String()
}
olderText := joinBlocks(olderBlocks)
recentText := joinBlocks(recentBlocks)
msg, err := s.summarizer.Invoke(ctx, map[string]any{
"system_prompt": joinBlocks([]block{systemBlock}),
"user_messages": joinBlocks([]block{userBlock}),
"previous_summary": joinBlocks([]block{summaryBlock}),
"older_messages": olderText,
"recent_messages": recentText,
})
if err != nil {
return fmt.Errorf("summarize failed, err=%w", err)
}
summaryMsg := schema.AssistantMessage(msg.Content, nil)
msg.Name = "summary"
summaryMsg.Extra = map[string]any{
summaryMessageFlag: true,
}
// Build new state: prepend summary message, keep recent messages
newMessages := make([]*schema.Message, 0, len(messages))
newMessages = append(newMessages, systemBlock.msgs...)
newMessages = append(newMessages, userBlock.msgs...)
newMessages = append(newMessages, summaryMsg)
for _, b := range recentBlocks {
newMessages = append(newMessages, b.msgs...)
}
state.Messages = newMessages
return nil
}
// Render messages into strings
func renderMsg(m *schema.Message) string {
if m == nil {
return ""
}
var sb strings.Builder
if m.Role == schema.Tool {
if m.ToolName != "" {
sb.WriteString("[tool:")
sb.WriteString(m.ToolName)
sb.WriteString("]\n")
} else {
sb.WriteString("[tool]\n")
}
} else {
sb.WriteString("[")
sb.WriteString(string(m.Role))
sb.WriteString("]\n")
}
if m.Content != "" {
sb.WriteString(m.Content)
sb.WriteString("\n")
}
if m.Role == schema.Assistant && len(m.ToolCalls) > 0 {
for _, tc := range m.ToolCalls {
if tc.Function.Name != "" {
sb.WriteString("tool_call: ")
sb.WriteString(tc.Function.Name)
sb.WriteString("\n")
}
if tc.Function.Arguments != "" {
sb.WriteString("args: ")
sb.WriteString(tc.Function.Arguments)
sb.WriteString("\n")
}
}
}
for _, part := range m.UserInputMultiContent {
if part.Type == schema.ChatMessagePartTypeText && part.Text != "" {
sb.WriteString(part.Text)
sb.WriteString("\n")
}
}
for _, part := range m.AssistantGenMultiContent {
if part.Type == schema.ChatMessagePartTypeText && part.Text != "" {
sb.WriteString(part.Text)
sb.WriteString("\n")
}
}
return sb.String()
}
func defaultCounterToken(ctx context.Context, msgs []adk.Message) (tokenNum []int64, err error) {
encoding := "cl100k_base"
tkt, err := tiktoken.GetEncoding(encoding)
if err != nil {
return nil, fmt.Errorf("get encoding failed, encoding=%v, err=%w", encoding, err)
}
tokenNum = make([]int64, len(msgs))
for i, m := range msgs {
if m == nil {
tokenNum[i] = 0
continue
}
var sb strings.Builder
// Message role contributes to chat tokenization overhead; include it as text.
if m.Role != "" {
sb.WriteString(string(m.Role))
sb.WriteString("\n")
}
// Core text content
if m.Content != "" {
sb.WriteString(m.Content)
sb.WriteString("\n")
}
// Reasoning content if present
// Reasoning Content is not used by model
// if m.ReasoningContent != "" {
// sb.WriteString(m.ReasoningContent)
// sb.WriteString("\n")
// }
// Multi modal input/output text parts
for _, part := range m.UserInputMultiContent {
if part.Type == schema.ChatMessagePartTypeText && part.Text != "" {
sb.WriteString(part.Text)
sb.WriteString("\n")
}
}
for _, part := range m.AssistantGenMultiContent {
if part.Type == schema.ChatMessagePartTypeText && part.Text != "" {
sb.WriteString(part.Text)
sb.WriteString("\n")
}
}
// Tool call textual context (name + arguments)
for _, tc := range m.ToolCalls {
if tc.Function.Name != "" {
sb.WriteString(tc.Function.Name)
sb.WriteString("\n")
}
if tc.Function.Arguments != "" {
sb.WriteString(tc.Function.Arguments)
sb.WriteString("\n")
}
}
text := sb.String()
if text == "" {
tokenNum[i] = 0
continue
}
tokens := tkt.Encode(text, nil, nil)
tokenNum[i] = int64(len(tokens))
}
return tokenNum, nil
}

@ -0,0 +1,77 @@
/*
* Copyright 2025 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"context"
"fmt"
"log"
"strconv"
"strings"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
)
type RepeatSectionsInput struct {
Title string `json:"title" jsonschema_description:"Section title"`
Paragraphs []string `json:"paragraphs" jsonschema_description:"Paragraphs to be repeated"`
RepeatCount int `json:"repeat_count" jsonschema_description:"Times to repeat paragraphs, default 2"`
}
func NewRepeatSectionsTool() tool.InvokableTool {
t, err := utils.InferTool(
"repeat_sections",
"Repeat given paragraphs to quickly accumulate context",
func(ctx context.Context, in *RepeatSectionsInput) (string, error) {
if in.RepeatCount <= 0 {
in.RepeatCount = 2
}
var b strings.Builder
b.WriteString("## ")
b.WriteString(in.Title)
b.WriteString("\n")
idx := 0
for i := 0; i < in.RepeatCount; i++ {
for _, sec := range in.Paragraphs {
b.WriteString(strconv.Itoa(idx + 1))
b.WriteString(". ")
b.WriteString(sec)
b.WriteString("\n")
idx++
}
}
callCount := 0
times, ok := adk.GetSessionValue(ctx, "_tool_call_count")
if !ok {
times = 0
} else {
callCount = times.(int)
}
callCount++
adk.AddSessionValue(ctx, "_tool_call_count", callCount)
b.WriteString(fmt.Sprintf("Tool calls so far: %d", callCount))
return b.String(), nil
},
)
if err != nil {
log.Fatalf("failed to create repeat_sections tool: %v", err)
}
return t
}

@ -29,7 +29,6 @@ import (
"github.com/cloudwego/eino/flow/agent"
"github.com/cloudwego/eino/flow/agent/multiagent/host"
"github.com/cloudwego/eino/schema"
"github.com/ollama/ollama/api"
)
// search journal: user ask a question, this specialist load today's journal and ground its answer onto it.
@ -58,7 +57,7 @@ func newAnswerWithJournalSpecialist(ctx context.Context, baseURL, model string)
BaseURL: baseURL,
Model: model,
Options: &api.Options{
Options: &ollama.Options{
Temperature: 0.000001,
},
})

@ -27,7 +27,6 @@ import (
"github.com/cloudwego/eino/flow/agent"
"github.com/cloudwego/eino/flow/agent/multiagent/host"
"github.com/cloudwego/eino/schema"
"github.com/ollama/ollama/api"
)
// create a specialist who can append text to the right local journal file
@ -81,7 +80,7 @@ func newWriteJournalSpecialist(ctx context.Context, baseURL, model string) (*hos
BaseURL: baseURL,
Model: model,
Options: &api.Options{
Options: &ollama.Options{
Temperature: 0.000001,
},
})

@ -7,17 +7,17 @@ toolchain go1.24.9
require (
github.com/bytedance/sonic v1.14.2
github.com/cloudwego/eino v0.7.0
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.4
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250117061805-cd80d1780d76
github.com/cloudwego/eino-ext/components/model/ark v0.1.41
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250826125654-37d4a5029810
github.com/cloudwego/eino-ext/components/model/ollama v0.1.2
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250826125654-37d4a5029810
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20250319082935-6219ec437e56
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251111090228-91a10bbc864f
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20250826125654-37d4a5029810
github.com/cloudwego/eino-ext/devops v0.1.7
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.6
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf
github.com/cloudwego/eino-ext/components/model/ark v0.1.45
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20251117090452-bd6375a0b3cf
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6
github.com/cloudwego/eino-ext/components/model/openai v0.1.5
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20251120060928-25485ef519b5
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251117090452-bd6375a0b3cf
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20251117090452-bd6375a0b3cf
github.com/cloudwego/eino-ext/devops v0.1.8
github.com/coze-dev/cozeloop-go v0.1.11
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/eino-contrib/jsonschema v1.0.2
@ -43,12 +43,14 @@ require (
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250826113018-8c6f6358d4bb // indirect
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 // indirect
github.com/cohesion-org/deepseek-go v1.3.2 // indirect
github.com/corpix/uarand v0.2.0 // indirect
github.com/coze-dev/cozeloop-go/spec v0.1.4 // indirect
github.com/coze-dev/cozeloop-go/spec v0.1.5 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dslipak/pdf v0.0.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eino-contrib/ollama v0.1.0 // indirect
github.com/evanphx/json-patch v0.5.2 // indirect
github.com/getkin/kin-openapi v0.118.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
@ -65,7 +67,7 @@ require (
github.com/mailru/easyjson v0.9.0 // indirect
github.com/matoous/go-nanoid v1.5.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0 // indirect
github.com/meguminnnnnnnnn/go-openai v0.1.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -76,6 +78,7 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkoukk/tiktoken-go v0.1.8 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
@ -90,7 +93,7 @@ require (
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/volcengine/volc-sdk-golang v1.0.196 // indirect
github.com/volcengine/volc-sdk-golang v1.0.199 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
github.com/yargevad/filepathx v1.0.0 // indirect

@ -127,28 +127,52 @@ github.com/cloudwego/eino v0.7.0 h1:XDGdGMZCAVx+OC0IxiLlyNFELoLN+56THUhYYqEujuM=
github.com/cloudwego/eino v0.7.0/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.4 h1:Cwm80+fMEAoY7uI9XUukDEV5J3Adb9HiXKmcXpXcDiM=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.4/go.mod h1:xxjNsJeZwdIooEYTt6tjw/YJzkA6xPcxn1u/HvA5xc0=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.6 h1:gS4nAOpQQC5WItt1k32yjZt9O2UWMpnbgF6vkMQAWhg=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.6/go.mod h1:ZniRkgN+9FUFxtN60X7yzD6UOruqrKQusjrOiGcH4I8=
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76 h1:kK4f2kunb5xlc0XTkg6wkjy8Z/BDfJjWAVm9EOdRErg=
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76/go.mod h1:LWR+h0EfIELl/I1tDSVH0Tgx8j2gymxa174U1C8BNps=
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf h1:Uwh3VT+xPrfDjM677dj1pSidCzBFoTrYlC274kEci5w=
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:DBwsPrdNxPeE3HNr5XyjjFreG4ypqCUjN0T2C2JSy6k=
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250117061805-cd80d1780d76 h1:GJ4OqxyBH8la8Gu4PhTHXZNZFmrtEIrrymkCEpJ7XZU=
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250117061805-cd80d1780d76/go.mod h1:swAgO0nNekTSKGgFqiy4zShKaCDhiIZoKEFwpi7NBFE=
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf h1:0KFSxuvFqs9dJ3Pu9Lk+4+Stt43I2u9eu3L4gDNcZ4A=
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:kHC3xkGM/gv3IHpOk33p75BfBaEIYATOs2XmYFKffcs=
github.com/cloudwego/eino-ext/components/model/ark v0.1.41 h1:l+WDY/nR1A5CzNkOJ+394EY+TxaW+NC5if1gnwcsvtE=
github.com/cloudwego/eino-ext/components/model/ark v0.1.41/go.mod h1:RIJTJsjS1Z1Xrldk6oeIkM0IHFasmYVfPh4b6ceExpY=
github.com/cloudwego/eino-ext/components/model/ark v0.1.45 h1:LWvSHJVlvS1S/IxN9XUKNw/MI0I7YPePt3LMNxyCrZ0=
github.com/cloudwego/eino-ext/components/model/ark v0.1.45/go.mod h1:e8P5dGVI/JMQ1FYNgmu5EFRWA8fivBc6NwNJ9g8FBK8=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250826125654-37d4a5029810 h1:zichoSWCoGqhUUwWscRNiTSH9j3KA8hReeEBMvr4i5w=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250826125654-37d4a5029810/go.mod h1:O6leaZBwE5s1wSsf5idW52Yaj1zWzhtjinSnfPXASNU=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20251117090452-bd6375a0b3cf h1:JA2W09VU+9zG9qRXheARRbi31cH+xHVzSJ/kFMxZvlo=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:3AlAYkxwlOAP0SnFj7Yu1Lsgp8XHpbSMaE3l0+vDHKQ=
github.com/cloudwego/eino-ext/components/model/ollama v0.1.2 h1:WxJ+7oXnr3AhM6u4VbFF3L2ionxCrPfmLetx7V+zthw=
github.com/cloudwego/eino-ext/components/model/ollama v0.1.2/go.mod h1:OgGMCiR/G/RnOWaJvdK8pVSxAzoz2SlCqim43oFTuwo=
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6 h1:ZbrhV91uE0hGIOYXhb2i3G6tQJ/rK2SLYtoYrmocZXM=
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6/go.mod h1:GDXrvorGdRNV6g2mK5jdla2D8Xc/hh7XDrTeGDteLLo=
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250826125654-37d4a5029810 h1:M8A7666rddupncJ4p3p1lH5jkNKtjzD7ULPE/I02o64=
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250826125654-37d4a5029810/go.mod h1:QQhCuQxuBAVWvu/YAZBhs/RsR76mUigw59Tl0kh04C8=
github.com/cloudwego/eino-ext/components/model/openai v0.1.5 h1:+yvGbTPw93li9GSmdm6Rix88Yy8AXg5NNBcRbWx3CQU=
github.com/cloudwego/eino-ext/components/model/openai v0.1.5/go.mod h1:IPVYMFoZcuHeVEsDTGN6SZjvue0xr1iZFhdpq1SBWdQ=
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20250319082935-6219ec437e56 h1:yL7nTthGoz35dh7RK9lazxDedGmSmlss8HrtXmd0O3Q=
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20250319082935-6219ec437e56/go.mod h1:OPQYefu4EWAUcP0HhtzJK+UjLtQc8T9YzmxcAwFf29o=
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20251120060928-25485ef519b5 h1:HoXqcYm3x4eXk6i2HLBklvx8WeFW7/MxijpCsEBQLSE=
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20251120060928-25485ef519b5/go.mod h1:He7AHJpLTs0MXKPx5JpI8MdYtLUcKhUAZwCsgNtaq6k=
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251111090228-91a10bbc864f h1:JkELXl5NquNnDgi81T3uiPfgpfcXzH81e3B/NDO4cwM=
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251111090228-91a10bbc864f/go.mod h1:WfhOxJY7J2ZqY9muahNOMtypoe4ovcAG1E97y4bQPfU=
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251117090452-bd6375a0b3cf h1:LLyVGanJpHpSSiqsbRXkuSpdpc1UmuaUIEs/BiOQkH4=
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:cMZb1KM71kM+2hTJwxLwXT+HC66HimZirDDA5Oo64Hw=
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20250826125654-37d4a5029810 h1:VtmhPdOY6wFR34Hdm4HNzWF9bwGz11x1TqBr8VpJDoc=
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20250826125654-37d4a5029810/go.mod h1:QB9TkAu6OVDvQm5hEoK+VFVIbfDHO+ZQyUR2cPSo6jk=
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20251117090452-bd6375a0b3cf h1:54xcNETtXP1gYieDuNyOIvzE4Mk/jOxjlqCERr4cxTk=
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20251117090452-bd6375a0b3cf/go.mod h1:Np0BXy/9hPRu3wCgn+ij6L7YsjFcybVzg1k7uYOXh0M=
github.com/cloudwego/eino-ext/devops v0.1.7 h1:w8q4SGDtrZk+8WUrLyVidX7GNsyWFsYPPz5HoJKgRcA=
github.com/cloudwego/eino-ext/devops v0.1.7/go.mod h1:W53NPBmkmM7ClyQqwOcFUC2kV4bsvCfh2FtHUcyMzMY=
github.com/cloudwego/eino-ext/devops v0.1.8 h1:qBg5vjZSDnd9tHzCHG8YsjnGB5vKG2EoZuuQCI8qrGs=
github.com/cloudwego/eino-ext/devops v0.1.8/go.mod h1:8yjvPNTaB5Ve4aJmJ0ysFgB10y3YbIuqMh0/Uwt5Fnw=
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250826113018-8c6f6358d4bb h1:RMslzyijc3bi9EkqCulpS0hZupTl1y/wayR3+fVRN/c=
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250826113018-8c6f6358d4bb/go.mod h1:fHn/6OqPPY1iLLx9wzz+MEVT5Dl9gwuZte1oLEnCoYw=
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 h1:r9Id2wzJ05PoHl+Km7jQgNMgciaZI93TVnUYso89esM=
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2/go.mod h1:S4OkvglPY9hsm9tXeShODrf/WN1Cgu4bqu4nn/CnIic=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@ -162,12 +186,16 @@ github.com/coze-dev/cozeloop-go v0.1.11 h1:NzdBKK3klhRgnPEH/PCjtP21+cRuz6qZlKPmJ
github.com/coze-dev/cozeloop-go v0.1.11/go.mod h1:GDF+MpqsUsq4oKy+L1FTd6Giy2cAmr/xXl9rOnub39A=
github.com/coze-dev/cozeloop-go/spec v0.1.4 h1:6yhjFQ39yn9VkZ68QG2W5WjzICl6GgwVsXmDmn75ySg=
github.com/coze-dev/cozeloop-go/spec v0.1.4/go.mod h1:/f3BrWehffwXIpd4b5rYIqktLd/v5dlLBw0h9F/LQIU=
github.com/coze-dev/cozeloop-go/spec v0.1.5 h1:tEQ82qlz9/HZv8MqyZq+043SaHs5C44MWslyGm5UcNI=
github.com/coze-dev/cozeloop-go/spec v0.1.5/go.mod h1:/f3BrWehffwXIpd4b5rYIqktLd/v5dlLBw0h9F/LQIU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dslipak/pdf v0.0.2 h1:djAvcM5neg9Ush+zR6QXB+VMJzR6TdnX766HPIg1JmI=
github.com/dslipak/pdf v0.0.2/go.mod h1:2L3SnkI9cQwnAS9gfPz2iUoLC0rUZwbucpbKi5R1mUo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -180,6 +208,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/eino-contrib/jsonschema v1.0.2 h1:HaxruBMUdnXa7Lg/lX8g0Hk71ZIfdTZXmBQz0e3esr8=
github.com/eino-contrib/jsonschema v1.0.2/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4=
github.com/eino-contrib/ollama v0.1.0 h1:z1NaMdKW6X1ftP8g5xGGR5zDRPUtuTKFq35vBQgxsN4=
github.com/eino-contrib/ollama v0.1.0/go.mod h1:mYsQ7b3DeqY8bHPuD3MZJYTqkgyL6LoemxoP/B7ZNhA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -433,6 +463,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0 h1:nIohpHs1ViKR0SVgW/cbBstHjmnqFZDM9RqgX9m9Xu8=
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
github.com/meguminnnnnnnnn/go-openai v0.1.0 h1:BGzB1PlS2Epq0mBB2TGLwzMihbR7BANrlMH3w4ZnY88=
github.com/meguminnnnnnnnn/go-openai v0.1.0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
@ -510,6 +542,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkoukk/tiktoken-go v0.1.8 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo=
github.com/pkoukk/tiktoken-go v0.1.8/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -616,6 +650,8 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
github.com/volcengine/volc-sdk-golang v1.0.196 h1:KYbX76ibTlNhgm6Kq3sDTjrujDBd0za8ULg0v7+sCag=
github.com/volcengine/volc-sdk-golang v1.0.196/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volc-sdk-golang v1.0.199 h1:zv9QOqTl/IsLwtfC37GlJtcz6vMAHi+pjq8ILWjLYUc=
github.com/volcengine/volc-sdk-golang v1.0.199/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volcengine-go-sdk v1.1.44 h1:WLoLlzt67ZlJeow55PPx65/Mh52DewVXqkHcFSodM9w=
github.com/volcengine/volcengine-go-sdk v1.1.44/go.mod h1:oxoVo+A17kvkwPkIeIHPVLjSw7EQAm+l/Vau1YGHN+A=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=

Loading…
Cancel
Save