You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
7.0 KiB
Go
224 lines
7.0 KiB
Go
/*
|
|
* 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"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cloudwego/eino/adk"
|
|
"github.com/cloudwego/eino/components/prompt"
|
|
"github.com/cloudwego/eino/components/tool"
|
|
"github.com/cloudwego/eino/compose"
|
|
"github.com/cloudwego/eino/schema"
|
|
|
|
"github.com/cloudwego/eino-examples/adk/common/model"
|
|
"github.com/cloudwego/eino-examples/adk/common/prints"
|
|
"github.com/cloudwego/eino-examples/adk/common/tool/graphtool"
|
|
)
|
|
|
|
type ResearchInput struct {
|
|
Query string `json:"query" jsonschema_description:"The research topic or question to investigate"`
|
|
}
|
|
|
|
func mockWebSearch(ctx context.Context, query string) (string, error) {
|
|
time.Sleep(100 * time.Millisecond)
|
|
return fmt.Sprintf(`Web Search Results for "%s":
|
|
1. Wikipedia: %s is a widely discussed topic with multiple perspectives...
|
|
2. News Article: Recent developments in %s show promising trends...
|
|
3. Research Paper: A comprehensive study on %s reveals key insights...`, query, query, query, query), nil
|
|
}
|
|
|
|
func mockKnowledgeBaseSearch(ctx context.Context, query string) (string, error) {
|
|
time.Sleep(80 * time.Millisecond)
|
|
return fmt.Sprintf(`Knowledge Base Results for "%s":
|
|
- Internal Doc #1: Company guidelines related to %s...
|
|
- Internal Doc #2: Best practices for handling %s...
|
|
- FAQ Entry: Common questions about %s answered...`, query, query, query, query), nil
|
|
}
|
|
|
|
func mockLocalFileSearch(ctx context.Context, query string) (string, error) {
|
|
time.Sleep(50 * time.Millisecond)
|
|
return fmt.Sprintf(`Local File Results for "%s":
|
|
- notes/research_%s.md: Personal notes on %s...
|
|
- docs/guide_%s.txt: Step-by-step guide for %s...`, query, strings.ReplaceAll(query, " ", "_"), query, strings.ReplaceAll(query, " ", "_"), query), nil
|
|
}
|
|
|
|
type searchResults struct {
|
|
Query string
|
|
WebResults string
|
|
KBResults string
|
|
LocalResults string
|
|
}
|
|
|
|
func NewResearchTool(ctx context.Context) (tool.StreamableTool, error) {
|
|
cm := model.NewChatModel()
|
|
|
|
synthesizePrompt := prompt.FromMessages(schema.FString,
|
|
schema.SystemMessage(`You are a research analyst. Synthesize the following search results from multiple sources into a coherent summary.
|
|
Focus on the most relevant and reliable information. Identify any conflicting information across sources.
|
|
Be concise but comprehensive. Output the summary directly without any JSON formatting.`),
|
|
schema.UserMessage(`Research Query: {query}
|
|
|
|
Web Search Results:
|
|
{web_results}
|
|
|
|
Knowledge Base Results:
|
|
{kb_results}
|
|
|
|
Local File Results:
|
|
{local_results}
|
|
|
|
Please synthesize these results into a comprehensive summary:`))
|
|
|
|
graph := compose.NewGraph[*ResearchInput, *schema.Message]()
|
|
|
|
_ = graph.AddLambdaNode("parallel_search", compose.InvokableLambda(func(ctx context.Context, input *ResearchInput) (*searchResults, error) {
|
|
fmt.Println(" [Graph] Starting parallel searches...")
|
|
|
|
type result struct {
|
|
source string
|
|
data string
|
|
err error
|
|
}
|
|
|
|
resultCh := make(chan result, 3)
|
|
|
|
go func() {
|
|
data, err := mockWebSearch(ctx, input.Query)
|
|
resultCh <- result{source: "web", data: data, err: err}
|
|
}()
|
|
|
|
go func() {
|
|
data, err := mockKnowledgeBaseSearch(ctx, input.Query)
|
|
resultCh <- result{source: "kb", data: data, err: err}
|
|
}()
|
|
|
|
go func() {
|
|
data, err := mockLocalFileSearch(ctx, input.Query)
|
|
resultCh <- result{source: "local", data: data, err: err}
|
|
}()
|
|
|
|
results := &searchResults{Query: input.Query}
|
|
for i := 0; i < 3; i++ {
|
|
r := <-resultCh
|
|
if r.err != nil {
|
|
return nil, r.err
|
|
}
|
|
switch r.source {
|
|
case "web":
|
|
results.WebResults = r.data
|
|
fmt.Println(" [Graph] Web search completed")
|
|
case "kb":
|
|
results.KBResults = r.data
|
|
fmt.Println(" [Graph] Knowledge base search completed")
|
|
case "local":
|
|
results.LocalResults = r.data
|
|
fmt.Println(" [Graph] Local file search completed")
|
|
}
|
|
}
|
|
|
|
fmt.Println(" [Graph] All searches completed, preparing synthesis...")
|
|
return results, nil
|
|
}))
|
|
|
|
_ = graph.AddLambdaNode("prepare_prompt_input", compose.InvokableLambda(func(ctx context.Context, results *searchResults) (map[string]any, error) {
|
|
return map[string]any{
|
|
"query": results.Query,
|
|
"web_results": results.WebResults,
|
|
"kb_results": results.KBResults,
|
|
"local_results": results.LocalResults,
|
|
}, nil
|
|
}))
|
|
|
|
_ = graph.AddChatTemplateNode("prepare_prompt", synthesizePrompt)
|
|
|
|
_ = graph.AddChatModelNode("synthesize", cm)
|
|
|
|
_ = graph.AddEdge(compose.START, "parallel_search")
|
|
_ = graph.AddEdge("parallel_search", "prepare_prompt_input")
|
|
_ = graph.AddEdge("prepare_prompt_input", "prepare_prompt")
|
|
_ = graph.AddEdge("prepare_prompt", "synthesize")
|
|
_ = graph.AddEdge("synthesize", compose.END)
|
|
|
|
return graphtool.NewStreamableGraphTool[*ResearchInput, *schema.Message](
|
|
graph,
|
|
"research_topic",
|
|
"Research a topic by querying multiple sources (web, knowledge base, local files) in parallel and synthesizing the results. Returns a streaming summary directly.",
|
|
)
|
|
}
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
researchTool, err := NewResearchTool(ctx)
|
|
if err != nil {
|
|
log.Fatalf("failed to create research tool: %v", err)
|
|
}
|
|
|
|
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
|
|
Name: "ResearchAssistant",
|
|
Description: "An assistant that can research topics using multiple sources",
|
|
Instruction: `You are a helpful research assistant.
|
|
When the user asks about a topic or wants to learn something, use the research_topic tool to gather information from multiple sources.
|
|
The tool will stream the research results directly to the user.`,
|
|
Model: model.NewChatModel(),
|
|
ToolsConfig: adk.ToolsConfig{
|
|
ToolsNodeConfig: compose.ToolsNodeConfig{
|
|
Tools: []tool.BaseTool{researchTool},
|
|
},
|
|
ReturnDirectly: map[string]bool{
|
|
"research_topic": true,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("failed to create agent: %v", err)
|
|
}
|
|
|
|
runner := adk.NewRunner(ctx, adk.RunnerConfig{
|
|
EnableStreaming: true,
|
|
Agent: agent,
|
|
})
|
|
|
|
query := "What are the best practices for building microservices?"
|
|
|
|
iter := runner.Query(ctx, query)
|
|
|
|
fmt.Println("=== Multi-Source Research Example (using compose.Graph + StreamableGraphTool) ===")
|
|
fmt.Println()
|
|
fmt.Println("This example demonstrates:")
|
|
fmt.Println("1. StreamableGraphTool with compose.Graph")
|
|
fmt.Println("2. Parallel search execution within a graph node")
|
|
fmt.Println("3. Streaming output from ChatModel via ReturnDirectly")
|
|
fmt.Println()
|
|
|
|
for {
|
|
event, ok := iter.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
if event.Err != nil {
|
|
log.Fatalf("error: %v", event.Err)
|
|
}
|
|
prints.Event(event)
|
|
}
|
|
}
|