├── .env.example ├── .gitignore ├── examples ├── config │ └── research_assistant.yaml ├── 5_search_example.go ├── web_scraper_example.go ├── 2_manager_example.go ├── 3_tools_example.go ├── 4_parallel_example.go ├── 1_basic_example.go ├── 6_api_example.go ├── 8_research_assistant_example.go └── 7_manager_mode_example.go ├── docs ├── README.md ├── tutorials │ ├── 8_research_assistant.md │ ├── web-scraping-assistant.md │ └── manager-mode-assistant.md ├── getting-started.md └── core-concepts.md ├── cmd └── enzu │ └── main.go ├── go.mod ├── agent.go ├── logger.go ├── tools └── exa_search.go ├── manager.go ├── tool.go ├── README.md ├── task.go ├── synergy.go ├── LICENSE └── go.sum /.env.example: -------------------------------------------------------------------------------- 1 | # OpenAI API Key for embeddings and LLM 2 | OPENAI_API_KEY=your-api-key-here 3 | ANTHROPIC_API_KEY=your-api-key-here 4 | EXA_API_KEY=your-api-key-here -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | -------------------------------------------------------------------------------- /examples/config/research_assistant.yaml: -------------------------------------------------------------------------------- 1 | objective: "Research and analyze technical topics" 2 | llm: 3 | type: "gpt4" 4 | parameters: 5 | api_key: "${OPENAI_API_KEY}" 6 | temperature: 0.7 7 | max_tokens: 2000 8 | 9 | agents: 10 | - name: "researcher" 11 | role: "Research technical topics and gather information" 12 | llm: 13 | type: "gpt4" 14 | parameters: 15 | api_key: "${OPENAI_API_KEY}" 16 | temperature: 0.5 17 | tool_lists: 18 | - "web_search" 19 | - "document_reader" 20 | inherit_synergy_tools: true 21 | parallel: false 22 | 23 | - name: "analyzer" 24 | role: "Analyze and synthesize research findings" 25 | llm: 26 | type: "gpt4" 27 | parameters: 28 | api_key: "${OPENAI_API_KEY}" 29 | temperature: 0.3 30 | tool_lists: 31 | - "text_analysis" 32 | - "summary_generator" 33 | inherit_synergy_tools: true 34 | parallel: false 35 | 36 | tool_lists: 37 | - "file_operations" 38 | - "markdown_writer" 39 | parallel: false 40 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Enzu Documentation 2 | 3 | Welcome to the Enzu documentation! This guide will help you understand how to build AI applications with multi-agent collaboration using Enzu. 4 | 5 | ## Table of Contents 6 | 7 | 1. [Getting Started](./getting-started.md) 8 | - Installation 9 | - Basic Concepts 10 | - Your First Enzu Application 11 | 12 | 2. [Core Concepts](./core-concepts.md) 13 | - Agents 14 | - Synergies 15 | - Tasks 16 | - Tools 17 | 18 | 3. [Tutorials](./tutorials/README.md) 19 | - Basic Chat Agent 20 | - File Management System 21 | - Web Scraping Assistant 22 | - System Monitor 23 | 24 | 4. [Advanced Topics](./advanced-topics.md) 25 | - Parallel Execution 26 | - Tool Inheritance 27 | - Custom Tool Development 28 | - Error Handling 29 | 30 | 5. [API Reference](./api-reference.md) 31 | - Agent API 32 | - Synergy API 33 | - Tool API 34 | - Task API 35 | 36 | 6. [Best Practices](./best-practices.md) 37 | - Agent Design 38 | - Tool Organization 39 | - Error Handling 40 | - Performance Optimization 41 | 42 | Start with the [Getting Started](./getting-started.md) guide to begin your journey with Enzu! 43 | -------------------------------------------------------------------------------- /cmd/enzu/main.go: -------------------------------------------------------------------------------- 1 | // Package main provides the entry point for the Enzu framework, a powerful tool for building 2 | // and executing AI-driven workflows. It handles configuration loading and orchestrates the 3 | // creation and execution of synergies based on provided YAML or JSON configurations. 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "log" 10 | 11 | "github.com/teilomillet/enzu/builder" 12 | ) 13 | 14 | // main is the entry point of the Enzu framework. It performs the following steps: 15 | // 1. Parses command-line flags to get the configuration file path 16 | // 2. Loads and validates the synergy configuration 17 | // 3. Creates a builder instance to construct the synergy 18 | // 4. Builds and executes the synergy with the specified configuration 19 | // 20 | // The program expects a configuration file path provided via the -config flag. 21 | // It will exit with an error if: 22 | // - No configuration file is provided 23 | // - The configuration file cannot be loaded or is invalid 24 | // - Synergy building fails 25 | // - Synergy execution encounters an error 26 | func main() { 27 | configFile := flag.String("config", "", "Path to the configuration file (YAML or JSON)") 28 | flag.Parse() 29 | 30 | if *configFile == "" { 31 | log.Fatal("Please provide a configuration file using -config flag") 32 | } 33 | 34 | // Load configuration 35 | config, err := builder.LoadConfig(*configFile) 36 | if err != nil { 37 | log.Fatalf("Failed to load configuration: %v", err) 38 | } 39 | 40 | // Create builder 41 | b := builder.NewBuilder(config) 42 | 43 | // Build synergy 44 | synergy, err := b.BuildSynergy() 45 | if err != nil { 46 | log.Fatalf("Failed to build synergy: %v", err) 47 | } 48 | 49 | // Execute synergy 50 | result, err := synergy.Execute(nil) 51 | if err != nil { 52 | log.Fatalf("Failed to execute synergy: %v", err) 53 | } 54 | 55 | fmt.Printf("Synergy execution completed. Result: %v\n", result) 56 | } 57 | -------------------------------------------------------------------------------- /examples/5_search_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/teilomillet/enzu" 10 | "github.com/teilomillet/enzu/tools" 11 | "github.com/teilomillet/gollm" 12 | ) 13 | 14 | func main() { 15 | // Load API key from environment variable 16 | openAIKey := os.Getenv("OPENAI_API_KEY") 17 | if openAIKey == "" { 18 | log.Fatalf("OPENAI_API_KEY environment variable is not set") 19 | } 20 | 21 | // Create a new LLM instance 22 | llm, err := gollm.NewLLM( 23 | gollm.SetProvider("openai"), 24 | gollm.SetModel("gpt-4o-mini"), 25 | gollm.SetAPIKey(openAIKey), 26 | gollm.SetMaxTokens(200), 27 | ) 28 | if err != nil { 29 | log.Fatalf("Failed to initialize LLM: %v", err) 30 | } 31 | 32 | // Create a logger for Enzu 33 | logger := enzu.NewLogger(enzu.LogLevelInfo) 34 | 35 | // Define options for the ExaSearch tool 36 | exaSearchOptions := tools.ExaSearchOptions{ 37 | NumResults: 5, 38 | Type: "neural", 39 | Contents: tools.Contents{ 40 | Text: true, 41 | }, 42 | UseAutoprompt: true, 43 | StartPublishedDate: "2023-01-01T00:00:00.000Z", 44 | } 45 | 46 | // Register the ExaSearch tool 47 | tools.ExaSearch("", exaSearchOptions, "ResearchTool") 48 | 49 | // Create research agents 50 | researchAgent1 := enzu.NewAgent("Research Agent 1", "Agent specialized in AI research", llm, 51 | enzu.WithToolLists("ResearchTool"), 52 | enzu.WithParallelExecution(true), 53 | ) 54 | researchAgent2 := enzu.NewAgent("Research Agent 2", "Agent specialized in startup research", llm, 55 | enzu.WithToolLists("ResearchTool"), 56 | enzu.WithParallelExecution(true), 57 | ) 58 | 59 | // Create research tasks 60 | task1 := enzu.NewTask("Research the latest advancements in artificial intelligence", researchAgent1) 61 | task2 := enzu.NewTask("Find information about the most promising AI startups in 2024", researchAgent2) 62 | 63 | // Create a synergy 64 | synergy := enzu.NewSynergy( 65 | "Parallel AI Research", 66 | llm, 67 | enzu.WithAgents(researchAgent1, researchAgent2), 68 | enzu.WithTasks(task1, task2), 69 | enzu.WithLogger(logger), 70 | ) 71 | 72 | // Execute the synergy 73 | ctx := context.Background() 74 | results, err := synergy.Execute(ctx) 75 | if err != nil { 76 | log.Fatalf("Error executing synergy: %v", err) 77 | } 78 | 79 | // Print results 80 | fmt.Println("Research Results:") 81 | for task, result := range results { 82 | fmt.Printf("Task: %s\nResult: %v\n\n", task, result) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/teilomillet/enzu 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.10.0 7 | github.com/teilomillet/gollm v0.1.1 8 | gopkg.in/yaml.v3 v3.0.1 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/cascadia v1.3.2 // indirect 13 | github.com/bahlo/generic-list-go v0.2.0 // indirect 14 | github.com/buger/jsonparser v1.1.1 // indirect 15 | github.com/caarlos0/env/v11 v11.2.2 // indirect 16 | github.com/cockroachdb/errors v1.11.3 // indirect 17 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect 18 | github.com/cockroachdb/redact v1.1.5 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/dlclark/regexp2 v1.11.4 // indirect 21 | github.com/gabriel-vasile/mimetype v1.4.7 // indirect 22 | github.com/getsentry/sentry-go v0.28.1 // indirect 23 | github.com/go-playground/locales v0.14.1 // indirect 24 | github.com/go-playground/universal-translator v0.18.1 // indirect 25 | github.com/go-playground/validator/v10 v10.23.0 // indirect 26 | github.com/gogo/protobuf v1.3.2 // indirect 27 | github.com/golang/protobuf v1.5.4 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect 30 | github.com/invopop/jsonschema v0.12.0 // indirect 31 | github.com/kr/pretty v0.3.1 // indirect 32 | github.com/kr/text v0.2.0 // indirect 33 | github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 // indirect 34 | github.com/leodido/go-urn v1.4.0 // indirect 35 | github.com/mailru/easyjson v0.7.7 // indirect 36 | github.com/milvus-io/milvus-proto/go-api/v2 v2.4.6 // indirect 37 | github.com/milvus-io/milvus-sdk-go/v2 v2.4.1 // indirect 38 | github.com/philippgille/chromem-go v0.7.0 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/pkoukk/tiktoken-go v0.1.7 // indirect 41 | github.com/pmezard/go-difflib v1.0.0 // indirect 42 | github.com/rogpeppe/go-internal v1.12.0 // indirect 43 | github.com/stretchr/objx v0.5.2 // indirect 44 | github.com/stretchr/testify v1.9.0 // indirect 45 | github.com/teilomillet/raggo v0.0.2 // indirect 46 | github.com/tidwall/gjson v1.17.1 // indirect 47 | github.com/tidwall/match v1.1.1 // indirect 48 | github.com/tidwall/pretty v1.2.1 // indirect 49 | github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect 50 | golang.org/x/crypto v0.29.0 // indirect 51 | golang.org/x/net v0.31.0 // indirect 52 | golang.org/x/sys v0.27.0 // indirect 53 | golang.org/x/text v0.20.0 // indirect 54 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 55 | google.golang.org/grpc v1.56.3 // indirect 56 | google.golang.org/protobuf v1.34.2 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /examples/web_scraper_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | "github.com/teilomillet/enzu" 11 | "github.com/teilomillet/gollm" 12 | ) 13 | 14 | func main() { 15 | // Initialize LLM 16 | llm, err := gollm.NewLLM( 17 | gollm.SetProvider("openai"), 18 | gollm.SetModel("gpt-4o-mini"), 19 | ) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // Create FetchURL tool 25 | enzu.NewTool( 26 | "FetchURL", 27 | "Fetches content from a URL", 28 | func(args ...interface{}) (interface{}, error) { 29 | if len(args) != 1 { 30 | return nil, fmt.Errorf("FetchURL requires 1 argument (url)") 31 | } 32 | url, ok := args[0].(string) 33 | if !ok { 34 | return nil, fmt.Errorf("URL must be a string") 35 | } 36 | 37 | resp, err := http.Get(url) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer resp.Body.Close() 42 | 43 | doc, err := goquery.NewDocumentFromReader(resp.Body) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return doc.Html() 49 | }, 50 | "NetworkTools", 51 | ) 52 | 53 | // Create ExtractText tool 54 | enzu.NewTool( 55 | "ExtractText", 56 | "Extracts text content from HTML using CSS selector", 57 | func(args ...interface{}) (interface{}, error) { 58 | if len(args) != 2 { 59 | return nil, fmt.Errorf("ExtractText requires 2 arguments (html, selector)") 60 | } 61 | html, ok1 := args[0].(string) 62 | selector, ok2 := args[1].(string) 63 | if !ok1 || !ok2 { 64 | return nil, fmt.Errorf("Arguments must be strings") 65 | } 66 | 67 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | var results []string 73 | doc.Find(selector).Each(func(i int, s *goquery.Selection) { 74 | results = append(results, strings.TrimSpace(s.Text())) 75 | }) 76 | 77 | return results, nil 78 | }, 79 | "ParsingTools", 80 | ) 81 | 82 | // Create web scraping agent 83 | agent := enzu.NewAgent( 84 | "WebScrapingAgent", 85 | "An agent that can fetch and extract information from web pages", 86 | llm, 87 | enzu.WithToolLists("NetworkTools", "ParsingTools"), 88 | ) 89 | 90 | // Create task 91 | task := enzu.NewTask( 92 | "Fetch content from example.com and extract all paragraph text", 93 | agent, 94 | ) 95 | 96 | // Create synergy 97 | synergy := enzu.NewSynergy( 98 | "Web Scraping Example", 99 | llm, 100 | enzu.WithAgents(agent), 101 | enzu.WithTasks(task), 102 | ) 103 | 104 | // Execute the synergy 105 | ctx := context.Background() 106 | results, err := synergy.Execute(ctx) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | fmt.Println("Extracted content:", results) 112 | } 113 | -------------------------------------------------------------------------------- /examples/2_manager_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/teilomillet/enzu" 11 | "github.com/teilomillet/gollm" 12 | ) 13 | 14 | func main() { 15 | // Load API key from environment variable 16 | apiKey := os.Getenv("OPENAI_API_KEY") 17 | if apiKey == "" { 18 | log.Fatalf("OPENAI_API_KEY environment variable is not set") 19 | } 20 | 21 | // Create a new LLM instance with custom configuration 22 | llm, err := gollm.NewLLM( 23 | gollm.SetProvider("openai"), 24 | gollm.SetModel("gpt-4o-mini"), 25 | gollm.SetAPIKey(apiKey), 26 | gollm.SetMaxTokens(200), 27 | gollm.SetMaxRetries(3), 28 | gollm.SetRetryDelay(time.Second*2), 29 | gollm.SetLogLevel(gollm.LogLevelInfo), 30 | ) 31 | if err != nil { 32 | log.Fatalf("Failed to initialize LLM: %v", err) 33 | } 34 | 35 | // Create a logger for Enzu 36 | logger := enzu.NewLogger(enzu.LogLevelDebug) 37 | 38 | // Create SynergyManager 39 | manager := enzu.NewSynergyManager("MainManager", llm, logger) 40 | 41 | // Create first Synergy (Marketing Team) 42 | marketingAgents := createMarketingTeam(llm) 43 | marketingSynergy := enzu.NewSynergy( 44 | "Develop Marketing Strategy", 45 | llm, 46 | enzu.WithAgents(marketingAgents...), 47 | enzu.WithTasks( 48 | enzu.NewTask("Analyze current market trends", marketingAgents[0]), 49 | enzu.NewTask("Develop marketing strategy", marketingAgents[1]), 50 | enzu.NewTask("Create marketing copy", marketingAgents[2]), 51 | ), 52 | enzu.WithLogger(logger), 53 | ) 54 | manager.AddSynergy(marketingSynergy) 55 | 56 | // Create second Synergy (Product Development Team) 57 | productAgents := createProductTeam(llm) 58 | productSynergy := enzu.NewSynergy( 59 | "Develop New Product Features", 60 | llm, 61 | enzu.WithAgents(productAgents...), 62 | enzu.WithTasks( 63 | enzu.NewTask("Brainstorm new features", productAgents[0]), 64 | enzu.NewTask("Prioritize features", productAgents[1]), 65 | enzu.NewTask("Create development roadmap", productAgents[2]), 66 | ), 67 | enzu.WithLogger(logger), 68 | ) 69 | manager.AddSynergy(productSynergy) 70 | 71 | // Execute all Synergies 72 | ctx := context.Background() 73 | initialPrompt := "Develop a comprehensive plan to launch a new AI-powered project management tool" 74 | results, err := manager.ExecuteSynergies(ctx, initialPrompt) 75 | if err != nil { 76 | log.Fatalf("Error executing Synergies: %v", err) 77 | } 78 | 79 | // Print synthesized results 80 | fmt.Printf("Synthesized Results:\n%v\n", results["synthesis"]) 81 | } 82 | 83 | func createMarketingTeam(llm gollm.LLM) []*enzu.Agent { 84 | return []*enzu.Agent{ 85 | enzu.NewAgent("Market Analyst", "Analyze market trends", llm), 86 | enzu.NewAgent("Marketing Strategist", "Develop marketing strategies", llm), 87 | enzu.NewAgent("Content Creator", "Create marketing content", llm), 88 | } 89 | } 90 | 91 | func createProductTeam(llm gollm.LLM) []*enzu.Agent { 92 | return []*enzu.Agent{ 93 | enzu.NewAgent("Product Visionary", "Brainstorm new features", llm), 94 | enzu.NewAgent("Product Manager", "Prioritize features", llm), 95 | enzu.NewAgent("Tech Lead", "Create development roadmap", llm), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/3_tools_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/teilomillet/enzu" 11 | "github.com/teilomillet/gollm" 12 | ) 13 | 14 | func main() { 15 | // Load API key from environment variable 16 | apiKey := os.Getenv("OPENAI_API_KEY") 17 | if apiKey == "" { 18 | log.Fatalf("OPENAI_API_KEY environment variable is not set") 19 | } 20 | 21 | // Create a new LLM instance 22 | llm, err := gollm.NewLLM( 23 | gollm.SetProvider("openai"), 24 | gollm.SetModel("gpt-4o-mini"), 25 | gollm.SetAPIKey(apiKey), 26 | gollm.SetMaxTokens(200), 27 | gollm.SetLogLevel(gollm.LogLevelDebug), 28 | ) 29 | if err != nil { 30 | log.Fatalf("Failed to initialize LLM: %v", err) 31 | } 32 | 33 | // Create a logger for Enzu 34 | logger := enzu.NewLogger(enzu.LogLevelDebug) 35 | 36 | // Create tools 37 | enzu.NewTool( 38 | "CurrentTime", 39 | "Returns the current time", 40 | func(args ...interface{}) (interface{}, error) { 41 | return time.Now().Format(time.RFC3339), nil 42 | }, 43 | "TimeTool", "BasicTools", 44 | ) 45 | 46 | enzu.NewTool( 47 | "Add", 48 | "Adds two numbers", 49 | func(args ...interface{}) (interface{}, error) { 50 | if len(args) != 2 { 51 | return nil, fmt.Errorf("Add tool requires exactly 2 arguments") 52 | } 53 | a, ok1 := args[0].(float64) 54 | b, ok2 := args[1].(float64) 55 | if !ok1 || !ok2 { 56 | return nil, fmt.Errorf("Add tool arguments must be numbers") 57 | } 58 | return a + b, nil 59 | }, 60 | "MathTool", "BasicTools", 61 | ) 62 | 63 | // Create agents 64 | timeAgent := enzu.NewAgent("Time Agent", "Agent specialized in time-related tasks", llm, 65 | enzu.WithToolLists("TimeTool"), 66 | ) 67 | mathAgent := enzu.NewAgent("Math Agent", "Agent specialized in mathematical operations", llm, 68 | enzu.WithToolLists("MathTool"), 69 | ) 70 | generalAgent := enzu.NewAgent("General Agent", "Agent with access to all tools", llm, 71 | enzu.WithToolLists("BasicTools"), 72 | enzu.WithInheritSynergyTools(true), // Explicitly set to true (default behavior) 73 | ) 74 | restrictedAgent := enzu.NewAgent("Restricted Agent", "Agent with no inheritance from Synergy", llm, 75 | enzu.WithToolLists("MathTool"), 76 | enzu.WithInheritSynergyTools(false), // This agent will not inherit Synergy tools 77 | ) 78 | 79 | // Create tasks 80 | timeTask := enzu.NewTask("What is the current time?", timeAgent) 81 | mathTask := enzu.NewTask("Add 5 and 7", mathAgent) 82 | combinedTask := enzu.NewTask("What is the current time and what is 5 + 7?", generalAgent) 83 | restrictedTask := enzu.NewTask("Add 10 and 20, and try to get the current time", restrictedAgent) 84 | 85 | // Create a synergy with a new ToolRegistry and the logger 86 | synergy := enzu.NewSynergy( 87 | "Demonstrate tool usage, agent specialization, and inheritance control", 88 | llm, 89 | enzu.WithAgents(timeAgent, mathAgent, generalAgent, restrictedAgent), 90 | enzu.WithTasks(timeTask, mathTask, combinedTask, restrictedTask), 91 | enzu.WithTools("TimeTool"), // This makes only TimeTool available to the synergy 92 | enzu.WithLogger(logger), // Use the created logger 93 | ) 94 | 95 | // Execute the synergy 96 | ctx := context.Background() 97 | results, err := synergy.Execute(ctx) 98 | if err != nil { 99 | log.Fatalf("Error executing synergy: %v", err) 100 | } 101 | 102 | // Print results 103 | fmt.Println("Synergy Results:") 104 | for task, result := range results { 105 | fmt.Printf("Task: %s\nResult: %s\n\n", task, result) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/4_parallel_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/teilomillet/enzu" 11 | "github.com/teilomillet/gollm" 12 | ) 13 | 14 | func main() { 15 | // Load API key from environment variable 16 | apiKey := os.Getenv("OPENAI_API_KEY") 17 | if apiKey == "" { 18 | log.Fatalf("OPENAI_API_KEY environment variable is not set") 19 | } 20 | 21 | // Create a new LLM instance 22 | llm, err := gollm.NewLLM( 23 | gollm.SetProvider("openai"), 24 | gollm.SetModel("gpt-4o-mini"), 25 | gollm.SetAPIKey(apiKey), 26 | gollm.SetMaxTokens(200), 27 | gollm.SetLogLevel(gollm.LogLevelDebug), 28 | ) 29 | if err != nil { 30 | log.Fatalf("Failed to initialize LLM: %v", err) 31 | } 32 | 33 | // Create a logger for Enzu 34 | logger := enzu.NewLogger(enzu.LogLevelDebug) 35 | 36 | // Create tools 37 | enzu.NewTool( 38 | "CurrentTime", 39 | "Returns the current time", 40 | func(args ...interface{}) (interface{}, error) { 41 | return time.Now().Format(time.RFC3339), nil 42 | }, 43 | "TimeTool", "BasicTools", 44 | ) 45 | 46 | enzu.NewTool( 47 | "Add", 48 | "Adds two numbers", 49 | func(args ...interface{}) (interface{}, error) { 50 | if len(args) != 2 { 51 | return nil, fmt.Errorf("Add tool requires exactly 2 arguments") 52 | } 53 | a, ok1 := args[0].(float64) 54 | b, ok2 := args[1].(float64) 55 | if !ok1 || !ok2 { 56 | return nil, fmt.Errorf("Add tool arguments must be numbers") 57 | } 58 | return a + b, nil 59 | }, 60 | "MathTool", "BasicTools", 61 | ) 62 | 63 | // Create agents 64 | timeAgent := enzu.NewAgent("Time Agent", "Agent specialized in time-related tasks", llm, 65 | enzu.WithToolLists("TimeTool"), 66 | enzu.WithParallelExecution(true), // This agent will execute tasks in parallel 67 | ) 68 | mathAgent := enzu.NewAgent("Math Agent", "Agent specialized in mathematical operations", llm, 69 | enzu.WithToolLists("MathTool"), 70 | ) 71 | generalAgent := enzu.NewAgent("General Agent", "Agent with access to all tools", llm, 72 | enzu.WithToolLists("BasicTools"), 73 | enzu.WithInheritSynergyTools(true), // Explicitly set to true (default behavior) 74 | ) 75 | restrictedAgent := enzu.NewAgent("Restricted Agent", "Agent with no inheritance from Synergy", llm, 76 | enzu.WithToolLists("MathTool"), 77 | enzu.WithInheritSynergyTools(false), // This agent will not inherit Synergy tools 78 | ) 79 | 80 | // Create tasks 81 | timeTask := enzu.NewTask("What is the current time?", timeAgent) 82 | mathTask := enzu.NewTask("Add 5 and 7", mathAgent) 83 | combinedTask := enzu.NewTask("What is the current time and what is 5 + 7?", generalAgent) 84 | restrictedTask := enzu.NewTask("Add 10 and 20, and try to get the current time", restrictedAgent) 85 | 86 | // Create a synergy with a new ToolRegistry and the logger 87 | synergy := enzu.NewSynergy( 88 | "Demonstrate tool usage, agent specialization, and inheritance control", 89 | llm, 90 | enzu.WithAgents(timeAgent, mathAgent, generalAgent, restrictedAgent), 91 | enzu.WithTasks(timeTask, mathTask, combinedTask, restrictedTask), 92 | enzu.WithTools("TimeTool"), // This makes only TimeTool available to the synergy 93 | enzu.WithLogger(logger), // Use the created logger 94 | ) 95 | 96 | // Execute the synergy 97 | ctx := context.Background() 98 | results, err := synergy.Execute(ctx) 99 | if err != nil { 100 | log.Fatalf("Error executing synergy: %v", err) 101 | } 102 | 103 | // Print results 104 | fmt.Println("Synergy Results:") 105 | for task, result := range results { 106 | fmt.Printf("Task: %s\nResult: %s\n\n", task, result) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /examples/1_basic_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/teilomillet/enzu" 11 | "github.com/teilomillet/gollm" 12 | ) 13 | 14 | func main() { 15 | // Load API key from environment variable 16 | apiKey := os.Getenv("OPENAI_API_KEY") 17 | if apiKey == "" { 18 | log.Fatalf("OPENAI_API_KEY environment variable is not set") 19 | } 20 | 21 | // Create a new LLM instance with custom configuration 22 | llm, err := gollm.NewLLM( 23 | gollm.SetProvider("openai"), 24 | gollm.SetModel("gpt-4o-mini"), 25 | gollm.SetAPIKey(apiKey), 26 | gollm.SetMaxTokens(200), 27 | gollm.SetMaxRetries(3), 28 | gollm.SetRetryDelay(time.Second*2), 29 | gollm.SetLogLevel(gollm.LogLevelInfo), 30 | ) 31 | if err != nil { 32 | log.Fatalf("Failed to initialize LLM: %v", err) 33 | } 34 | 35 | // Create a logger for Enzu 36 | logger := enzu.NewLogger(enzu.LogLevelWarn) 37 | 38 | // Register tools 39 | enzu.NewTool( 40 | "WebSearch", 41 | "Performs web searches to gather market intelligence", 42 | func(args ...interface{}) (interface{}, error) { 43 | if len(args) != 1 { 44 | return nil, fmt.Errorf("WebSearch requires 1 argument (search query)") 45 | } 46 | query, ok := args[0].(string) 47 | if !ok { 48 | return nil, fmt.Errorf("Search query must be a string") 49 | } 50 | return "Web search results for: " + query, nil 51 | }, 52 | "WebSearchTools", 53 | ) 54 | 55 | enzu.NewTool( 56 | "StrategyAnalysis", 57 | "Analyzes market data to develop AI-focused marketing strategies", 58 | func(args ...interface{}) (interface{}, error) { 59 | if len(args) != 1 { 60 | return nil, fmt.Errorf("StrategyAnalysis requires 1 argument (market data)") 61 | } 62 | data, ok := args[0].(string) 63 | if !ok { 64 | return nil, fmt.Errorf("Market data must be a string") 65 | } 66 | return "Strategy analysis for: " + data, nil 67 | }, 68 | "StrategyTools", 69 | ) 70 | 71 | // Create agents 72 | marketAnalyst := enzu.NewAgent( 73 | "Lead Market Analyst", 74 | "Market Research Specialist", 75 | llm, 76 | enzu.WithToolLists("WebSearchTools"), 77 | ) 78 | 79 | marketingStrategist := enzu.NewAgent( 80 | "Chief Marketing Strategist", 81 | "Marketing Strategy Expert", 82 | llm, 83 | enzu.WithToolLists("StrategyTools"), 84 | ) 85 | 86 | contentCreator := enzu.NewAgent( 87 | "Creative Content Creator", 88 | "Content Creation Specialist", 89 | llm, 90 | ) 91 | 92 | // Create tasks 93 | researchTask := enzu.NewTask( 94 | "Analyze current market trends for AI solutions, focusing on multi-agent systems like CrewAI", 95 | marketAnalyst, 96 | ) 97 | 98 | strategyTask := enzu.NewTask( 99 | "Develop a marketing strategy for CrewAI, highlighting its unique features and benefits", 100 | marketingStrategist, 101 | researchTask, 102 | ) 103 | 104 | contentTask := enzu.NewTask( 105 | "Create marketing copy for a new CrewAI campaign, emphasizing its advantages over traditional AI solutions", 106 | contentCreator, 107 | strategyTask, 108 | ) 109 | 110 | // Create synergy with logger 111 | marketingSynergy := enzu.NewSynergy( 112 | "Develop and execute a marketing campaign for CrewAI", 113 | llm, 114 | enzu.WithAgents(marketAnalyst, marketingStrategist, contentCreator), 115 | enzu.WithTasks(researchTask, strategyTask, contentTask), 116 | enzu.WithLogger(logger), 117 | ) 118 | 119 | // Execute the synergy 120 | ctx := context.Background() 121 | results, err := marketingSynergy.Execute(ctx) 122 | if err != nil { 123 | log.Fatalf("Error executing synergy: %v", err) 124 | } 125 | 126 | // Print results 127 | for task, result := range results { 128 | fmt.Printf("Task: %s\nResult: %s\n\n", task, result) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /examples/6_api_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "github.com/teilomillet/enzu" 13 | "github.com/teilomillet/enzu/tools" 14 | "github.com/teilomillet/gollm" 15 | ) 16 | 17 | type Server struct { 18 | synergy *enzu.Synergy 19 | logger *enzu.Logger 20 | } 21 | 22 | func NewServer() (*Server, error) { 23 | // Load API key from environment variable 24 | openAIKey := os.Getenv("OPENAI_API_KEY") 25 | if openAIKey == "" { 26 | return nil, fmt.Errorf("OPENAI_API_KEY environment variable is not set") 27 | } 28 | 29 | // Create a new LLM instance 30 | llm, err := gollm.NewLLM( 31 | gollm.SetProvider("openai"), 32 | gollm.SetModel("gpt-4o-mini"), 33 | gollm.SetAPIKey(openAIKey), 34 | gollm.SetMaxTokens(200), 35 | ) 36 | if err != nil { 37 | return nil, fmt.Errorf("failed to initialize LLM: %v", err) 38 | } 39 | 40 | // Create a logger for Enzu 41 | logger := enzu.NewLogger(enzu.LogLevelInfo) 42 | 43 | // Define options for the ExaSearch tool 44 | exaSearchOptions := tools.ExaSearchOptions{ 45 | NumResults: 5, 46 | Type: "neural", 47 | Contents: tools.Contents{ 48 | Text: true, 49 | }, 50 | UseAutoprompt: true, 51 | StartPublishedDate: "2023-01-01T00:00:00.000Z", 52 | } 53 | 54 | // Register the ExaSearch tool 55 | tools.ExaSearch("", exaSearchOptions, "ResearchTool") 56 | 57 | // Create research agents 58 | researchAgent1 := enzu.NewAgent("Research Agent 1", "Agent specialized in AI research", llm, 59 | enzu.WithToolLists("ResearchTool"), 60 | enzu.WithParallelExecution(true), 61 | ) 62 | researchAgent2 := enzu.NewAgent("Research Agent 2", "Agent specialized in startup research", llm, 63 | enzu.WithToolLists("ResearchTool"), 64 | enzu.WithParallelExecution(true), 65 | ) 66 | 67 | // Create a synergy 68 | synergy := enzu.NewSynergy( 69 | "Parallel AI Research", 70 | llm, 71 | enzu.WithAgents(researchAgent1, researchAgent2), 72 | enzu.WithLogger(logger), 73 | ) 74 | 75 | return &Server{ 76 | synergy: synergy, 77 | logger: logger, 78 | }, nil 79 | } 80 | 81 | func (s *Server) handleExecute(w http.ResponseWriter, r *http.Request) { 82 | if r.Method != http.MethodPost { 83 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 84 | return 85 | } 86 | 87 | var request struct { 88 | Tasks []string `json:"tasks"` 89 | } 90 | 91 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 92 | http.Error(w, "Invalid request body", http.StatusBadRequest) 93 | return 94 | } 95 | 96 | // Create tasks based on the request 97 | var tasks []*enzu.Task 98 | agents := s.synergy.GetAgents() 99 | for i, taskDescription := range request.Tasks { 100 | agent := agents[i%len(agents)] // Assign tasks to agents in a round-robin fashion 101 | tasks = append(tasks, enzu.NewTask(taskDescription, agent)) 102 | } 103 | 104 | // Update synergy with new tasks 105 | s.synergy.SetTasks(tasks) 106 | 107 | // Execute the synergy 108 | ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute) 109 | defer cancel() 110 | 111 | results, err := s.synergy.Execute(ctx) 112 | if err != nil { 113 | http.Error(w, fmt.Sprintf("Error executing synergy: %v", err), http.StatusInternalServerError) 114 | return 115 | } 116 | 117 | // Send results back to the client 118 | w.Header().Set("Content-Type", "application/json") 119 | json.NewEncoder(w).Encode(results) 120 | } 121 | 122 | func main() { 123 | server, err := NewServer() 124 | if err != nil { 125 | log.Fatalf("Failed to create server: %v", err) 126 | } 127 | 128 | http.HandleFunc("/execute", server.handleExecute) 129 | 130 | port := os.Getenv("PORT") 131 | if port == "" { 132 | port = "8080" 133 | } 134 | 135 | log.Printf("Server starting on port %s", port) 136 | if err := http.ListenAndServe(":"+port, nil); err != nil { 137 | log.Fatalf("Failed to start server: %v", err) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /agent.go: -------------------------------------------------------------------------------- 1 | // Package enzu provides the core functionality for building and managing AI agents and synergies. 2 | // It implements a flexible framework for creating AI agents with specific roles, capabilities, 3 | // and behaviors, allowing them to work independently or collaboratively within a synergy. 4 | package enzu 5 | 6 | import ( 7 | "github.com/teilomillet/gollm" 8 | ) 9 | 10 | // Agent represents an AI agent with a specific role and capabilities within the Enzu framework. 11 | // Each agent has its own identity, language model, tool access, and execution preferences. 12 | // Agents can work independently or as part of a larger synergy, sharing tools and coordinating 13 | // actions with other agents. 14 | type Agent struct { 15 | // name is the unique identifier for this agent 16 | name string 17 | 18 | // role defines the agent's purpose and expected behavior 19 | role string 20 | 21 | // llm is the language model powering this agent's intelligence 22 | llm gollm.LLM 23 | 24 | // toolLists contains the names of tool collections this agent can access 25 | toolLists []string 26 | 27 | // inheritSynergyTools determines if the agent inherits tools from its parent synergy 28 | inheritSynergyTools bool 29 | 30 | // synergy is a reference to the parent synergy this agent belongs to 31 | synergy *Synergy 32 | 33 | // parallel indicates whether this agent can execute tasks concurrently 34 | parallel bool 35 | } 36 | 37 | // AgentOption is a function type for configuring an Agent using the functional options pattern. 38 | // It allows for flexible and extensible agent configuration without breaking existing code 39 | // when new options are added. 40 | type AgentOption func(*Agent) 41 | 42 | // NewAgent creates a new Agent with the specified name, role, language model, and options. 43 | // It initializes the agent with default settings that can be overridden using option functions. 44 | // 45 | // Parameters: 46 | // - name: Unique identifier for the agent 47 | // - role: Description of the agent's purpose and behavior 48 | // - llm: Language model instance that powers the agent 49 | // - opts: Variable number of AgentOption functions for additional configuration 50 | // 51 | // Returns: 52 | // - *Agent: A new agent instance configured with the specified options 53 | // 54 | // By default, agents are configured to: 55 | // - Inherit tools from their parent synergy 56 | // - Execute tasks sequentially (non-parallel) 57 | func NewAgent(name, role string, llm gollm.LLM, opts ...AgentOption) *Agent { 58 | a := &Agent{ 59 | name: name, 60 | role: role, 61 | llm: llm, 62 | inheritSynergyTools: true, // Default to inheriting Synergy tools 63 | parallel: false, // Default to sequential execution 64 | } 65 | for _, opt := range opts { 66 | opt(a) 67 | } 68 | return a 69 | } 70 | 71 | // WithToolLists specifies which ToolLists the Agent has access to. 72 | // This option allows you to grant the agent access to specific sets of tools 73 | // that it can use to accomplish its tasks. 74 | // 75 | // Parameters: 76 | // - lists: Variable number of tool list names to assign to the agent 77 | // 78 | // Returns: 79 | // - AgentOption: A function that configures the agent's tool lists when applied 80 | func WithToolLists(lists ...string) AgentOption { 81 | return func(a *Agent) { 82 | a.toolLists = lists 83 | } 84 | } 85 | 86 | // WithInheritSynergyTools controls whether the Agent inherits tool lists from its Synergy. 87 | // When enabled, the agent can access tools available to its parent synergy in addition 88 | // to its own tool lists. 89 | // 90 | // Parameters: 91 | // - inherit: If true, the agent will inherit tools from its parent synergy 92 | // 93 | // Returns: 94 | // - AgentOption: A function that configures the agent's tool inheritance when applied 95 | func WithInheritSynergyTools(inherit bool) AgentOption { 96 | return func(a *Agent) { 97 | a.inheritSynergyTools = inherit 98 | } 99 | } 100 | 101 | // WithParallelExecution sets whether tasks assigned to this agent should be executed in parallel. 102 | // When enabled, the agent can process multiple tasks concurrently, potentially improving 103 | // performance for independent operations. 104 | // 105 | // Parameters: 106 | // - parallel: If true, enables parallel task execution for this agent 107 | // 108 | // Returns: 109 | // - AgentOption: A function that configures the agent's execution mode when applied 110 | func WithParallelExecution(parallel bool) AgentOption { 111 | return func(a *Agent) { 112 | a.parallel = parallel 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /docs/tutorials/8_research_assistant.md: -------------------------------------------------------------------------------- 1 | # Enzu Research Assistant Tutorial 2 | 3 | This tutorial demonstrates how to build a comprehensive research assistant using the Enzu library. The example combines multiple Enzu features including synergy management, parallel task execution, and AI-powered research capabilities. 4 | 5 | ## Overview 6 | 7 | The research assistant is implemented as an HTTP server that: 8 | 1. Accepts research requests with a main topic and optional subtopics 9 | 2. Performs parallel research on all topics using AI agents 10 | 3. Synthesizes the results into a coherent summary 11 | 4. Returns the findings via a JSON response 12 | 13 | ## Key Components 14 | 15 | ### 1. Server Structure 16 | ```go 17 | type Server struct { 18 | manager *enzu.SynergyManager 19 | logger *enzu.Logger 20 | llm gollm.LLM 21 | } 22 | ``` 23 | - `manager`: Coordinates multiple synergies and their execution 24 | - `logger`: Provides structured logging 25 | - `llm`: Handles LLM interactions 26 | 27 | ### 2. Request/Response Format 28 | ```go 29 | type ResearchRequest struct { 30 | Topic string `json:"topic"` 31 | Subtopics []string `json:"subtopics,omitempty"` 32 | MaxResults int `json:"max_results,omitempty"` 33 | TimeoutSecs int `json:"timeout_secs,omitempty"` 34 | } 35 | 36 | type ResearchResponse struct { 37 | MainTopicResults map[string]interface{} `json:"main_topic_results"` 38 | SubtopicResults map[string]interface{} `json:"subtopic_results,omitempty"` 39 | ExecutionTime float64 `json:"execution_time_seconds"` 40 | } 41 | ``` 42 | 43 | ## Implementation Steps 44 | 45 | ### 1. Create Research Synergy 46 | The research synergy is responsible for gathering information: 47 | ```go 48 | func createResearchSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 49 | synergy := enzu.NewSynergy("Research Team") 50 | 51 | // Add research agents with different roles 52 | primaryResearcher := enzu.NewAgent("Primary Research", "Specialized in deep research and fact verification", llm) 53 | analysisResearcher := enzu.NewAgent("Analysis Research", "Specialized in data analysis and insights", llm) 54 | 55 | synergy.AddAgent(primaryResearcher) 56 | synergy.AddAgent(analysisResearcher) 57 | 58 | return synergy 59 | } 60 | ``` 61 | 62 | ### 2. Create Analysis Synergy 63 | The analysis synergy synthesizes the research results: 64 | ```go 65 | func createAnalysisSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 66 | synergy := enzu.NewSynergy("Analysis Team") 67 | 68 | analyst := enzu.NewAgent("Data Analyst", "Specialized in processing and analyzing research results", llm) 69 | synergy.AddAgent(analyst) 70 | 71 | return synergy 72 | } 73 | ``` 74 | 75 | ### 3. Handle Research Requests 76 | The main research process: 77 | 1. Create research synergy for main topic 78 | 2. Add subtopic tasks if provided 79 | 3. Create analysis synergy for result synthesis 80 | 4. Execute all synergies in parallel 81 | 5. Process and return results 82 | 83 | ## Example Usage 84 | 85 | 1. Start the server: 86 | ```bash 87 | go run examples/8_research_assistant_example.go 88 | ``` 89 | 90 | 2. Send a research request: 91 | ```bash 92 | curl -X POST http://localhost:8080/research \ 93 | -H "Content-Type: application/json" \ 94 | -d '{ 95 | "topic": "Latest advancements in quantum computing", 96 | "subtopics": ["quantum supremacy", "error correction"], 97 | "max_results": 5, 98 | "timeout_secs": 30 99 | }' 100 | ``` 101 | 102 | 3. Example Response: 103 | ```json 104 | { 105 | "main_topic_results": { 106 | "synthesis": "### Summary of Latest Advancements in Quantum Computing\n\n#### Key Insights\n\n1. **Advancements in Quantum Processing**:\n - Reconfigurable Atom Arrays\n - Universal Logic with Encoded Spin Qubits\n ..." 107 | }, 108 | "execution_time_seconds": 24.02 109 | } 110 | ``` 111 | 112 | ## Key Features 113 | 114 | 1. **Parallel Execution**: Multiple research tasks run concurrently 115 | 2. **Structured Logging**: Detailed logging at INFO and DEBUG levels 116 | 3. **Flexible Configuration**: Configurable timeouts and result limits 117 | 4. **Error Handling**: Robust error handling throughout the process 118 | 5. **Result Synthesis**: AI-powered analysis of research findings 119 | 120 | ## Best Practices 121 | 122 | 1. **Error Handling**: Always check for and handle errors appropriately 123 | 2. **Logging**: Use structured logging for better debugging 124 | 3. **Timeouts**: Set reasonable timeouts for research tasks 125 | 4. **Rate Limiting**: Consider implementing rate limiting for production use 126 | 5. **Response Size**: Monitor and potentially limit response sizes 127 | 128 | ## Advanced Usage 129 | 130 | ### Custom Agent Roles 131 | You can customize agent roles for specific research needs: 132 | ```go 133 | customAgent := enzu.NewAgent( 134 | "Domain Expert", 135 | "Specialized in specific domain knowledge", 136 | llm, 137 | ) 138 | ``` 139 | 140 | ### Enhanced Error Handling 141 | Add custom error handling for specific scenarios: 142 | ```go 143 | if err := synergy.Execute(ctx); err != nil { 144 | switch err.(type) { 145 | case *enzu.TimeoutError: 146 | // Handle timeout 147 | case *enzu.APIError: 148 | // Handle API errors 149 | default: 150 | // Handle other errors 151 | } 152 | } 153 | ``` 154 | 155 | ## Conclusion 156 | 157 | This example demonstrates the power of combining multiple Enzu features to create a sophisticated research assistant. The modular design allows for easy customization and extension while maintaining robust error handling and logging. 158 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // Package enzu provides logging functionality for the Enzu framework. 2 | // This file implements a flexible, leveled logging system that supports 3 | // categorized log messages with different severity levels and formatted output. 4 | package enzu 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // LogLevel represents the severity level of a log message. 15 | // Higher values indicate more verbose logging, with Debug being 16 | // the most verbose and Off completely disabling logging. 17 | type LogLevel int 18 | 19 | // Log level constants define the available logging severity levels. 20 | // These levels follow common logging conventions: 21 | // - LogLevelOff: Disables all logging 22 | // - LogLevelError: Only critical errors 23 | // - LogLevelWarn: Warnings and errors 24 | // - LogLevelInfo: General information, warnings, and errors 25 | // - LogLevelDebug: Detailed debug information and all above levels 26 | const ( 27 | LogLevelOff LogLevel = iota 28 | LogLevelError 29 | LogLevelWarn 30 | LogLevelInfo 31 | LogLevelDebug 32 | ) 33 | 34 | // Logger provides structured logging capabilities for the Enzu framework. 35 | // It supports different log levels, categorized messages, and formatted output 36 | // with timestamps. The logger can be configured to show or hide messages based 37 | // on their severity level. 38 | type Logger struct { 39 | // level determines which messages are logged based on their severity 40 | level LogLevel 41 | 42 | // logger is the underlying Go standard library logger 43 | logger *log.Logger 44 | } 45 | 46 | // NewLogger creates a new Logger instance with the specified log level. 47 | // The logger writes to standard output with a custom format that includes 48 | // timestamps, log levels, and message categories. 49 | // 50 | // Parameters: 51 | // - level: The minimum severity level of messages to log 52 | // 53 | // Returns: 54 | // - *Logger: A new logger instance configured with the specified level 55 | func NewLogger(level LogLevel) *Logger { 56 | return &Logger{ 57 | level: level, 58 | logger: log.New(os.Stdout, "", 0), 59 | } 60 | } 61 | 62 | // SetLevel changes the logger's minimum severity level. 63 | // Messages with a severity level lower than this will not be logged. 64 | // 65 | // Parameters: 66 | // - level: The new minimum severity level for logging 67 | func (l *Logger) SetLevel(level LogLevel) { 68 | l.level = level 69 | } 70 | 71 | // log is an internal method that handles the actual logging of messages. 72 | // It formats the message with a timestamp, level indicator, and category, 73 | // and writes it to the output if the message's level is within the logger's 74 | // configured severity threshold. 75 | // 76 | // Parameters: 77 | // - level: Severity level of the message 78 | // - category: Category or component the message relates to 79 | // - message: Format string for the log message 80 | // - args: Arguments to be formatted into the message 81 | func (l *Logger) log(level LogLevel, category, message string, args ...interface{}) { 82 | if level <= l.level { 83 | timestamp := time.Now().Format("15:04:05") 84 | levelStr := strings.ToUpper(level.String()) 85 | formattedMessage := fmt.Sprintf(message, args...) 86 | 87 | l.logger.Printf("\n%s [%-5s] %-15s\n%s\n%s\n", 88 | timestamp, levelStr, category, 89 | strings.Repeat("-", 50), 90 | formattedMessage) 91 | } 92 | } 93 | 94 | // Debug logs a message at DEBUG level. 95 | // These messages are typically used during development and debugging 96 | // to trace program execution and internal state. 97 | // 98 | // Parameters: 99 | // - category: The component or area of the code generating the message 100 | // - message: Format string for the log message 101 | // - args: Values to be formatted into the message 102 | func (l *Logger) Debug(category, message string, args ...interface{}) { 103 | l.log(LogLevelDebug, category, message, args...) 104 | } 105 | 106 | // Info logs a message at INFO level. 107 | // These messages provide general information about program execution 108 | // that could be helpful to users and administrators. 109 | // 110 | // Parameters: 111 | // - category: The component or area of the code generating the message 112 | // - message: Format string for the log message 113 | // - args: Values to be formatted into the message 114 | func (l *Logger) Info(category, message string, args ...interface{}) { 115 | l.log(LogLevelInfo, category, message, args...) 116 | } 117 | 118 | // Warn logs a message at WARN level. 119 | // These messages indicate potentially harmful situations or 120 | // unexpected states that the program can recover from. 121 | // 122 | // Parameters: 123 | // - category: The component or area of the code generating the message 124 | // - message: Format string for the log message 125 | // - args: Values to be formatted into the message 126 | func (l *Logger) Warn(category, message string, args ...interface{}) { 127 | l.log(LogLevelWarn, category, message, args...) 128 | } 129 | 130 | // Error logs a message at ERROR level. 131 | // These messages indicate serious problems that need 132 | // immediate attention from users or administrators. 133 | // 134 | // Parameters: 135 | // - category: The component or area of the code generating the message 136 | // - message: Format string for the log message 137 | // - args: Values to be formatted into the message 138 | func (l *Logger) Error(category, message string, args ...interface{}) { 139 | l.log(LogLevelError, category, message, args...) 140 | } 141 | 142 | // String converts a LogLevel to its string representation. 143 | // This method is used internally for formatting log messages 144 | // and implements the Stringer interface. 145 | // 146 | // Returns: 147 | // - string: The string representation of the log level (e.g., "DEBUG", "INFO") 148 | func (l LogLevel) String() string { 149 | return [...]string{"OFF", "ERROR", "WARN", "INFO", "DEBUG"}[l] 150 | } 151 | -------------------------------------------------------------------------------- /docs/tutorials/web-scraping-assistant.md: -------------------------------------------------------------------------------- 1 | # Tutorial: Building a Web Scraping Assistant 2 | 3 | In this tutorial, we'll build a web scraping assistant that can fetch web pages, extract information, and save the results. This example demonstrates how to combine multiple agents with different responsibilities to create a useful application. 4 | 5 | ## What We'll Build 6 | 7 | We'll create a system that can: 8 | 1. Fetch web pages 9 | 2. Extract specific information 10 | 3. Save the results to files 11 | 4. Handle errors gracefully 12 | 13 | ## Prerequisites 14 | 15 | ```go 16 | go get github.com/teilomillet/enzu 17 | go get github.com/PuerkitoBio/goquery // For HTML parsing 18 | ``` 19 | 20 | ## Step 1: Create the Tools 21 | 22 | First, let's create the tools our agents will need: 23 | 24 | ```go 25 | // Create network tools for fetching web pages 26 | enzu.NewTool( 27 | "FetchURL", 28 | "Fetches content from a URL", 29 | func(args ...interface{}) (interface{}, error) { 30 | if len(args) != 1 { 31 | return nil, fmt.Errorf("FetchURL requires 1 argument (url)") 32 | } 33 | url, ok := args[0].(string) 34 | if !ok { 35 | return nil, fmt.Errorf("URL must be a string") 36 | } 37 | 38 | resp, err := http.Get(url) 39 | if err != nil { 40 | return nil, err 41 | } 42 | defer resp.Body.Close() 43 | 44 | doc, err := goquery.NewDocumentFromReader(resp.Body) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return doc.Html() 50 | }, 51 | "NetworkTools", 52 | ) 53 | 54 | // Create parsing tools for extracting information 55 | enzu.NewTool( 56 | "ExtractText", 57 | "Extracts text content from HTML using CSS selector", 58 | func(args ...interface{}) (interface{}, error) { 59 | if len(args) != 2 { 60 | return nil, fmt.Errorf("ExtractText requires 2 arguments (html, selector)") 61 | } 62 | html, ok1 := args[0].(string) 63 | selector, ok2 := args[1].(string) 64 | if !ok1 || !ok2 { 65 | return nil, fmt.Errorf("Arguments must be strings") 66 | } 67 | 68 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | var results []string 74 | doc.Find(selector).Each(func(i int, s *goquery.Selection) { 75 | results = append(results, strings.TrimSpace(s.Text())) 76 | }) 77 | 78 | return results, nil 79 | }, 80 | "ParsingTools", 81 | ) 82 | ``` 83 | 84 | ## Step 2: Create the Agent 85 | 86 | Next, we'll create an agent that can use these tools: 87 | 88 | ```go 89 | agent := enzu.NewAgent( 90 | "WebScrapingAgent", 91 | "An agent that can fetch and extract information from web pages", 92 | llm, 93 | enzu.WithToolLists("NetworkTools", "ParsingTools"), 94 | ) 95 | ``` 96 | 97 | ## Step 3: Create Task and Synergy 98 | 99 | Create a task for the agent and a synergy to manage execution: 100 | 101 | ```go 102 | // Create task 103 | task := enzu.NewTask( 104 | "Fetch content from example.com and extract all paragraph text", 105 | agent, 106 | ) 107 | 108 | // Create synergy 109 | synergy := enzu.NewSynergy( 110 | "Web Scraping Example", 111 | llm, 112 | enzu.WithAgents(agent), 113 | enzu.WithTasks(task), 114 | ) 115 | ``` 116 | 117 | ## Step 4: Execute the Synergy 118 | 119 | Finally, execute the synergy to perform the web scraping: 120 | 121 | ```go 122 | ctx := context.Background() 123 | results, err := synergy.Execute(ctx) 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | fmt.Println("Extracted content:", results) 129 | ``` 130 | 131 | ## Complete Example 132 | 133 | For a complete working example, see [web_scraper_example.go](../../examples/web_scraper_example.go) in the examples directory. This example demonstrates all the concepts covered in this tutorial in a single, runnable file. 134 | 135 | The example includes: 136 | - Tool creation for URL fetching and text extraction 137 | - Agent setup with appropriate tool lists 138 | - Task and synergy creation 139 | - Error handling and result output 140 | 141 | Run the example with: 142 | ```bash 143 | go run examples/web_scraper_example.go 144 | ``` 145 | 146 | ## Running the Example 147 | 148 | 1. Save the code as `web_scraper.go` 149 | 2. Make sure your OpenAI API key is set: 150 | ```bash 151 | export OPENAI_API_KEY=your_api_key_here 152 | ``` 153 | 3. Run the program: 154 | ```bash 155 | go run web_scraper.go 156 | ``` 157 | 158 | ## Understanding the Flow 159 | 160 | 1. The `WebScrapingAgent` agent uses the `FetchURL` tool to download the webpage 161 | 2. The `WebScrapingAgent` agent uses the `ExtractText` tool to find and extract paragraph text 162 | 3. The results are printed to the console 163 | 164 | ## Extending the Example 165 | 166 | You can extend this example by: 167 | 168 | 1. Adding more parsing tools for different types of content 169 | 2. Creating a cleaning agent to process the extracted data 170 | 3. Adding error retry logic to the fetch tool 171 | 4. Implementing concurrent scraping of multiple pages 172 | 173 | ## Error Handling 174 | 175 | The example includes basic error handling, but you might want to add: 176 | 177 | 1. Retry logic for failed requests 178 | 2. Input validation 179 | 3. Rate limiting 180 | 4. Response status code checking 181 | 182 | ## Best Practices 183 | 184 | 1. **Respect Websites**: 185 | - Check robots.txt 186 | - Implement rate limiting 187 | - Add proper User-Agent headers 188 | 189 | 2. **Error Handling**: 190 | - Handle network errors gracefully 191 | - Validate all inputs 192 | - Log errors for debugging 193 | 194 | 3. **Data Management**: 195 | - Clean and validate extracted data 196 | - Handle different character encodings 197 | - Store data in appropriate formats 198 | 199 | 4. **Performance**: 200 | - Use parallel execution when appropriate 201 | - Implement caching if needed 202 | - Monitor memory usage 203 | 204 | Continue to the next tutorial to learn more advanced Enzu features! 205 | -------------------------------------------------------------------------------- /tools/exa_search.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "sync" 10 | "time" 11 | 12 | "github.com/teilomillet/enzu" 13 | ) 14 | 15 | // ExaSearchOptions represents the options for the ExaSearch tool 16 | type ExaSearchOptions struct { 17 | Type string 18 | UseAutoprompt bool 19 | NumResults int 20 | Contents Contents 21 | StartPublishedDate string 22 | } 23 | 24 | // Contents represents the contents options for the Exa.ai API 25 | type Contents struct { 26 | Text bool `json:"text"` 27 | } 28 | 29 | // ExaSearchResponse represents the response from the Exa.ai API 30 | type ExaSearchResponse struct { 31 | RequestID string `json:"requestId"` 32 | AutopromptString string `json:"autopromptString"` 33 | ResolvedSearchType string `json:"resolvedSearchType"` 34 | Results []ExaSearchResult `json:"results"` 35 | } 36 | 37 | // ExaSearchResult represents a single result from the Exa.ai API 38 | type ExaSearchResult struct { 39 | Score float64 `json:"score"` 40 | Title string `json:"title"` 41 | ID string `json:"id"` 42 | URL string `json:"url"` 43 | PublishedDate string `json:"publishedDate"` 44 | Author string `json:"author"` 45 | Text string `json:"text"` 46 | } 47 | 48 | // ExaSearchTool represents a tool for performing research using the Exa.ai API 49 | type ExaSearchTool struct { 50 | APIKey string 51 | Options ExaSearchOptions 52 | Logger *enzu.Logger 53 | client *http.Client 54 | } 55 | 56 | // NewExaSearchTool creates a new ExaSearchTool 57 | func NewExaSearchTool(apiKey string, options ExaSearchOptions, logger *enzu.Logger) *ExaSearchTool { 58 | return &ExaSearchTool{ 59 | APIKey: apiKey, 60 | Options: options, 61 | Logger: logger, 62 | client: &http.Client{Timeout: 10 * time.Second}, 63 | } 64 | } 65 | 66 | // Execute performs research queries using the Exa.ai API, supporting parallel execution 67 | func (t *ExaSearchTool) Execute(args ...interface{}) (interface{}, error) { 68 | if len(args) < 1 { 69 | return nil, fmt.Errorf("ExaSearch tool requires at least one argument (query or queries)") 70 | } 71 | 72 | var queries []string 73 | var opts ExaSearchOptions 74 | 75 | // Handle different types of first argument 76 | switch arg := args[0].(type) { 77 | case string: 78 | queries = []string{arg} 79 | case []string: 80 | queries = arg 81 | case []interface{}: 82 | for _, q := range arg { 83 | if s, ok := q.(string); ok { 84 | queries = append(queries, s) 85 | } else { 86 | return nil, fmt.Errorf("all queries must be strings") 87 | } 88 | } 89 | default: 90 | return nil, fmt.Errorf("first argument must be a string or a slice of strings") 91 | } 92 | 93 | // Handle options if provided 94 | if len(args) > 1 { 95 | if o, ok := args[1].(ExaSearchOptions); ok { 96 | opts = o 97 | } else { 98 | opts = t.Options 99 | } 100 | } else { 101 | opts = t.Options 102 | } 103 | 104 | results := make([]interface{}, len(queries)) 105 | errors := make([]error, len(queries)) 106 | var wg sync.WaitGroup 107 | 108 | for i, query := range queries { 109 | wg.Add(1) 110 | go func(i int, query string) { 111 | defer wg.Done() 112 | result, err := t.executeQuery(query, opts) 113 | results[i] = result 114 | errors[i] = err 115 | }(i, query) 116 | } 117 | 118 | wg.Wait() 119 | 120 | // Check for errors 121 | for _, err := range errors { 122 | if err != nil { 123 | return nil, err 124 | } 125 | } 126 | 127 | return results, nil 128 | } 129 | 130 | // executeQuery performs a single query to the Exa.ai API 131 | func (t *ExaSearchTool) executeQuery(query string, opts ExaSearchOptions) (interface{}, error) { 132 | url := "https://api.exa.ai/search" 133 | 134 | body := struct { 135 | Query string `json:"query"` 136 | Type string `json:"type"` 137 | UseAutoprompt bool `json:"useAutoprompt"` 138 | NumResults int `json:"numResults"` 139 | Contents Contents `json:"contents"` 140 | StartPublishedDate string `json:"startPublishedDate,omitempty"` 141 | }{ 142 | Query: query, 143 | Type: opts.Type, 144 | UseAutoprompt: opts.UseAutoprompt, 145 | NumResults: opts.NumResults, 146 | Contents: opts.Contents, 147 | StartPublishedDate: opts.StartPublishedDate, 148 | } 149 | 150 | jsonBody, err := json.Marshal(body) 151 | if err != nil { 152 | t.Logger.Error("ExaSearchTool", "failed to marshal request body: %v", err) 153 | return nil, fmt.Errorf("failed to marshal request body: %w", err) 154 | } 155 | 156 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody)) 157 | if err != nil { 158 | t.Logger.Error("ExaSearchTool", "failed to create HTTP request: %v", err) 159 | return nil, fmt.Errorf("failed to create HTTP request: %w", err) 160 | } 161 | 162 | req.Header.Set("accept", "application/json") 163 | req.Header.Set("content-type", "application/json") 164 | req.Header.Set("x-api-key", t.APIKey) 165 | 166 | resp, err := t.client.Do(req) 167 | if err != nil { 168 | t.Logger.Error("ExaSearchTool", "failed to send HTTP request: %v", err) 169 | return nil, fmt.Errorf("failed to send HTTP request: %w", err) 170 | } 171 | defer resp.Body.Close() 172 | 173 | var result ExaSearchResponse 174 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 175 | t.Logger.Error("ExaSearchTool", "failed to decode response: %v", err) 176 | return nil, fmt.Errorf("failed to decode response: %w", err) 177 | } 178 | 179 | var results []struct { 180 | Title string `json:"title"` 181 | URL string `json:"url"` 182 | } 183 | for _, res := range result.Results { 184 | results = append(results, struct { 185 | Title string `json:"title"` 186 | URL string `json:"url"` 187 | }{ 188 | Title: res.Title, 189 | URL: res.URL, 190 | }) 191 | } 192 | 193 | t.Logger.Info("ExaSearchTool", "Found %d results for query: %s", len(results), query) 194 | return results, nil 195 | } 196 | 197 | // ExaSearch returns a Tool for the ExaSearch tool with the given options 198 | func ExaSearch(apiKey string, options ExaSearchOptions, lists ...string) enzu.Tool { 199 | if apiKey == "" { 200 | apiKey = os.Getenv("EXA_API_KEY") 201 | if apiKey == "" { 202 | panic("EXA_API_KEY environment variable is not set") 203 | } 204 | } 205 | 206 | logger := enzu.NewLogger(enzu.LogLevelDebug) 207 | exaTool := NewExaSearchTool(apiKey, options, logger) 208 | 209 | return enzu.NewTool( 210 | "ExaSearch", 211 | "Performs research using the Exa.ai API and returns the titles and URLs of the results. Supports parallel execution.", 212 | exaTool.Execute, 213 | lists..., 214 | ) 215 | } 216 | -------------------------------------------------------------------------------- /manager.go: -------------------------------------------------------------------------------- 1 | // Package enzu implements a hierarchical AI orchestration system where SynergyManager 2 | // sits at the top level, coordinating multiple Synergies. Each Synergy, in turn, 3 | // manages multiple Agents working together toward specific objectives. 4 | // 5 | // The manager layer provides high-level orchestration capabilities: 6 | // - Parallel execution of multiple Synergies 7 | // - Result synthesis across different AI workflows 8 | // - Centralized logging and error handling 9 | // - Cross-Synergy coordination and conflict resolution 10 | package enzu 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | 16 | "github.com/teilomillet/gollm" 17 | ) 18 | 19 | // SynergyManager is the top-level coordinator in the Enzu framework's hierarchy. 20 | // It orchestrates multiple Synergies, each representing a distinct AI workflow, 21 | // and synthesizes their results into coherent outputs. 22 | // 23 | // The manager serves several key purposes in the framework: 24 | // 1. Workflow Orchestration: Coordinates multiple AI workflows running in parallel 25 | // 2. Result Synthesis: Combines outputs from different Synergies into meaningful insights 26 | // 3. Resource Management: Centralizes LLM usage and logging across workflows 27 | // 4. Error Handling: Provides system-wide error management and recovery 28 | type SynergyManager struct { 29 | // name identifies this manager instance 30 | name string 31 | 32 | // llm is the language model used for result synthesis 33 | llm gollm.LLM 34 | 35 | // synergies are the AI workflows being managed 36 | synergies []*Synergy 37 | 38 | // logger provides centralized logging for all managed Synergies 39 | logger *Logger 40 | } 41 | 42 | // NewSynergyManager creates a new SynergyManager instance that will coordinate 43 | // multiple AI workflows through their respective Synergies. 44 | // 45 | // The manager uses its own LLM instance specifically for synthesizing results 46 | // across different Synergies, ensuring that cross-workflow insights can be 47 | // generated without interfering with individual Synergy operations. 48 | // 49 | // Parameters: 50 | // - name: Identifier for this manager instance 51 | // - llm: Language model used for result synthesis 52 | // - logger: Centralized logger for all managed workflows 53 | // 54 | // Returns: 55 | // - *SynergyManager: A new manager instance ready to coordinate AI workflows 56 | func NewSynergyManager(name string, llm gollm.LLM, logger *Logger) *SynergyManager { 57 | return &SynergyManager{ 58 | name: name, 59 | llm: llm, 60 | synergies: make([]*Synergy, 0), 61 | logger: logger, 62 | } 63 | } 64 | 65 | // AddSynergy registers a new Synergy with the manager for coordination. 66 | // Each Synergy represents a distinct AI workflow with its own objective, 67 | // agents, and tools. The manager will execute and coordinate all registered 68 | // Synergies during workflow execution. 69 | // 70 | // Parameters: 71 | // - s: The Synergy to be managed 72 | func (sm *SynergyManager) AddSynergy(s *Synergy) { 73 | sm.synergies = append(sm.synergies, s) 74 | } 75 | 76 | // ExecuteSynergies runs all managed Synergies and synthesizes their results 77 | // into a coherent output. This is the main entry point for executing complex 78 | // AI workflows that require coordination across multiple objectives. 79 | // 80 | // The execution process: 81 | // 1. Runs all Synergies in sequence (future: parallel execution) 82 | // 2. Collects results from each Synergy 83 | // 3. Synthesizes all results using the manager's LLM 84 | // 4. Provides a unified view of the entire workflow's output 85 | // 86 | // Parameters: 87 | // - ctx: Context for cancellation and deadline control 88 | // - initialPrompt: The original user request or objective 89 | // 90 | // Returns: 91 | // - map[string]interface{}: Synthesized results from all Synergies 92 | // - error: Any error encountered during execution or synthesis 93 | func (sm *SynergyManager) ExecuteSynergies(ctx context.Context, initialPrompt string) (map[string]interface{}, error) { 94 | sm.logger.Info("SynergyManager", "Starting execution of all Synergies") 95 | 96 | allResults := make(map[string]interface{}) 97 | 98 | for _, synergy := range sm.synergies { 99 | sm.logger.Info("SynergyManager", "Executing Synergy: %s", synergy.objective) 100 | results, err := synergy.Execute(ctx) 101 | if err != nil { 102 | sm.logger.Error("SynergyManager", "Error executing Synergy %s: %v", synergy.objective, err) 103 | return nil, err 104 | } 105 | 106 | for k, v := range results { 107 | allResults[fmt.Sprintf("%s:%s", synergy.objective, k)] = v 108 | } 109 | } 110 | 111 | // Use the manager's LLM to synthesize the results 112 | synthesizedResults, err := sm.synthesizeResults(ctx, allResults, initialPrompt) 113 | if err != nil { 114 | sm.logger.Error("SynergyManager", "Error synthesizing results: %v", err) 115 | return nil, err 116 | } 117 | 118 | sm.logger.Info("SynergyManager", "All Synergies executed and results synthesized") 119 | return synthesizedResults, nil 120 | } 121 | 122 | // synthesizeResults combines outputs from multiple Synergies into a coherent summary. 123 | // It uses the manager's LLM to analyze results across different workflows, identify 124 | // patterns, resolve conflicts, and generate insights that might not be apparent 125 | // when looking at individual Synergy results in isolation. 126 | // 127 | // The synthesis process: 128 | // 1. Formats all Synergy results and the initial prompt 129 | // 2. Constructs a meta-prompt for the LLM to analyze the results 130 | // 3. Generates a comprehensive synthesis highlighting key insights 131 | // 132 | // Parameters: 133 | // - ctx: Context for cancellation and deadline control 134 | // - results: Combined results from all Synergies 135 | // - initialPrompt: The original user request for context 136 | // 137 | // Returns: 138 | // - map[string]interface{}: Synthesized insights and conclusions 139 | // - error: Any error encountered during synthesis 140 | func (sm *SynergyManager) synthesizeResults(ctx context.Context, results map[string]interface{}, initialPrompt string) (map[string]interface{}, error) { 141 | resultString := fmt.Sprintf("Initial prompt: %s\n\nResults from Synergies:\n", initialPrompt) 142 | for k, v := range results { 143 | resultString += fmt.Sprintf("%s: %v\n", k, v) 144 | } 145 | 146 | prompt := fmt.Sprintf( 147 | "As a manager overseeing multiple AI Synergies, synthesize the following results into a cohesive summary. "+ 148 | "Highlight key insights, resolve any conflicts, and provide an overall conclusion.\n\n%s", 149 | resultString, 150 | ) 151 | 152 | response, err := gollm.LLM(sm.llm).Generate(ctx, gollm.NewPrompt(prompt)) 153 | if err != nil { 154 | return nil, fmt.Errorf("error generating synthesis: %w", err) 155 | } 156 | 157 | return map[string]interface{}{"synthesis": response}, nil 158 | } 159 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Enzu 2 | 3 | ## Installation 4 | 5 | First, install Enzu using Go's package manager: 6 | 7 | ```bash 8 | go get github.com/teilomillet/enzu 9 | ``` 10 | 11 | Make sure you have an OpenAI API key set in your environment: 12 | ```bash 13 | export OPENAI_API_KEY=your_api_key_here 14 | ``` 15 | 16 | ## Basic Concepts 17 | 18 | Enzu is built around these main concepts: 19 | 20 | 1. **Tools**: Functions that agents can use to perform actions 21 | - Custom tools for specific tasks 22 | - Built-in tools like ExaSearch 23 | - Tool inheritance and specialization 24 | 25 | 2. **Agents**: AI entities that can use tools and capabilities 26 | - Specialized or general-purpose 27 | - Parallel execution support 28 | - Tool inheritance control 29 | 30 | 3. **Tasks**: Units of work assigned to agents 31 | - Sequential or parallel execution 32 | - Dependencies between tasks 33 | - Clear, focused objectives 34 | 35 | 4. **Synergies**: Groups of agents working together 36 | - Tool sharing between agents 37 | - Task orchestration 38 | - Logging and monitoring 39 | 40 | 5. **SynergyManager**: Orchestrator for multiple synergies 41 | - Interactive mode support 42 | - Multi-team coordination 43 | - Result synthesis 44 | 45 | ## Advanced Features 46 | 47 | ### Parallel Execution 48 | 49 | ```go 50 | // Create agent with parallel execution 51 | agent := enzu.NewAgent( 52 | "FastAgent", 53 | "Processes tasks in parallel", 54 | llm, 55 | enzu.WithToolLists("BasicTools"), 56 | enzu.WithParallelExecution(true), 57 | ) 58 | ``` 59 | 60 | ### Built-in Research Tools 61 | 62 | ```go 63 | // Configure and register ExaSearch 64 | options := tools.ExaSearchOptions{ 65 | NumResults: 3, 66 | Type: "neural", 67 | Contents: tools.Contents{ 68 | Text: true, 69 | }, 70 | } 71 | tools.ExaSearch("", options, "ResearchTool") 72 | ``` 73 | 74 | ### Interactive Mode 75 | 76 | ```go 77 | // Create interactive assistant 78 | manager := enzu.NewSynergyManager( 79 | "Interactive Assistant", 80 | llm, 81 | logger, 82 | ) 83 | 84 | // Add specialized synergies 85 | manager.AddSynergy(researchSynergy) 86 | manager.AddSynergy(analysisSynergy) 87 | 88 | // Start interactive loop 89 | fmt.Println("Ask me anything! Type 'exit' to quit.") 90 | scanner := bufio.NewScanner(os.Stdin) 91 | for scanner.Scan() { 92 | input := scanner.Text() 93 | if input == "exit" { 94 | break 95 | } 96 | results, _ := manager.ExecuteSynergies( 97 | context.Background(), 98 | input, 99 | ) 100 | fmt.Printf("Response: %v\n", results["synthesis"]) 101 | } 102 | ``` 103 | 104 | ## Your First Enzu Application 105 | 106 | Let's create an example that demonstrates these concepts with a tool management system: 107 | 108 | ```go 109 | package main 110 | 111 | import ( 112 | "context" 113 | "fmt" 114 | "log" 115 | "os" 116 | "time" 117 | 118 | "github.com/teilomillet/enzu" 119 | "github.com/teilomillet/gollm" 120 | ) 121 | 122 | func main() { 123 | // Initialize LLM 124 | llm, err := gollm.NewLLM( 125 | gollm.SetProvider("openai"), 126 | gollm.SetModel("gpt-4o-mini"), 127 | gollm.SetAPIKey(os.Getenv("OPENAI_API_KEY")), 128 | gollm.SetMaxTokens(200), 129 | gollm.SetLogLevel(gollm.LogLevelDebug), 130 | ) 131 | if err != nil { 132 | log.Fatal(err) 133 | } 134 | 135 | // Create a logger 136 | logger := enzu.NewLogger(enzu.LogLevelDebug) 137 | 138 | // Create specialized tools 139 | enzu.NewTool( 140 | "CurrentTime", 141 | "Returns the current time", 142 | func(args ...interface{}) (interface{}, error) { 143 | return time.Now().Format(time.RFC3339), nil 144 | }, 145 | "TimeTool", "BasicTools", // Tool belongs to two lists 146 | ) 147 | 148 | enzu.NewTool( 149 | "Add", 150 | "Adds two numbers", 151 | func(args ...interface{}) (interface{}, error) { 152 | if len(args) != 2 { 153 | return nil, fmt.Errorf("Add requires 2 arguments") 154 | } 155 | a, ok1 := args[0].(float64) 156 | b, ok2 := args[1].(float64) 157 | if !ok1 || !ok2 { 158 | return nil, fmt.Errorf("Arguments must be numbers") 159 | } 160 | return a + b, nil 161 | }, 162 | "MathTool", 163 | ) 164 | 165 | // Create specialized agents 166 | timeAgent := enzu.NewAgent( 167 | "Time Agent", 168 | "Handles time operations", 169 | llm, 170 | enzu.WithToolLists("TimeTool"), 171 | ) 172 | 173 | mathAgent := enzu.NewAgent( 174 | "Math Agent", 175 | "Handles calculations", 176 | llm, 177 | enzu.WithToolLists("MathTool"), 178 | ) 179 | 180 | // Create general agent that can use all tools 181 | generalAgent := enzu.NewAgent( 182 | "General Agent", 183 | "Can use all tools", 184 | llm, 185 | enzu.WithToolLists("BasicTools"), 186 | enzu.WithInheritSynergyTools(true), 187 | ) 188 | 189 | // Create tasks 190 | timeTask := enzu.NewTask( 191 | "What time is it?", 192 | timeAgent, 193 | ) 194 | 195 | mathTask := enzu.NewTask( 196 | "Add 5 and 7", 197 | mathAgent, 198 | ) 199 | 200 | generalTask := enzu.NewTask( 201 | "Tell me the time and add some numbers", 202 | generalAgent, 203 | ) 204 | 205 | // Create synergy 206 | synergy := enzu.NewSynergy( 207 | "Tool Demo", 208 | llm, 209 | enzu.WithAgents(timeAgent, mathAgent, generalAgent), 210 | enzu.WithTasks(timeTask, mathTask, generalTask), 211 | enzu.WithLogger(logger), 212 | ) 213 | 214 | // Execute synergy 215 | ctx := context.Background() 216 | results, err := synergy.Execute(ctx) 217 | if err != nil { 218 | log.Fatal(err) 219 | } 220 | 221 | // Print results 222 | for task, result := range results { 223 | fmt.Printf("Task: %s\nResult: %s\n\n", task, result) 224 | } 225 | } 226 | 227 | ## Understanding the Code 228 | 229 | 1. **Tool Creation**: 230 | - Tools can belong to multiple lists (`TimeTool`, `BasicTools`) 231 | - Each tool has clear validation and error handling 232 | - Tools are organized by functionality 233 | 234 | 2. **Agent Specialization**: 235 | - Time Agent only uses time tools 236 | - Math Agent only uses math tools 237 | - General Agent can use all tools 238 | 239 | 3. **Tool Inheritance**: 240 | - General Agent inherits tools from synergy 241 | - Specialized agents only use their assigned tools 242 | 243 | 4. **Task Organization**: 244 | - Each task is assigned to the appropriate agent 245 | - Tasks are clear and focused 246 | 247 | ## Next Steps 248 | 249 | 1. Learn about parallel execution in the [Core Concepts](./core-concepts.md#parallel-execution) guide 250 | 2. Try the built-in tools in the [Search Example](../examples/5_search_example.go) 251 | 3. Build an interactive assistant using the [Manager Mode Example](../examples/7_manager_mode_example.go) 252 | 253 | ## Common Issues 254 | 255 | 1. **API Key**: Ensure your OpenAI API key is set correctly 256 | 2. **Tool Lists**: Make sure tool lists exist before assigning to agents 257 | 3. **Task Dependencies**: Consider task order when using dependencies 258 | 4. **Parallel Execution**: Monitor resource usage with parallel tasks 259 | 5. **Interactive Mode**: Handle user input validation and error cases 260 | 261 | For more examples and detailed documentation, visit our [GitHub repository](https://github.com/teilomillet/enzu). 262 | -------------------------------------------------------------------------------- /tool.go: -------------------------------------------------------------------------------- 1 | // Package enzu implements a flexible and extensible tool system that enables 2 | // agents to interact with external services and perform complex operations. 3 | // The tool system provides: 4 | // - Dynamic tool registration and discovery 5 | // - Type-safe tool execution 6 | // - Tool organization through named lists 7 | // - Thread-safe concurrent access 8 | // - Validation of tool definitions 9 | package enzu 10 | 11 | import ( 12 | "fmt" 13 | "reflect" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | // Tool represents an executable capability that can be used by agents within the framework. 19 | // Tools are the primary mechanism for agents to interact with external systems, perform 20 | // computations, or access services. Each tool has: 21 | // - A unique name for identification 22 | // - A description of its functionality 23 | // - An Execute function that implements the tool's behavior 24 | // 25 | // Tools can be organized into lists and shared across agents and synergies. 26 | type Tool struct { 27 | // Name uniquely identifies the tool within the registry 28 | Name string 29 | 30 | // Description explains the tool's purpose and usage 31 | Description string 32 | 33 | // Execute implements the tool's functionality 34 | // Parameters: 35 | // - args: Variable number of arguments specific to the tool 36 | // Returns: 37 | // - interface{}: Tool-specific result 38 | // - error: Any error encountered during execution 39 | Execute func(args ...interface{}) (interface{}, error) 40 | } 41 | 42 | // ToolList represents a named collection of related tools. Lists enable: 43 | // - Logical grouping of related tools 44 | // - Sharing common tools across agents 45 | // - Access control through tool availability 46 | // - Organization of domain-specific capabilities 47 | type ToolList struct { 48 | // Name identifies the tool list 49 | Name string 50 | 51 | // Tools maps tool names to their implementations 52 | Tools map[string]Tool 53 | } 54 | 55 | // ToolRegistry manages the lifecycle and organization of tools within the framework. 56 | // It provides: 57 | // - Centralized tool management 58 | // - Thread-safe tool access 59 | // - Tool list organization 60 | // - Tool discovery and retrieval 61 | type ToolRegistry struct { 62 | // tools maps tool names to their implementations 63 | tools map[string]Tool 64 | 65 | // toolLists maps list names to ToolList instances 66 | toolLists map[string]*ToolList 67 | 68 | // mu ensures thread-safe access to the registry 69 | mu sync.Mutex 70 | } 71 | 72 | // NewToolRegistry creates a new ToolRegistry instance with an initialized 73 | // default "Tools" list. The registry serves as the central repository for 74 | // all tools in the framework. 75 | // 76 | // Returns: 77 | // - *ToolRegistry: A new registry instance ready for tool registration 78 | func NewToolRegistry() *ToolRegistry { 79 | tr := &ToolRegistry{ 80 | tools: make(map[string]Tool), 81 | toolLists: make(map[string]*ToolList), 82 | } 83 | tr.toolLists["Tools"] = &ToolList{Name: "Tools", Tools: make(map[string]Tool)} 84 | return tr 85 | } 86 | 87 | // NewTool creates and registers a new tool with the default registry. 88 | // It validates the tool definition and panics if validation fails. 89 | // 90 | // Parameters: 91 | // - name: Unique identifier for the tool 92 | // - description: Clear explanation of the tool's purpose 93 | // - execute: Function implementing the tool's behavior 94 | // - lists: Optional list names to add the tool to 95 | // 96 | // Returns: 97 | // - Tool: The created and registered tool instance 98 | // 99 | // Example: 100 | // calculator := NewTool( 101 | // "Calculator", 102 | // "Performs basic arithmetic operations", 103 | // func(args ...interface{}) (interface{}, error) { 104 | // // Implementation 105 | // }, 106 | // "Math", "Basic" 107 | // ) 108 | func NewTool(name, description string, execute func(args ...interface{}) (interface{}, error), lists ...string) Tool { 109 | tool := Tool{ 110 | Name: name, 111 | Description: description, 112 | Execute: execute, 113 | } 114 | 115 | if err := validateTool(tool); err != nil { 116 | panic(fmt.Sprintf("Invalid tool: %v", err)) 117 | } 118 | 119 | defaultRegistry.registerTool(tool, lists...) 120 | 121 | return tool 122 | } 123 | 124 | // registerTool adds a tool to the registry and specified tool lists. 125 | // If no lists are specified, the tool is added to the default "Tools" list. 126 | // 127 | // Parameters: 128 | // - tool: The tool to register 129 | // - lists: Optional list names to add the tool to 130 | func (tr *ToolRegistry) registerTool(tool Tool, lists ...string) { 131 | tr.mu.Lock() 132 | defer tr.mu.Unlock() 133 | 134 | tr.tools[tool.Name] = tool 135 | 136 | if len(lists) == 0 { 137 | lists = []string{"Tools"} 138 | } 139 | 140 | for _, listName := range lists { 141 | if list, exists := tr.toolLists[listName]; exists { 142 | list.Tools[tool.Name] = tool 143 | } else { 144 | newList := &ToolList{ 145 | Name: listName, 146 | Tools: make(map[string]Tool), 147 | } 148 | newList.Tools[tool.Name] = tool 149 | tr.toolLists[listName] = newList 150 | } 151 | } 152 | } 153 | 154 | // GetTool retrieves a tool from the registry by its name. 155 | // 156 | // Parameters: 157 | // - name: The name of the tool to retrieve 158 | // 159 | // Returns: 160 | // - Tool: The requested tool 161 | // - bool: True if the tool exists, false otherwise 162 | func (tr *ToolRegistry) GetTool(name string) (Tool, bool) { 163 | tool, exists := tr.tools[name] 164 | return tool, exists 165 | } 166 | 167 | // GetToolList retrieves a tool list from the registry by its name. 168 | // 169 | // Parameters: 170 | // - name: The name of the tool list to retrieve 171 | // 172 | // Returns: 173 | // - *ToolList: The requested tool list 174 | // - bool: True if the list exists, false otherwise 175 | func (tr *ToolRegistry) GetToolList(name string) (*ToolList, bool) { 176 | list, exists := tr.toolLists[name] 177 | return list, exists 178 | } 179 | 180 | // ListTools returns a formatted list of all available tools 181 | // and their descriptions. This is useful for: 182 | // - Tool discovery 183 | // - Documentation generation 184 | // - User interface presentation 185 | // 186 | // Returns: 187 | // - []string: List of tool descriptions in "name: description" format 188 | func (tr *ToolRegistry) ListTools() []string { 189 | var toolList []string 190 | for _, tool := range tr.tools { 191 | toolList = append(toolList, fmt.Sprintf("%s: %s", tool.Name, tool.Description)) 192 | } 193 | return toolList 194 | } 195 | 196 | // ListToolLists returns the names of all available tool lists 197 | // in the registry. This enables: 198 | // - List discovery 199 | // - Tool organization overview 200 | // - Configuration validation 201 | // 202 | // Returns: 203 | // - []string: List of tool list names 204 | func (tr *ToolRegistry) ListToolLists() []string { 205 | var listNames []string 206 | for name := range tr.toolLists { 207 | listNames = append(listNames, name) 208 | } 209 | return listNames 210 | } 211 | 212 | // validateTool ensures a tool definition meets the required schema: 213 | // - Name must be non-empty 214 | // - Description must be non-empty 215 | // - Execute must be a valid function 216 | // - Execute must return (interface{}, error) 217 | // 218 | // Parameters: 219 | // - tool: The tool to validate 220 | // 221 | // Returns: 222 | // - error: Validation error or nil if valid 223 | func validateTool(tool Tool) error { 224 | if strings.TrimSpace(tool.Name) == "" { 225 | return fmt.Errorf("tool name cannot be empty") 226 | } 227 | if strings.TrimSpace(tool.Description) == "" { 228 | return fmt.Errorf("tool description cannot be empty") 229 | } 230 | if tool.Execute == nil { 231 | return fmt.Errorf("tool must have an Execute function") 232 | } 233 | executeType := reflect.TypeOf(tool.Execute) 234 | if executeType.Kind() != reflect.Func { 235 | return fmt.Errorf("Execute must be a function") 236 | } 237 | if executeType.NumOut() != 2 { 238 | return fmt.Errorf("Execute function must return two values (result and error)") 239 | } 240 | if !executeType.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { 241 | return fmt.Errorf("second return value of Execute must be an error") 242 | } 243 | return nil 244 | } 245 | 246 | // defaultRegistry provides a package-level registry instance 247 | // for convenient tool registration and access. 248 | var defaultRegistry = NewToolRegistry() 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enzu: Multi-Agent Framework for AI Systems 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/teilomillet/enzu.svg)](https://pkg.go.dev/github.com/teilomillet/enzu) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/teilomillet/enzu)](https://goreportcard.com/report/github.com/teilomillet/enzu) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | Enzu is a declarative Go framework designed for building sophisticated multi-agent AI systems. It enables LLMs and AI agents to collaborate, execute parallel tasks, and leverage extensible tools while maintaining clear hierarchies and communication patterns. 8 | 9 | ## 🎯 Framework Capabilities 10 | 11 | ### Agent System Architecture 12 | - **Hierarchical Agent Organization**: Define agent roles, responsibilities, and relationships 13 | - **Dynamic Task Distribution**: Automatically route tasks to specialized agents 14 | - **Parallel Processing**: Execute multiple agent tasks concurrently 15 | - **State Management**: Track and maintain agent states across interactions 16 | 17 | ### Tool Integration System 18 | - **Declarative Tool Registry**: Register and manage tools with clear interfaces 19 | - **Inheritance Patterns**: Tools can be inherited and shared across agent hierarchies 20 | - **Thread-Safe Operations**: Concurrent tool access with built-in safety mechanisms 21 | - **Custom Tool Creation**: Extend functionality through a standardized tool interface 22 | 23 | ### Execution Patterns 24 | - **Synergy-Based Collaboration**: Group agents into task-focused collaborative units 25 | - **Context Propagation**: Share context and state across agent boundaries 26 | - **Parallel Task Execution**: Optimize performance through concurrent processing 27 | - **Error Recovery**: Built-in retry mechanisms and error handling patterns 28 | 29 | ### Communication Infrastructure 30 | - **HTTP Server Integration**: Built-in REST API capabilities 31 | - **Structured Message Passing**: Type-safe communication between agents 32 | - **Event System**: Publish-subscribe patterns for agent coordination 33 | - **Logging System**: Comprehensive tracing and debugging capabilities 34 | 35 | ## 🔧 Core Integration Patterns 36 | 37 | ### 1. Research and Analysis Pattern 38 | ```go 39 | // Pattern: Distributed Research System 40 | type ResearchRequest struct { 41 | Topic string `json:"topic"` 42 | Subtopics []string `json:"subtopics,omitempty"` 43 | MaxResults int `json:"max_results,omitempty"` 44 | TimeoutSecs int `json:"timeout_secs,omitempty"` 45 | } 46 | 47 | // Create specialized research agents 48 | researcher := enzu.NewAgent("Primary Researcher", 49 | "Deep research and fact verification", 50 | llm, 51 | enzu.WithToolLists("ResearchTool"), 52 | enzu.WithParallelExecution(true), 53 | ) 54 | 55 | analyst := enzu.NewAgent("Data Analyst", 56 | "Process and analyze research results", 57 | llm, 58 | enzu.WithToolLists("AnalysisTool"), 59 | enzu.WithParallelExecution(true), 60 | ) 61 | ``` 62 | 63 | ### 2. Self-Aware System Pattern 64 | ```go 65 | // Pattern: Self-Aware Interactive System 66 | manager := enzu.NewSynergyManager("Self-Aware System", llm, logger) 67 | 68 | // Define capability domains 69 | researchSynergy := createDomainSynergy("Research", llm, logger) 70 | analysisSynergy := createDomainSynergy("Analysis", llm, logger) 71 | creativeSynergy := createDomainSynergy("Creative", llm, logger) 72 | 73 | // Register domains 74 | manager.AddSynergy(researchSynergy) 75 | manager.AddSynergy(analysisSynergy) 76 | manager.AddSynergy(creativeSynergy) 77 | ``` 78 | 79 | ### 3. Tool Integration Pattern 80 | ```go 81 | // Pattern: Extensible Tool System 82 | exaSearchOptions := tools.ExaSearchOptions{ 83 | NumResults: 5, 84 | Type: "neural", 85 | Contents: tools.Contents{ 86 | Text: true, 87 | }, 88 | UseAutoprompt: true, 89 | StartPublishedDate: "2023-01-01T00:00:00.000Z", 90 | } 91 | tools.RegisterTool("ResearchTool", exaSearchOptions) 92 | ``` 93 | 94 | ### 4. API Integration Pattern 95 | ```go 96 | // Pattern: Multi-Agent API Server 97 | type Server struct { 98 | synergy *enzu.Synergy 99 | logger *enzu.Logger 100 | } 101 | 102 | // Initialize server with parallel processing capabilities 103 | func NewServer() (*Server, error) { 104 | // Create research agents with specific roles 105 | researchAgent1 := enzu.NewAgent("Research Agent 1", 106 | "Agent specialized in AI research", 107 | llm, 108 | enzu.WithToolLists("ResearchTool"), 109 | enzu.WithParallelExecution(true), 110 | ) 111 | researchAgent2 := enzu.NewAgent("Research Agent 2", 112 | "Agent specialized in startup research", 113 | llm, 114 | enzu.WithToolLists("ResearchTool"), 115 | enzu.WithParallelExecution(true), 116 | ) 117 | 118 | // Create parallel processing synergy 119 | synergy := enzu.NewSynergy( 120 | "Parallel AI Research", 121 | llm, 122 | enzu.WithAgents(researchAgent1, researchAgent2), 123 | enzu.WithLogger(logger), 124 | ) 125 | 126 | return &Server{synergy: synergy, logger: logger}, nil 127 | } 128 | 129 | // Handle parallel task execution 130 | func (s *Server) handleExecute(w http.ResponseWriter, r *http.Request) { 131 | var request struct { 132 | Tasks []string `json:"tasks"` 133 | } 134 | 135 | // Distribute tasks among agents 136 | agents := s.synergy.GetAgents() 137 | for i, taskDescription := range request.Tasks { 138 | agent := agents[i%len(agents)] // Round-robin distribution 139 | tasks = append(tasks, enzu.NewTask(taskDescription, agent)) 140 | } 141 | } 142 | ``` 143 | 144 | ## 🚀 Capability Domains 145 | 146 | ### 1. Research & Information Retrieval 147 | - Neural search integration (`ExaSearch` tool) 148 | - Multi-agent research coordination 149 | - Parallel information gathering 150 | - Research result synthesis 151 | 152 | ### 2. Task Management & Execution 153 | - Multi-agent task distribution 154 | - Parallel task execution 155 | - Progress tracking 156 | - Result aggregation 157 | 158 | ### 3. Web Content Processing 159 | - URL content fetching (`FetchURL` tool) 160 | - HTML parsing and extraction 161 | - CSS selector-based targeting 162 | - Structured data collection 163 | 164 | ### 4. Synergy Management 165 | - Multi-synergy orchestration 166 | - Result synthesis across synergies 167 | - Team-based agent organization 168 | - Cross-team coordination 169 | - Hierarchical task execution 170 | 171 | ### 5. Team Organization 172 | - Role-specialized agents 173 | - Team-based synergies 174 | - Domain-specific agent groups 175 | - Task-team alignment 176 | 177 | ### 6. API Integration & Scaling 178 | - **Parallel Task Distribution** 179 | - Round-robin task assignment 180 | - Load-balanced processing 181 | - Concurrent execution 182 | - Real-time response handling 183 | 184 | - **HTTP Service Integration** 185 | - RESTful endpoints 186 | - JSON request/response 187 | - Error handling patterns 188 | - Status monitoring 189 | 190 | - **Multi-Agent Coordination** 191 | - Role-based agent assignment 192 | - Task synchronization 193 | - Result aggregation 194 | - State management 195 | 196 | ## 📦 Installation 197 | 198 | ```bash 199 | go get github.com/teilomillet/enzu 200 | ``` 201 | 202 | ## 📚 Integration Resources 203 | 204 | ### Core Documentation 205 | - `/docs`: Architecture and integration guides 206 | - `/docs/tutorials`: Step-by-step implementation patterns 207 | - `/examples`: Reference implementations and use cases 208 | 209 | ### Example Implementations 210 | 1. Research Assistant System (`examples/8_research_assistant_example.go`) 211 | 2. Self-Aware System (`examples/7_manager_mode_example.go`) 212 | 3. Parallel Processing System (`examples/4_parallel_example.go`) 213 | 4. Tool Integration System (`examples/3_tools_example.go`) 214 | 5. API Integration System (`examples/6_api_example.go`) 215 | 216 | ### Integration Patterns 217 | 1. **HTTP API Integration** 218 | - REST endpoint creation 219 | - Request/Response handling 220 | - Timeout management 221 | - Error recovery 222 | - Round-robin task distribution 223 | - Load balancing strategies 224 | 225 | 2. **Tool Registry Integration** 226 | - Tool registration 227 | - Capability inheritance 228 | - Access control 229 | - Resource management 230 | 231 | 3. **Agent Collaboration** 232 | - Task distribution 233 | - Result synthesis 234 | - Context sharing 235 | - Error handling 236 | -------------------------------------------------------------------------------- /examples/8_research_assistant_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "github.com/teilomillet/enzu" 13 | "github.com/teilomillet/enzu/tools" 14 | "github.com/teilomillet/gollm" 15 | ) 16 | 17 | // Server represents our HTTP server with AI capabilities 18 | type Server struct { 19 | manager *enzu.SynergyManager 20 | logger *enzu.Logger 21 | llm gollm.LLM 22 | } 23 | 24 | // ResearchRequest represents the structure of research requests 25 | type ResearchRequest struct { 26 | Topic string `json:"topic"` 27 | Subtopics []string `json:"subtopics,omitempty"` 28 | MaxResults int `json:"max_results,omitempty"` 29 | TimeoutSecs int `json:"timeout_secs,omitempty"` 30 | } 31 | 32 | // ResearchResponse represents the structure of research responses 33 | type ResearchResponse struct { 34 | MainTopicResults map[string]interface{} `json:"main_topic_results"` 35 | SubtopicResults map[string]interface{} `json:"subtopic_results,omitempty"` 36 | ExecutionTime float64 `json:"execution_time_seconds"` 37 | } 38 | 39 | func NewServer() (*Server, error) { 40 | // Load API key from environment variable 41 | openAIKey := os.Getenv("OPENAI_API_KEY") 42 | if openAIKey == "" { 43 | return nil, fmt.Errorf("OPENAI_API_KEY environment variable is not set") 44 | } 45 | 46 | // Create a new LLM instance 47 | llm, err := gollm.NewLLM( 48 | gollm.SetProvider("openai"), 49 | gollm.SetModel("gpt-4o-mini"), 50 | gollm.SetAPIKey(openAIKey), 51 | gollm.SetMaxTokens(300), 52 | gollm.SetMaxRetries(3), 53 | gollm.SetRetryDelay(time.Second*2), 54 | gollm.SetLogLevel(gollm.LogLevelInfo), 55 | ) 56 | if err != nil { 57 | return nil, fmt.Errorf("failed to initialize LLM: %v", err) 58 | } 59 | 60 | // Create a logger for Enzu 61 | logger := enzu.NewLogger(enzu.LogLevelInfo) 62 | 63 | // Register the ExaSearch tool 64 | exaSearchOptions := tools.ExaSearchOptions{ 65 | NumResults: 5, 66 | Type: "neural", 67 | Contents: tools.Contents{ 68 | Text: true, 69 | }, 70 | UseAutoprompt: true, 71 | StartPublishedDate: "2023-01-01T00:00:00.000Z", 72 | } 73 | tools.ExaSearch("", exaSearchOptions, "ResearchTool") 74 | 75 | // Create SynergyManager 76 | manager := enzu.NewSynergyManager("Advanced Research Assistant", llm, logger) 77 | 78 | return &Server{ 79 | manager: manager, 80 | logger: logger, 81 | llm: llm, 82 | }, nil 83 | } 84 | 85 | func createResearchSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 86 | agent1 := enzu.NewAgent("Primary Research", "Specialized in deep research and fact verification", llm, 87 | enzu.WithToolLists("ResearchTool"), 88 | enzu.WithParallelExecution(true), 89 | ) 90 | agent2 := enzu.NewAgent("Analysis Research", "Specialized in data analysis and insights", llm, 91 | enzu.WithToolLists("ResearchTool"), 92 | enzu.WithParallelExecution(true), 93 | ) 94 | 95 | return enzu.NewSynergy( 96 | "Research Team", 97 | llm, 98 | enzu.WithAgents(agent1, agent2), 99 | enzu.WithLogger(logger), 100 | ) 101 | } 102 | 103 | func createAnalysisSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 104 | agent := enzu.NewAgent("Data Analyst", "Specialized in processing and analyzing research results", llm, 105 | enzu.WithToolLists("ResearchTool"), 106 | enzu.WithParallelExecution(true), 107 | ) 108 | 109 | return enzu.NewSynergy( 110 | "Analysis Team", 111 | llm, 112 | enzu.WithAgents(agent), 113 | enzu.WithLogger(logger), 114 | ) 115 | } 116 | 117 | func (s *Server) handleResearch(w http.ResponseWriter, r *http.Request) { 118 | if r.Method != http.MethodPost { 119 | s.logger.Error("API", "Invalid method: %s, expected POST", r.Method) 120 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 121 | return 122 | } 123 | 124 | var req ResearchRequest 125 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 126 | s.logger.Error("API", "Failed to decode request body: %v", err) 127 | http.Error(w, "Invalid request body", http.StatusBadRequest) 128 | return 129 | } 130 | 131 | s.logger.Info("API", "Received research request for topic: %s with %d subtopics", 132 | req.Topic, len(req.Subtopics)) 133 | 134 | // Set defaults if not provided 135 | if req.MaxResults == 0 { 136 | req.MaxResults = 5 137 | s.logger.Debug("API", "Using default MaxResults: %d", req.MaxResults) 138 | } 139 | if req.TimeoutSecs == 0 { 140 | req.TimeoutSecs = 30 141 | s.logger.Debug("API", "Using default TimeoutSecs: %d", req.TimeoutSecs) 142 | } 143 | 144 | // Create context with timeout 145 | ctx, cancel := context.WithTimeout(r.Context(), time.Duration(req.TimeoutSecs)*time.Second) 146 | defer cancel() 147 | 148 | // Create tasks for research synergy 149 | s.logger.Info("Research", "Creating research synergy for main topic: %s", req.Topic) 150 | researchSynergy := createResearchSynergy(s.llm, s.logger) 151 | mainTask := enzu.NewTask(fmt.Sprintf("Research thoroughly: %s", req.Topic), researchSynergy.GetAgents()[0]) 152 | researchSynergy.AddTask(mainTask) 153 | 154 | // Add subtopic tasks if any 155 | if len(req.Subtopics) > 0 { 156 | s.logger.Info("Research", "Adding %d subtopic tasks", len(req.Subtopics)) 157 | for _, subtopic := range req.Subtopics { 158 | s.logger.Debug("Research", "Adding subtopic task: %s", subtopic) 159 | subtask := enzu.NewTask( 160 | fmt.Sprintf("Research subtopic: %s in context of %s", subtopic, req.Topic), 161 | researchSynergy.GetAgents()[1], 162 | ) 163 | researchSynergy.AddTask(subtask) 164 | } 165 | } 166 | 167 | // Create analysis synergy with task 168 | s.logger.Info("Analysis", "Creating analysis synergy") 169 | analysisSynergy := createAnalysisSynergy(s.llm, s.logger) 170 | analysisTask := enzu.NewTask(fmt.Sprintf("Analyze and synthesize research results for topic '%s' and subtopics %v", req.Topic, req.Subtopics), analysisSynergy.GetAgents()[0]) 171 | analysisSynergy.AddTask(analysisTask) 172 | 173 | // Add both synergies to manager 174 | s.logger.Info("Manager", "Adding synergies to manager") 175 | s.manager.AddSynergy(researchSynergy) 176 | s.manager.AddSynergy(analysisSynergy) 177 | 178 | // Execute all synergies 179 | startTime := time.Now() 180 | s.logger.Info("Manager", "Starting execution of all synergies") 181 | results, err := s.manager.ExecuteSynergies(ctx, req.Topic) 182 | if err != nil { 183 | s.logger.Error("Manager", "Failed to execute synergies: %v", err) 184 | http.Error(w, fmt.Sprintf("Error executing synergies: %v", err), http.StatusInternalServerError) 185 | return 186 | } 187 | 188 | executionTime := time.Since(startTime).Seconds() 189 | s.logger.Info("Manager", "Synergies execution completed in %.2f seconds", executionTime) 190 | 191 | // Log results details 192 | if results != nil { 193 | s.logger.Info("Results", "Number of result keys: %d", len(results)) 194 | for key := range results { 195 | s.logger.Debug("Results", "Found result key: %s", key) 196 | } 197 | 198 | if synthesis, ok := results["synthesis"]; ok { 199 | // Log first 100 characters of synthesis for preview 200 | synthStr, _ := synthesis.(string) 201 | previewLen := 100 202 | if len(synthStr) < previewLen { 203 | previewLen = len(synthStr) 204 | } 205 | s.logger.Info("Results", "Synthesis preview: %s...", synthStr[:previewLen]) 206 | } 207 | } 208 | 209 | // Prepare response 210 | response := ResearchResponse{ 211 | MainTopicResults: results, 212 | SubtopicResults: nil, // Results are now combined in MainTopicResults 213 | ExecutionTime: executionTime, 214 | } 215 | 216 | // Log response details 217 | responseJSON, err := json.MarshalIndent(response, "", " ") 218 | if err != nil { 219 | s.logger.Error("API", "Failed to marshal response: %v", err) 220 | } else { 221 | s.logger.Debug("API", "Full response:\n%s", string(responseJSON)) 222 | } 223 | 224 | s.logger.Info("API", "Sending response with %d main results", len(results)) 225 | w.Header().Set("Content-Type", "application/json") 226 | json.NewEncoder(w).Encode(response) 227 | } 228 | 229 | func main() { 230 | server, err := NewServer() 231 | if err != nil { 232 | log.Fatalf("Failed to create server: %v", err) 233 | } 234 | 235 | // Set logger to debug level for more detailed logs 236 | server.logger.SetLevel(enzu.LogLevelDebug) 237 | 238 | // Register routes 239 | http.HandleFunc("/research", server.handleResearch) 240 | 241 | // Start the server 242 | port := ":8080" 243 | fmt.Printf("Server starting on port %s...\n", port) 244 | if err := http.ListenAndServe(port, nil); err != nil { 245 | log.Fatalf("Failed to start server: %v", err) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /docs/tutorials/manager-mode-assistant.md: -------------------------------------------------------------------------------- 1 | # Building an Interactive AI Assistant with Manager Mode 2 | 3 | This tutorial demonstrates how to build a sophisticated interactive AI assistant using Enzu's manager mode. We'll create an assistant that can handle research, analysis, and creative tasks through specialized synergies. 4 | 5 | ## Overview 6 | 7 | The manager mode allows you to: 8 | 1. Create multiple specialized synergies for different capabilities 9 | 2. Handle interactive conversations with users 10 | 3. Maintain conversation history for context 11 | 4. Route tasks to appropriate synergies based on user input 12 | 13 | ## Prerequisites 14 | 15 | - Go 1.16 or later 16 | - OpenAI API key 17 | - Enzu framework 18 | - Basic understanding of Enzu concepts (agents, tools, synergies) 19 | 20 | ## Step 1: Define Assistant Capabilities 21 | 22 | First, define your assistant's capabilities clearly. This helps users understand what the assistant can do and helps the AI provide appropriate responses: 23 | 24 | ```go 25 | const aiCapabilities = ` 26 | I am an AI assistant with the following capabilities: 27 | 28 | 1. Research and Fact-Checking: 29 | - Conduct web searches on various topics 30 | - Verify and validate information 31 | - Provide summaries of researched topics 32 | 33 | 2. Data Analysis and Insights: 34 | - Analyze data sets 35 | - Identify patterns and trends 36 | - Generate insights and conclusions 37 | 38 | 3. Creative Content Generation: 39 | - Generate original content 40 | - Edit and refine content 41 | - Assist with brainstorming 42 | 43 | [...] 44 | ` 45 | ``` 46 | 47 | ## Step 2: Initialize Core Components 48 | 49 | Set up the LLM and logger with appropriate configuration: 50 | 51 | ```go 52 | // Create LLM instance with retry logic 53 | llm, err := gollm.NewLLM( 54 | gollm.SetProvider("openai"), 55 | gollm.SetModel("gpt-4o-mini"), 56 | gollm.SetAPIKey(openAIKey), 57 | gollm.SetMaxTokens(300), 58 | gollm.SetMaxRetries(3), 59 | gollm.SetRetryDelay(time.Second*2), 60 | ) 61 | 62 | // Create logger for monitoring 63 | logger := enzu.NewLogger(enzu.LogLevelInfo) 64 | ``` 65 | 66 | ## Step 3: Register Built-in Tools 67 | 68 | Register any built-in tools that your assistant will need: 69 | 70 | ```go 71 | // Configure ExaSearch for web research 72 | exaSearchOptions := tools.ExaSearchOptions{ 73 | NumResults: 3, 74 | Type: "neural", 75 | Contents: tools.Contents{ 76 | Text: true, 77 | }, 78 | UseAutoprompt: true, 79 | } 80 | tools.ExaSearch("", exaSearchOptions, "ResearchTool") 81 | ``` 82 | 83 | ## Step 4: Create Specialized Synergies 84 | 85 | Create separate synergies for different types of tasks: 86 | 87 | ```go 88 | func createResearchSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 89 | // Create specialized agents 90 | researcher := enzu.NewAgent( 91 | "Web Researcher", 92 | "Perform web searches and summarize information", 93 | llm, 94 | enzu.WithToolLists("ResearchTool"), 95 | ) 96 | factChecker := enzu.NewAgent( 97 | "Fact Checker", 98 | "Verify information and cross-reference sources", 99 | llm, 100 | ) 101 | 102 | // Create research tasks 103 | tasks := []*enzu.Task{ 104 | enzu.NewTask("Conduct research", researcher), 105 | enzu.NewTask("Verify findings", factChecker), 106 | } 107 | 108 | // Create and return synergy 109 | return enzu.NewSynergy( 110 | "Research Operations", 111 | llm, 112 | enzu.WithAgents(researcher, factChecker), 113 | enzu.WithTasks(tasks...), 114 | enzu.WithLogger(logger), 115 | ) 116 | } 117 | ``` 118 | 119 | ## Step 5: Set Up the SynergyManager 120 | 121 | Create and configure the SynergyManager to orchestrate all synergies: 122 | 123 | ```go 124 | // Create manager 125 | manager := enzu.NewSynergyManager( 126 | "Self-Aware Interactive AI Assistant", 127 | llm, 128 | logger, 129 | ) 130 | 131 | // Add specialized synergies 132 | manager.AddSynergy(createResearchSynergy(llm, logger)) 133 | manager.AddSynergy(createAnalysisSynergy(llm, logger)) 134 | manager.AddSynergy(createCreativeSynergy(llm, logger)) 135 | ``` 136 | 137 | ## Step 6: Implement Conversation Handling 138 | 139 | Create functions to handle user input and maintain conversation history: 140 | 141 | ```go 142 | func handleUserInput(manager *enzu.SynergyManager, input string, history []string) string { 143 | // Check for capability questions 144 | if isAskingAboutCapabilities(input) { 145 | return explainCapabilities() 146 | } 147 | 148 | // Create context-aware prompt 149 | historyContext := strings.Join(history, "\n") 150 | prompt := fmt.Sprintf(` 151 | AI Capabilities: 152 | %s 153 | 154 | Conversation history: 155 | %s 156 | 157 | Current user input: %s 158 | `, aiCapabilities, historyContext, input) 159 | 160 | // Execute synergies with context 161 | results, err := manager.ExecuteSynergies(context.Background(), prompt) 162 | if err != nil { 163 | return fmt.Sprintf("Error: %v", err) 164 | } 165 | 166 | return results["synthesis"].(string) 167 | } 168 | ``` 169 | 170 | ## Step 7: Create the Interactive Loop 171 | 172 | Implement the main interaction loop: 173 | 174 | ```go 175 | func main() { 176 | // ... initialization code ... 177 | 178 | reader := bufio.NewReader(os.Stdin) 179 | history := []string{} 180 | 181 | for { 182 | fmt.Print("\nYou: ") 183 | input, _ := reader.ReadString('\n') 184 | input = strings.TrimSpace(input) 185 | 186 | if input == "exit" { 187 | break 188 | } 189 | 190 | // Get response 191 | response := handleUserInput(manager, input, history) 192 | fmt.Printf("\nAI Assistant: %s\n", response) 193 | 194 | // Update history 195 | history = append(history, 196 | fmt.Sprintf("User: %s", input), 197 | fmt.Sprintf("Assistant: %s", response), 198 | ) 199 | 200 | // Keep history manageable 201 | if len(history) > 10 { 202 | history = history[len(history)-10:] 203 | } 204 | } 205 | } 206 | ``` 207 | 208 | ## How It Works 209 | 210 | 1. **Initialization** 211 | - The assistant starts by setting up the LLM, logger, and tools 212 | - Specialized synergies are created for different types of tasks 213 | - The SynergyManager orchestrates all synergies 214 | 215 | 2. **User Interaction** 216 | - The assistant maintains a conversation history 217 | - Each user input is processed with historical context 218 | - Special queries (like capability questions) are handled directly 219 | 220 | 3. **Task Processing** 221 | - The manager routes tasks to appropriate synergies 222 | - Multiple agents collaborate within each synergy 223 | - Results are synthesized into a coherent response 224 | 225 | 4. **Context Management** 226 | - Conversation history provides context for responses 227 | - History is limited to recent interactions 228 | - AI capabilities are included in each prompt 229 | 230 | ## Best Practices 231 | 232 | 1. **Synergy Design** 233 | - Create focused synergies for specific capabilities 234 | - Use appropriate tools for each synergy 235 | - Keep agent responsibilities clear and distinct 236 | 237 | 2. **Error Handling** 238 | - Implement retry logic for LLM calls 239 | - Provide helpful error messages 240 | - Log errors for debugging 241 | 242 | 3. **User Experience** 243 | - Maintain clear conversation flow 244 | - Provide helpful responses 245 | - Keep response times reasonable 246 | 247 | 4. **Resource Management** 248 | - Limit conversation history size 249 | - Configure appropriate token limits 250 | - Monitor API usage 251 | 252 | ## Running the Example 253 | 254 | 1. Set your OpenAI API key: 255 | ```bash 256 | export OPENAI_API_KEY='your-key-here' 257 | ``` 258 | 259 | 2. Run the example: 260 | ```bash 261 | go run examples/7_manager_mode_example.go 262 | ``` 263 | 264 | 3. Interact with the assistant: 265 | ``` 266 | Welcome to the Self-Aware Interactive AI Assistant! 267 | You can ask questions, request tasks, or inquire about my capabilities. 268 | 269 | You: What can you do? 270 | AI Assistant: [Explains capabilities] 271 | 272 | You: Can you research recent AI developments? 273 | AI Assistant: [Provides researched information] 274 | ``` 275 | 276 | ## Next Steps 277 | 278 | 1. Add more specialized synergies for new capabilities 279 | 2. Implement additional tools for specific tasks 280 | 3. Enhance the conversation history management 281 | 4. Add user authentication and session management 282 | 5. Implement persistent storage for conversation history 283 | 284 | For the complete example code, see [manager_mode_example.go](../../examples/7_manager_mode_example.go). 285 | -------------------------------------------------------------------------------- /examples/7_manager_mode_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/teilomillet/enzu" 13 | "github.com/teilomillet/enzu/tools" 14 | "github.com/teilomillet/gollm" 15 | ) 16 | 17 | // Define the AI's capabilities 18 | const aiCapabilities = ` 19 | I am an AI assistant with the following capabilities: 20 | 21 | 1. Research and Fact-Checking: 22 | - Conduct web searches on various topics 23 | - Verify and validate information from multiple sources 24 | - Provide summaries of researched topics 25 | 26 | 2. Data Analysis and Insights: 27 | - Analyze provided data sets or information 28 | - Identify patterns and trends in data 29 | - Generate insights and conclusions based on analysis 30 | - Provide data visualization suggestions 31 | 32 | 3. Creative Content Generation: 33 | - Generate original content based on given prompts or themes 34 | - Edit and refine existing content 35 | - Assist with brainstorming ideas for various creative projects 36 | 37 | 4. General Knowledge and Q&A: 38 | - Answer questions on a wide range of topics 39 | - Explain complex concepts in simple terms 40 | - Provide definitions and explanations 41 | 42 | 5. Task Planning and Problem-Solving: 43 | - Break down complex tasks into manageable steps 44 | - Suggest approaches to solve various problems 45 | - Help with decision-making by providing pros and cons 46 | 47 | To make the best use of my capabilities, please provide clear and specific prompts or questions. If you need clarification on any of my functions, feel free to ask! 48 | ` 49 | 50 | func main() { 51 | // Load API key from environment variable 52 | openAIKey := os.Getenv("OPENAI_API_KEY") 53 | if openAIKey == "" { 54 | log.Fatalf("OPENAI_API_KEY environment variable is not set") 55 | } 56 | 57 | // Create a new LLM instance 58 | llm, err := gollm.NewLLM( 59 | gollm.SetProvider("openai"), 60 | gollm.SetModel("gpt-4o-mini"), 61 | gollm.SetAPIKey(openAIKey), 62 | gollm.SetMaxTokens(300), 63 | gollm.SetMaxRetries(3), 64 | gollm.SetRetryDelay(time.Second*2), 65 | gollm.SetLogLevel(gollm.LogLevelInfo), 66 | ) 67 | if err != nil { 68 | log.Fatalf("Failed to initialize LLM: %v", err) 69 | } 70 | 71 | // Create a logger for Enzu 72 | logger := enzu.NewLogger(enzu.LogLevelInfo) 73 | 74 | // Register the ExaSearch tool 75 | exaSearchOptions := tools.ExaSearchOptions{ 76 | NumResults: 3, 77 | Type: "neural", 78 | Contents: tools.Contents{ 79 | Text: true, 80 | }, 81 | UseAutoprompt: true, 82 | StartPublishedDate: "2023-01-01T00:00:00.000Z", 83 | } 84 | tools.ExaSearch("", exaSearchOptions, "ResearchTool") 85 | 86 | // Create SynergyManager 87 | manager := enzu.NewSynergyManager("Self-Aware Interactive AI Assistant", llm, logger) 88 | 89 | // Create Synergies 90 | researchSynergy := createResearchSynergy(llm, logger) 91 | analysisSynergy := createAnalysisSynergy(llm, logger) 92 | creativeSynergy := createCreativeSynergy(llm, logger) 93 | 94 | manager.AddSynergy(researchSynergy) 95 | manager.AddSynergy(analysisSynergy) 96 | manager.AddSynergy(creativeSynergy) 97 | 98 | // Start interactive chat 99 | fmt.Println("Welcome to the Self-Aware Interactive AI Assistant!") 100 | fmt.Println("You can ask questions, request tasks, or inquire about my capabilities. Type 'exit' to quit.") 101 | 102 | reader := bufio.NewReader(os.Stdin) 103 | conversationHistory := []string{} 104 | 105 | for { 106 | fmt.Print("\nYou: ") 107 | input, _ := reader.ReadString('\n') 108 | input = strings.TrimSpace(input) 109 | 110 | if input == "exit" { 111 | fmt.Println("Goodbye!") 112 | break 113 | } 114 | 115 | response := handleUserInput(manager, input, conversationHistory) 116 | fmt.Printf("\nAI Assistant: %s\n", response) 117 | 118 | // Update conversation history 119 | conversationHistory = append(conversationHistory, fmt.Sprintf("User: %s", input)) 120 | conversationHistory = append(conversationHistory, fmt.Sprintf("Assistant: %s", response)) 121 | 122 | // Limit history to last 5 interactions (10 messages) 123 | if len(conversationHistory) > 10 { 124 | conversationHistory = conversationHistory[len(conversationHistory)-10:] 125 | } 126 | } 127 | } 128 | 129 | func handleUserInput(manager *enzu.SynergyManager, input string, history []string) string { 130 | // Check if the user is asking about capabilities 131 | if isAskingAboutCapabilities(input) { 132 | return explainCapabilities() 133 | } 134 | 135 | ctx := context.Background() 136 | 137 | // Create a prompt that includes conversation history and AI capabilities 138 | historyContext := strings.Join(history, "\n") 139 | prompt := fmt.Sprintf(` 140 | AI Capabilities: 141 | %s 142 | 143 | Conversation history: 144 | %s 145 | 146 | Current user input: %s 147 | 148 | Please provide a response based on your capabilities, the conversation history, and the current input. 149 | `, aiCapabilities, historyContext, input) 150 | 151 | results, err := manager.ExecuteSynergies(ctx, prompt) 152 | if err != nil { 153 | return fmt.Sprintf("I apologize, but I encountered an error while processing your request: %v", err) 154 | } 155 | 156 | return results["synthesis"].(string) 157 | } 158 | 159 | func isAskingAboutCapabilities(input string) bool { 160 | lowercaseInput := strings.ToLower(input) 161 | capabilityKeywords := []string{"what can you do", "your capabilities", "what are you capable of", "your functions", "your abilities"} 162 | 163 | for _, keyword := range capabilityKeywords { 164 | if strings.Contains(lowercaseInput, keyword) { 165 | return true 166 | } 167 | } 168 | return false 169 | } 170 | 171 | func explainCapabilities() string { 172 | return `Certainly! I'd be happy to explain my capabilities. As an AI assistant, I'm designed to help with a variety of tasks: 173 | 174 | 1. Research and Fact-Checking: 175 | I can conduct web searches on various topics, verify information from multiple sources, and provide summaries of researched topics. 176 | 177 | 2. Data Analysis and Insights: 178 | I can analyze provided data sets, identify patterns and trends, generate insights and conclusions, and even suggest data visualizations. 179 | 180 | 3. Creative Content Generation: 181 | I can generate original content based on prompts or themes, edit and refine existing content, and assist with brainstorming ideas for creative projects. 182 | 183 | 4. General Knowledge and Q&A: 184 | I can answer questions on a wide range of topics, explain complex concepts in simple terms, and provide definitions and explanations. 185 | 186 | 5. Task Planning and Problem-Solving: 187 | I can help break down complex tasks into manageable steps, suggest approaches to solve various problems, and assist with decision-making by providing pros and cons. 188 | 189 | To make the best use of my capabilities, it's helpful if you provide clear and specific prompts or questions. Feel free to ask for clarification or more details about any of these functions! 190 | 191 | What kind of task can I help you with today?` 192 | } 193 | 194 | func createResearchSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 195 | agents := []*enzu.Agent{ 196 | enzu.NewAgent("Web Researcher", "Perform web searches and summarize information", llm, enzu.WithToolLists("ResearchTool")), 197 | enzu.NewAgent("Fact Checker", "Verify information and cross-reference sources", llm), 198 | } 199 | 200 | tasks := []*enzu.Task{ 201 | enzu.NewTask("Conduct web research on the given topic", agents[0]), 202 | enzu.NewTask("Verify and validate the researched information", agents[1]), 203 | } 204 | 205 | return enzu.NewSynergy( 206 | "Research and Fact-Checking", 207 | llm, 208 | enzu.WithAgents(agents...), 209 | enzu.WithTasks(tasks...), 210 | enzu.WithLogger(logger), 211 | ) 212 | } 213 | 214 | func createAnalysisSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 215 | agents := []*enzu.Agent{ 216 | enzu.NewAgent("Data Analyst", "Analyze data and identify patterns", llm), 217 | enzu.NewAgent("Insight Generator", "Generate insights from analyzed data", llm), 218 | } 219 | 220 | tasks := []*enzu.Task{ 221 | enzu.NewTask("Analyze the provided information or data", agents[0]), 222 | enzu.NewTask("Generate insights and conclusions from the analysis", agents[1]), 223 | } 224 | 225 | return enzu.NewSynergy( 226 | "Data Analysis and Insights", 227 | llm, 228 | enzu.WithAgents(agents...), 229 | enzu.WithTasks(tasks...), 230 | enzu.WithLogger(logger), 231 | ) 232 | } 233 | 234 | func createCreativeSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 235 | agents := []*enzu.Agent{ 236 | enzu.NewAgent("Creative Writer", "Generate creative content and ideas", llm), 237 | enzu.NewAgent("Editor", "Refine and polish creative content", llm), 238 | } 239 | 240 | tasks := []*enzu.Task{ 241 | enzu.NewTask("Generate creative content based on the given prompt", agents[0]), 242 | enzu.NewTask("Edit and refine the generated creative content", agents[1]), 243 | } 244 | 245 | return enzu.NewSynergy( 246 | "Creative Content Generation", 247 | llm, 248 | enzu.WithAgents(agents...), 249 | enzu.WithTasks(tasks...), 250 | enzu.WithLogger(logger), 251 | ) 252 | } 253 | -------------------------------------------------------------------------------- /task.go: -------------------------------------------------------------------------------- 1 | // Package enzu implements task execution and management within the framework's 2 | // agent-based architecture. Tasks represent discrete units of work that agents 3 | // execute as part of a synergy's workflow. The task system provides: 4 | // - Contextual task execution with access to previous results 5 | // - Dynamic tool discovery and execution 6 | // - Structured agent-LLM interaction 7 | // - Flexible execution patterns through the TaskExecutor interface 8 | package enzu 9 | 10 | import ( 11 | "context" 12 | "encoding/json" 13 | "fmt" 14 | "github.com/teilomillet/gollm" 15 | ) 16 | 17 | // Task represents a discrete unit of work within a Synergy's workflow. It encapsulates 18 | // both the work to be done and the context needed to execute it properly. 19 | // 20 | // Tasks are the fundamental execution units in the Enzu framework: 21 | // - Assigned to specific agents for execution 22 | // - Can access results from previous tasks 23 | // - Have access to agent-specific and synergy-wide tools 24 | // - Support both sequential and parallel execution patterns 25 | type Task struct { 26 | // description defines what the task needs to accomplish 27 | description string 28 | 29 | // agent is the AI entity responsible for executing this task 30 | agent *Agent 31 | 32 | // context contains references to previous tasks whose results may be needed 33 | context []*Task 34 | } 35 | 36 | // NewTask creates a new Task instance with the specified parameters. 37 | // It establishes the relationship between the task, its executing agent, 38 | // and any contextual tasks whose results may be needed. 39 | // 40 | // Parameters: 41 | // - description: Clear description of what the task should accomplish 42 | // - agent: The agent responsible for executing this task 43 | // - context: Optional slice of previous tasks providing context 44 | // 45 | // Returns: 46 | // - *Task: A new task instance ready for execution 47 | func NewTask(description string, agent *Agent, context ...*Task) *Task { 48 | return &Task{ 49 | description: description, 50 | agent: agent, 51 | context: context, 52 | } 53 | } 54 | 55 | // Description returns the task's description string. 56 | // This is useful for logging, debugging, and generating prompts. 57 | // 58 | // Returns: 59 | // - string: The task's description 60 | func (t *Task) Description() string { 61 | return t.description 62 | } 63 | 64 | // TaskExecutor defines the interface for executing tasks within the framework. 65 | // This interface allows for different execution strategies while maintaining 66 | // a consistent execution pattern across the system. 67 | // 68 | // Implementations of this interface handle: 69 | // - Task prompt preparation 70 | // - LLM interaction 71 | // - Tool execution 72 | // - Result processing 73 | type TaskExecutor interface { 74 | // ExecuteTask runs a single task with the provided context and returns its result. 75 | // 76 | // Parameters: 77 | // - ctx: Context for cancellation and deadline control 78 | // - task: The task to execute 79 | // - taskContext: Results from previous task executions 80 | // - logger: Logger for tracking execution progress 81 | // 82 | // Returns: 83 | // - string: The task execution result 84 | // - error: Any error encountered during execution 85 | ExecuteTask(ctx context.Context, task *Task, taskContext map[string]string, logger *Logger) (string, error) 86 | } 87 | 88 | // DefaultTaskExecutor provides the standard implementation of TaskExecutor. 89 | // It implements a straightforward execution flow that includes: 90 | // 1. Prompt preparation with task and context 91 | // 2. LLM interaction for decision making 92 | // 3. Tool execution when requested 93 | // 4. Result collection and return 94 | type DefaultTaskExecutor struct{} 95 | 96 | // ExecuteTask implements the TaskExecutor interface with the default execution strategy. 97 | // It manages the complete lifecycle of a task execution, from prompt preparation 98 | // to result collection. 99 | // 100 | // The execution process: 101 | // 1. Prepares the execution context and prompt 102 | // 2. Interacts with the agent's LLM 103 | // 3. Handles any tool execution requests 104 | // 4. Processes and returns the final result 105 | // 106 | // Parameters: 107 | // - ctx: Context for cancellation and deadline control 108 | // - task: The task to execute 109 | // - taskContext: Map of previous task results 110 | // - logger: Logger for execution tracking 111 | // 112 | // Returns: 113 | // - string: The task's execution result 114 | // - error: Any error encountered during execution 115 | func (e *DefaultTaskExecutor) ExecuteTask(ctx context.Context, task *Task, taskContext map[string]string, logger *Logger) (string, error) { 116 | logger.Info("Task", "Executing: %s", task.description) 117 | prompt := preparePrompt(task, taskContext) 118 | logger.Debug("Prompt", "Prepared prompt for task '%s':\n%s", task.description, prompt) 119 | 120 | response, err := executeWithLLM(ctx, task.agent, prompt, logger) 121 | if err != nil { 122 | logger.Error("Task", "Error executing task '%s': %v", task.description, err) 123 | return "", err 124 | } 125 | 126 | logger.Info("Task", "Completed: %s", task.description) 127 | logger.Debug("Result", "Task '%s' result:\n%s", task.description, response) 128 | return response, nil 129 | } 130 | 131 | // preparePrompt constructs the prompt for LLM interaction based on the task's context. 132 | // It builds a comprehensive prompt that includes: 133 | // - Agent identity and role 134 | // - Task description 135 | // - Results from contextual tasks 136 | // - Available tools and their usage instructions 137 | // 138 | // Parameters: 139 | // - task: The task being executed 140 | // - taskContext: Map of previous task results 141 | // 142 | // Returns: 143 | // - string: The formatted prompt ready for LLM interaction 144 | func preparePrompt(task *Task, taskContext map[string]string) string { 145 | prompt := fmt.Sprintf("You are %s. Your role is %s.\n\n", task.agent.name, task.agent.role) 146 | prompt += fmt.Sprintf("Task: %s\n\n", task.description) 147 | 148 | if len(task.context) > 0 { 149 | prompt += "Context from previous tasks:\n" 150 | for _, contextTask := range task.context { 151 | if result, ok := taskContext[contextTask.description]; ok { 152 | prompt += fmt.Sprintf("- %s: %s\n", contextTask.description, result) 153 | } 154 | } 155 | prompt += "\n" 156 | } 157 | 158 | prompt += "Available tools:\n" 159 | toolLists := task.agent.toolLists 160 | if task.agent.inheritSynergyTools { 161 | toolLists = append(toolLists, task.agent.synergy.toolLists...) 162 | } 163 | for _, listName := range toolLists { 164 | if list, exists := task.agent.synergy.toolRegistry.GetToolList(listName); exists { 165 | for _, tool := range list.Tools { 166 | prompt += fmt.Sprintf("- %s: %s\n", tool.Name, tool.Description) 167 | } 168 | } 169 | } 170 | prompt += "\nTo use a tool, respond with a JSON object in this format: {\"tool\": \"ToolName\", \"args\": [arg1, arg2, ...]}\n\n" 171 | 172 | prompt += "Please complete this task based on the information provided. If you need to use a tool, respond with the appropriate JSON object." 173 | return prompt 174 | } 175 | 176 | // executeWithLLM manages the interaction between the agent's LLM and available tools. 177 | // It implements a conversation loop that allows the LLM to: 178 | // 1. Analyze the task and context 179 | // 2. Decide whether to use tools 180 | // 3. Execute tools and process their results 181 | // 4. Generate the final response 182 | // 183 | // Parameters: 184 | // - ctx: Context for cancellation and deadline control 185 | // - agent: The agent executing the task 186 | // - prompt: The prepared prompt for LLM interaction 187 | // - logger: Logger for tracking the execution 188 | // 189 | // Returns: 190 | // - string: The final execution result 191 | // - error: Any error encountered during execution 192 | func executeWithLLM(ctx context.Context, agent *Agent, prompt string, logger *Logger) (string, error) { 193 | for { 194 | response, err := agent.llm.Generate(ctx, gollm.NewPrompt(prompt)) 195 | if err != nil { 196 | return "", fmt.Errorf("LLM generation error: %w", err) 197 | } 198 | 199 | // Check if the response is a tool execution request 200 | var toolRequest struct { 201 | Tool string `json:"tool"` 202 | Args []interface{} `json:"args"` 203 | } 204 | 205 | if err := json.Unmarshal([]byte(response), &toolRequest); err == nil && toolRequest.Tool != "" { 206 | // Execute the tool 207 | tool, exists := agent.synergy.toolRegistry.GetTool(toolRequest.Tool) 208 | if !exists { 209 | logger.Warn("Tool", "Unknown tool requested: %s", toolRequest.Tool) 210 | prompt += fmt.Sprintf("\nThe tool '%s' is not available. Please try again with an available tool or complete the task without a tool.", toolRequest.Tool) 211 | continue 212 | } 213 | 214 | result, err := tool.Execute(toolRequest.Args...) 215 | if err != nil { 216 | logger.Error("Tool", "Error executing tool '%s': %v", toolRequest.Tool, err) 217 | prompt += fmt.Sprintf("\nThere was an error executing the tool '%s': %v. Please try again or complete the task without this tool.", toolRequest.Tool, err) 218 | continue 219 | } 220 | 221 | // Add tool result to the prompt and continue the conversation 222 | prompt += fmt.Sprintf("\nTool '%s' executed successfully. Result: %v\nPlease complete the task based on this result.", toolRequest.Tool, result) 223 | } else { 224 | // If it's not a tool request, return the response 225 | return response, nil 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /synergy.go: -------------------------------------------------------------------------------- 1 | // Package enzu implements a multi-agent AI orchestration system where Synergy acts as 2 | // the middle layer, coordinating multiple AI agents working towards a common objective. 3 | // Synergy sits between the high-level SynergyManager and individual Agents, providing: 4 | // - Task orchestration and execution management 5 | // - Agent collaboration and coordination 6 | // - Tool and resource sharing 7 | // - Contextual state management across tasks 8 | package enzu 9 | 10 | import ( 11 | "context" 12 | "fmt" 13 | 14 | "github.com/teilomillet/gollm" 15 | ) 16 | 17 | // Synergy represents a collaborative AI workflow where multiple agents work together 18 | // towards a common objective. It serves as the primary orchestration unit in the 19 | // Enzu framework, managing task execution, agent coordination, and resource sharing. 20 | // 21 | // In the framework's hierarchy: 22 | // - Above: SynergyManager coordinates multiple Synergies 23 | // - Current: Synergy orchestrates multiple Agents 24 | // - Below: Agents execute individual tasks 25 | // 26 | // Key responsibilities: 27 | // 1. Task Management: Coordinates task execution across agents 28 | // 2. Resource Sharing: Manages shared tools and context 29 | // 3. Execution Control: Handles sequential/parallel execution 30 | // 4. State Management: Maintains task context and results 31 | type Synergy struct { 32 | // objective defines the common goal for this collaborative workflow 33 | objective string 34 | 35 | // llm is the language model used for task coordination 36 | llm gollm.LLM 37 | 38 | // agents are the AI entities working together in this synergy 39 | agents []*Agent 40 | 41 | // tasks is the sequence of operations to be executed 42 | tasks []*Task 43 | 44 | // executor handles the actual execution of tasks 45 | executor TaskExecutor 46 | 47 | // logger provides logging capabilities for this synergy 48 | logger *Logger 49 | 50 | // toolRegistry manages available tools for all agents 51 | toolRegistry *ToolRegistry 52 | 53 | // toolLists specifies which tool collections are available 54 | toolLists []string 55 | 56 | // parallel indicates whether tasks can be executed concurrently 57 | parallel bool 58 | } 59 | 60 | // SynergyOption is a function type for configuring a Synergy using the functional 61 | // options pattern. This allows for flexible and extensible configuration without 62 | // breaking existing code when new features are added. 63 | type SynergyOption func(*Synergy) 64 | 65 | // NewSynergy creates a new Synergy instance that will coordinate multiple agents 66 | // working towards a common objective. It initializes the synergy with default 67 | // settings that can be customized through options. 68 | // 69 | // Parameters: 70 | // - objective: The common goal this synergy works towards 71 | // - llm: Language model for task coordination 72 | // - opts: Configuration options for customizing behavior 73 | // 74 | // Returns: 75 | // - *Synergy: A new synergy instance ready to coordinate agents 76 | // 77 | // By default, synergies are configured with: 78 | // - Sequential task execution 79 | // - Info-level logging 80 | // - Default tool registry 81 | func NewSynergy(objective string, llm gollm.LLM, opts ...SynergyOption) *Synergy { 82 | s := &Synergy{ 83 | objective: objective, 84 | llm: llm, 85 | executor: &DefaultTaskExecutor{}, 86 | logger: NewLogger(LogLevelInfo), 87 | toolRegistry: defaultRegistry, 88 | parallel: false, // Default to sequential execution 89 | } 90 | for _, opt := range opts { 91 | opt(s) 92 | } 93 | return s 94 | } 95 | 96 | // WithLogger configures the logging system for this synergy. 97 | // The logger tracks execution progress, debug information, and errors 98 | // across all tasks and agents within this synergy. 99 | // 100 | // Parameters: 101 | // - logger: The logger instance to use 102 | func WithLogger(logger *Logger) SynergyOption { 103 | return func(s *Synergy) { 104 | s.logger = logger 105 | } 106 | } 107 | 108 | // WithToolRegistry configures a custom tool registry for this synergy. 109 | // The tool registry manages which tools are available to agents and 110 | // how they can be accessed during task execution. 111 | // 112 | // Parameters: 113 | // - registry: Custom tool registry to use 114 | func WithToolRegistry(registry *ToolRegistry) SynergyOption { 115 | return func(s *Synergy) { 116 | s.toolRegistry = registry 117 | } 118 | } 119 | 120 | // WithTools specifies which tool collections are available to this synergy. 121 | // These tools will be available to all agents unless specifically restricted. 122 | // 123 | // Parameters: 124 | // - lists: Names of tool collections to make available 125 | func WithTools(lists ...string) SynergyOption { 126 | return func(s *Synergy) { 127 | s.toolLists = lists 128 | } 129 | } 130 | 131 | // WithAgents adds agents to this synergy and establishes the bidirectional 132 | // relationship between agents and their synergy. Each agent becomes part 133 | // of the collaborative workflow and gains access to shared resources. 134 | // 135 | // Parameters: 136 | // - agents: The agents to add to this synergy 137 | func WithAgents(agents ...*Agent) SynergyOption { 138 | return func(s *Synergy) { 139 | s.agents = append(s.agents, agents...) 140 | for _, agent := range agents { 141 | agent.synergy = s 142 | } 143 | } 144 | } 145 | 146 | // WithTasks configures the sequence of tasks this synergy will execute. 147 | // Tasks represent the concrete steps needed to achieve the synergy's objective. 148 | // 149 | // Parameters: 150 | // - tasks: The tasks to be executed 151 | func WithTasks(tasks ...*Task) SynergyOption { 152 | return func(s *Synergy) { 153 | s.tasks = append(s.tasks, tasks...) 154 | } 155 | } 156 | 157 | // GetAgents returns the list of agents participating in this synergy. 158 | // This is useful for inspecting the current state of agent collaboration. 159 | // 160 | // Returns: 161 | // - []*Agent: Slice of all agents in this synergy 162 | func (s *Synergy) GetAgents() []*Agent { 163 | return s.agents 164 | } 165 | 166 | // GetTasks returns the current sequence of tasks in this synergy. 167 | // This allows inspection of the workflow's structure and progress. 168 | // 169 | // Returns: 170 | // - []*Task: Slice of all tasks in this synergy 171 | func (s *Synergy) GetTasks() []*Task { 172 | return s.tasks 173 | } 174 | 175 | // SetTasks updates the entire task sequence for this synergy. 176 | // This is useful when dynamically generating or modifying workflows. 177 | // 178 | // Parameters: 179 | // - tasks: The new set of tasks to execute 180 | func (s *Synergy) SetTasks(tasks []*Task) { 181 | s.tasks = tasks 182 | } 183 | 184 | // AddTask appends a new task to this synergy's workflow. 185 | // This allows for dynamic expansion of the workflow during execution. 186 | // 187 | // Parameters: 188 | // - task: The new task to add 189 | func (s *Synergy) AddTask(task *Task) { 190 | s.tasks = append(s.tasks, task) 191 | } 192 | 193 | // Execute runs the entire synergy workflow, coordinating all tasks across agents. 194 | // It maintains a shared context between tasks and supports both sequential and 195 | // parallel execution modes. 196 | // 197 | // The execution process: 198 | // 1. Initializes execution context and result storage 199 | // 2. Executes each task in sequence or parallel based on configuration 200 | // 3. Maintains task context for inter-task communication 201 | // 4. Collects and aggregates results from all tasks 202 | // 203 | // Parameters: 204 | // - ctx: Context for cancellation and deadline control 205 | // 206 | // Returns: 207 | // - map[string]interface{}: Results from all executed tasks 208 | // - error: Any error encountered during execution 209 | func (s *Synergy) Execute(ctx context.Context) (map[string]interface{}, error) { 210 | s.logger.Info("Synergy", "Starting execution for objective: %s", s.objective) 211 | s.logger.Debug("Synergy", "Number of agents: %d, Number of tasks: %d", len(s.agents), len(s.tasks)) 212 | 213 | results := make(map[string]interface{}) 214 | taskContext := make(map[string]string) 215 | 216 | for i, task := range s.tasks { 217 | s.logger.Info("Synergy", "Starting task %d/%d: %s", i+1, len(s.tasks), task.description) 218 | s.logger.Debug("Synergy", "Executing task with agent: %s", task.agent.name) 219 | 220 | var response string 221 | var err error 222 | 223 | if task.agent.parallel { 224 | // Execute task in parallel 225 | response, err = s.executeTaskInParallel(ctx, task, taskContext) 226 | if err != nil { 227 | s.logger.Error("synergy", "Error executing task '%s': %v", task.description, err) 228 | return nil, fmt.Errorf("error executing task '%s': %w", task.description, err) 229 | } 230 | } else { 231 | // Execute task sequentially 232 | response, err = s.executor.ExecuteTask(ctx, task, taskContext, s.logger) 233 | if err != nil { 234 | s.logger.Error("synergy", "Error executing task '%s': %v", task.description, err) 235 | return nil, fmt.Errorf("error executing task '%s': %w", task.description, err) 236 | } 237 | } 238 | 239 | results[task.description] = response 240 | taskContext[task.description] = response 241 | 242 | s.logger.Info("synergy", "Completed task %d/%d: %s", i+1, len(s.tasks), task.description) 243 | s.logger.Debug("synergy", "Task result: %s", response) 244 | } 245 | 246 | s.logger.Info("synergy", "Execution completed successfully") 247 | s.logger.Debug("synergy", "Final results: %v", results) 248 | return results, nil 249 | } 250 | 251 | // executeTaskInParallel executes a task in parallel using goroutines. 252 | // It manages the complexity of parallel execution while ensuring proper 253 | // error handling and result collection. 254 | // 255 | // Parameters: 256 | // - ctx: Context for cancellation and deadline control 257 | // - task: The task to execute in parallel 258 | // - taskContext: Shared context from previous task executions 259 | // 260 | // Returns: 261 | // - string: The task execution result 262 | // - error: Any error encountered during execution 263 | func (s *Synergy) executeTaskInParallel(ctx context.Context, task *Task, taskContext map[string]string) (string, error) { 264 | taskCh := make(chan *Task, 1) 265 | resultCh := make(chan *taskResult, 1) 266 | errorCh := make(chan error, 1) 267 | 268 | // Start worker goroutine 269 | go func() { 270 | for t := range taskCh { 271 | response, err := s.executor.ExecuteTask(ctx, t, taskContext, s.logger) 272 | if err != nil { 273 | errorCh <- err 274 | } else { 275 | resultCh <- &taskResult{task: t, result: response} 276 | } 277 | } 278 | }() 279 | 280 | // Send task to worker 281 | taskCh <- task 282 | close(taskCh) 283 | 284 | // Collect result or error 285 | select { 286 | case err := <-errorCh: 287 | return "", err 288 | case result := <-resultCh: 289 | return result.result, nil 290 | } 291 | } 292 | 293 | // taskResult is an internal type used to collect results from parallel task execution. 294 | // It associates task results with their corresponding tasks for proper tracking. 295 | type taskResult struct { 296 | // task is the executed task 297 | task *Task 298 | 299 | // result contains the task's output 300 | result string 301 | } 302 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/core-concepts.md: -------------------------------------------------------------------------------- 1 | # Core Concepts 2 | 3 | This guide explains the core concepts of Enzu in detail, with examples for each concept. 4 | 5 | ## Tools 6 | 7 | Tools are functions that agents can use to take actions. They are the building blocks of agent capabilities. 8 | 9 | ### Anatomy of a Tool 10 | 11 | ```go 12 | // Basic tool with single tool list 13 | enzu.NewTool( 14 | "ReadFile", // Name 15 | "Reads content from a file", // Description 16 | func(args ...interface{}) (interface{}, error) { 17 | if len(args) != 1 { 18 | return nil, fmt.Errorf("ReadFile requires 1 argument (filepath)") 19 | } 20 | filepath, ok := args[0].(string) 21 | if !ok { 22 | return nil, fmt.Errorf("Argument must be a string") 23 | } 24 | content, err := ioutil.ReadFile(filepath) 25 | return string(content), err 26 | }, 27 | "FileTools", // Tool list name 28 | ) 29 | 30 | // Tool belonging to multiple tool lists 31 | enzu.NewTool( 32 | "CurrentTime", 33 | "Returns the current time", 34 | func(args ...interface{}) (interface{}, error) { 35 | return time.Now().Format(time.RFC3339), nil 36 | }, 37 | "TimeTool", "BasicTools", // Multiple tool lists 38 | ) 39 | ``` 40 | 41 | ### Tool Organization 42 | 43 | Tools can be organized into ToolLists for better management and specialization: 44 | 45 | ```go 46 | // Create specialized tools 47 | enzu.NewTool("Add", "Adds numbers", addFn, "MathTool") 48 | enzu.NewTool("Subtract", "Subtracts numbers", subtractFn, "MathTool") 49 | enzu.NewTool("CurrentTime", "Gets current time", timeFn, "TimeTool") 50 | enzu.NewTool("ReadFile", "Reads files", readFn, "FileTools") 51 | 52 | // Create specialized agents 53 | mathAgent := enzu.NewAgent( 54 | "Math Agent", 55 | "Handles mathematical operations", 56 | llm, 57 | enzu.WithToolLists("MathTool"), 58 | ) 59 | 60 | timeAgent := enzu.NewAgent( 61 | "Time Agent", 62 | "Handles time-related operations", 63 | llm, 64 | enzu.WithToolLists("TimeTool"), 65 | ) 66 | 67 | // Create general-purpose agent with all tools 68 | generalAgent := enzu.NewAgent( 69 | "General Agent", 70 | "Has access to all tools", 71 | llm, 72 | enzu.WithToolLists("MathTool", "TimeTool", "FileTools"), 73 | ) 74 | ``` 75 | 76 | ### Tool Inheritance 77 | 78 | Agents can inherit tools from their synergy: 79 | 80 | ```go 81 | // Agent that inherits synergy tools (default behavior) 82 | agent1 := enzu.NewAgent( 83 | "Inheriting Agent", 84 | "Can use synergy tools", 85 | llm, 86 | enzu.WithToolLists("MathTool"), 87 | enzu.WithInheritSynergyTools(true), 88 | ) 89 | 90 | // Agent that doesn't inherit synergy tools 91 | agent2 := enzu.NewAgent( 92 | "Restricted Agent", 93 | "Only uses own tools", 94 | llm, 95 | enzu.WithToolLists("MathTool"), 96 | enzu.WithInheritSynergyTools(false), 97 | ) 98 | 99 | // Create synergy with shared tools 100 | synergy := enzu.NewSynergy( 101 | "Tool Inheritance Demo", 102 | llm, 103 | enzu.WithAgents(agent1, agent2), 104 | enzu.WithTools("TimeTool"), // Only agent1 can use these 105 | ) 106 | ``` 107 | 108 | ## Agents 109 | 110 | Agents are AI entities that can perform specific tasks using tools, capabilities, and enhancements. Think of them as specialized workers in your system. 111 | 112 | ### Creating an Agent 113 | 114 | ```go 115 | // Basic agent with tools 116 | agent := enzu.NewAgent( 117 | "DataAnalyst", // Name 118 | "Analyzes data and generates reports", // Description 119 | llm, // LLM instance 120 | enzu.WithToolLists("AnalysisTools"), // Tools the agent can use 121 | ) 122 | 123 | // Agent with capabilities 124 | agent := enzu.NewAgent( 125 | "WebResearcher", 126 | "Performs web research", 127 | llm, 128 | enzu.WithCapabilities(&WebSearchCapability{}), 129 | ) 130 | 131 | // Agent with enhancements 132 | agent := enzu.NewAgent( 133 | "Strategist", 134 | "Develops strategies", 135 | llm, 136 | enzu.WithEnhancement(&enzu.EnhancementConfig{ 137 | Instructions: "Focus on innovative AI-driven strategies", 138 | }), 139 | ) 140 | ``` 141 | 142 | ### Agent Properties 143 | 144 | - **Name**: Identifies the agent 145 | - **Description**: Explains the agent's purpose and capabilities 146 | - **Tools**: Set of tools the agent can use 147 | - **Capabilities**: Custom behaviors that extend agent functionality 148 | - **Enhancements**: Configuration to modify agent behavior 149 | 150 | ## Tasks 151 | 152 | Tasks are units of work assigned to agents. They can be executed independently or in sequence. 153 | 154 | ### Creating Tasks 155 | 156 | ```go 157 | // Simple task 158 | task := enzu.NewTask( 159 | "Read config.json and post its contents to API", 160 | fileAgent, 161 | ) 162 | 163 | // Sequential tasks 164 | researchTask := enzu.NewTask( 165 | "Analyze market trends", 166 | marketAnalyst, 167 | ) 168 | 169 | strategyTask := enzu.NewTask( 170 | "Develop marketing strategy", 171 | marketingStrategist, 172 | researchTask, // This task depends on researchTask 173 | ) 174 | ``` 175 | 176 | ### Task Properties 177 | 178 | - **Description**: What needs to be done 179 | - **Agent**: The agent responsible for the task 180 | - **Dependencies**: Other tasks that must complete first 181 | 182 | ## Synergies 183 | 184 | Synergies orchestrate multiple agents and tasks to achieve complex goals. They can be managed individually or through a SynergyManager. 185 | 186 | ### Basic Synergy 187 | 188 | ```go 189 | synergy := enzu.NewSynergy( 190 | "Marketing Campaign", 191 | llm, 192 | enzu.WithAgents(marketAnalyst, strategist), 193 | enzu.WithTasks(researchTask, strategyTask), 194 | enzu.WithLogger(logger), 195 | ) 196 | ``` 197 | 198 | ### SynergyManager 199 | 200 | For complex projects with multiple synergies: 201 | 202 | ```go 203 | // Create SynergyManager 204 | manager := enzu.NewSynergyManager("MainManager", llm, logger) 205 | 206 | // Create and add marketing team synergy 207 | marketingAgents := []*enzu.Agent{ 208 | enzu.NewAgent("Market Analyst", "Analyze trends", llm), 209 | enzu.NewAgent("Strategist", "Create strategy", llm), 210 | } 211 | marketingSynergy := enzu.NewSynergy( 212 | "Marketing Strategy", 213 | llm, 214 | enzu.WithAgents(marketingAgents...), 215 | enzu.WithTasks( 216 | enzu.NewTask("Analyze trends", marketingAgents[0]), 217 | enzu.NewTask("Create strategy", marketingAgents[1]), 218 | ), 219 | ) 220 | manager.AddSynergy(marketingSynergy) 221 | 222 | // Create and add product team synergy 223 | productAgents := []*enzu.Agent{ 224 | enzu.NewAgent("Product Manager", "Define features", llm), 225 | enzu.NewAgent("Tech Lead", "Create roadmap", llm), 226 | } 227 | productSynergy := enzu.NewSynergy( 228 | "Product Development", 229 | llm, 230 | enzu.WithAgents(productAgents...), 231 | enzu.WithTasks( 232 | enzu.NewTask("Define features", productAgents[0]), 233 | enzu.NewTask("Create roadmap", productAgents[1]), 234 | ), 235 | ) 236 | manager.AddSynergy(productSynergy) 237 | 238 | // Execute all synergies with a single prompt 239 | results, err := manager.ExecuteSynergies(ctx, 240 | "Plan launch of new AI product") 241 | ``` 242 | 243 | ## Parallel Execution 244 | 245 | Agents can execute tasks in parallel for improved performance: 246 | 247 | ```go 248 | // Create agent with parallel execution enabled 249 | agent := enzu.NewAgent( 250 | "ParallelAgent", 251 | "Executes tasks in parallel", 252 | llm, 253 | enzu.WithToolLists("BasicTools"), 254 | enzu.WithParallelExecution(true), // Enable parallel execution 255 | ) 256 | 257 | // Create multiple tasks 258 | task1 := enzu.NewTask("Task 1", agent) 259 | task2 := enzu.NewTask("Task 2", agent) 260 | 261 | // Tasks will execute in parallel 262 | synergy := enzu.NewSynergy( 263 | "Parallel Processing", 264 | llm, 265 | enzu.WithAgents(agent), 266 | enzu.WithTasks(task1, task2), 267 | ) 268 | ``` 269 | 270 | ## Built-in Tools 271 | 272 | Enzu provides built-in tools for common operations: 273 | 274 | ### ExaSearch Tool 275 | 276 | ```go 277 | // Configure ExaSearch options 278 | options := tools.ExaSearchOptions{ 279 | NumResults: 5, 280 | Type: "neural", 281 | Contents: tools.Contents{ 282 | Text: true, 283 | }, 284 | UseAutoprompt: true, 285 | StartPublishedDate: "2023-01-01T00:00:00.000Z", 286 | } 287 | 288 | // Register ExaSearch tool 289 | tools.ExaSearch("", options, "ResearchTool") 290 | 291 | // Create research agent 292 | researchAgent := enzu.NewAgent( 293 | "Researcher", 294 | "Conducts web research", 295 | llm, 296 | enzu.WithToolLists("ResearchTool"), 297 | ) 298 | ``` 299 | 300 | ## Interactive Mode 301 | 302 | Enzu supports interactive mode for building conversational AI assistants: 303 | 304 | ```go 305 | // Create SynergyManager for interactive mode 306 | manager := enzu.NewSynergyManager( 307 | "Interactive Assistant", 308 | llm, 309 | logger, 310 | ) 311 | 312 | // Add specialized synergies 313 | manager.AddSynergy(createResearchSynergy(llm, logger)) 314 | manager.AddSynergy(createAnalysisSynergy(llm, logger)) 315 | manager.AddSynergy(createCreativeSynergy(llm, logger)) 316 | 317 | // Handle user input 318 | func handleUserInput(manager *enzu.SynergyManager, input string) { 319 | ctx := context.Background() 320 | results, err := manager.ExecuteSynergies(ctx, input) 321 | if err != nil { 322 | log.Printf("Error: %v\n", err) 323 | return 324 | } 325 | fmt.Printf("Response: %v\n", results["synthesis"]) 326 | } 327 | ``` 328 | 329 | ### Creating Specialized Synergies 330 | 331 | ```go 332 | func createResearchSynergy(llm gollm.LLM, logger *enzu.Logger) *enzu.Synergy { 333 | agent := enzu.NewAgent( 334 | "Research Agent", 335 | "Conducts research", 336 | llm, 337 | enzu.WithToolLists("ResearchTool"), 338 | ) 339 | 340 | return enzu.NewSynergy( 341 | "Research Operations", 342 | llm, 343 | enzu.WithAgents(agent), 344 | enzu.WithLogger(logger), 345 | ) 346 | } 347 | ``` 348 | 349 | ## Best Practices 350 | 351 | 1. **Tool Organization** 352 | - Group related tools into focused tool lists 353 | - Consider tool inheritance when designing agent access 354 | - Use clear error messages in tool implementations 355 | 356 | 2. **Agent Design** 357 | - Create specialized agents for specific domains 358 | - Use tool inheritance thoughtfully 359 | - Combine capabilities and enhancements as needed 360 | 361 | 3. **Synergy Management** 362 | - Use SynergyManager for complex, multi-team projects 363 | - Keep synergies focused on specific objectives 364 | - Consider tool sharing between agents carefully 365 | 366 | 4. **Error Handling** 367 | - Implement comprehensive error checking in tools 368 | - Use logging to track execution 369 | - Handle tool failures gracefully 370 | 371 | 5. **Parallel Processing** 372 | - Use parallel execution for independent tasks 373 | - Consider resource constraints 374 | - Handle concurrent tool access properly 375 | 376 | 6. **Interactive Mode** 377 | - Define clear capabilities 378 | - Create specialized synergies for different functions 379 | - Maintain conversation context 380 | - Handle errors gracefully 381 | 382 | ## Complete Example: Data Processing Pipeline 383 | 384 | Here's a complete example that combines all concepts: 385 | 386 | ```go 387 | package main 388 | 389 | import ( 390 | "context" 391 | "fmt" 392 | "log" 393 | "os" 394 | 395 | "github.com/teilomillet/enzu" 396 | "github.com/teilomillet/gollm" 397 | ) 398 | 399 | func main() { 400 | // Initialize LLM 401 | llm := initLLM() 402 | 403 | // Create tools 404 | createFileTools() 405 | createProcessingTools() 406 | createOutputTools() 407 | 408 | // Create agents 409 | reader := enzu.NewAgent("Reader", "Reads input files", llm, 410 | enzu.WithToolLists("FileTools")) 411 | 412 | processor := enzu.NewAgent("Processor", "Processes data", llm, 413 | enzu.WithToolLists("ProcessingTools")) 414 | 415 | writer := enzu.NewAgent("Writer", "Writes results", llm, 416 | enzu.WithToolLists("OutputTools")) 417 | 418 | // Create tasks 419 | readTask := enzu.NewTask("Read input.csv", reader) 420 | processTask := enzu.NewTask("Process the data", processor) 421 | writeTask := enzu.NewTask("Write results to output.json", writer) 422 | 423 | // Create synergy 424 | synergy := enzu.NewSynergy( 425 | "Data Pipeline", 426 | llm, 427 | enzu.WithAgents(reader, processor, writer), 428 | enzu.WithTasks(readTask, processTask, writeTask), 429 | enzu.WithParallel(false), // Sequential execution 430 | ) 431 | 432 | // Execute 433 | results, err := synergy.Execute(context.Background()) 434 | if err != nil { 435 | log.Fatal(err) 436 | } 437 | 438 | fmt.Printf("Pipeline completed: %v\n", results) 439 | } 440 | 441 | func createFileTools() { 442 | enzu.NewTool("ReadCSV", "Reads CSV file", readCSVFn, "FileTools") 443 | // Add more file tools... 444 | } 445 | 446 | func createProcessingTools() { 447 | enzu.NewTool("ProcessData", "Processes data", processDataFn, "ProcessingTools") 448 | // Add more processing tools... 449 | } 450 | 451 | func createOutputTools() { 452 | enzu.NewTool("WriteJSON", "Writes JSON file", writeJSONFn, "OutputTools") 453 | // Add more output tools... 454 | } 455 | ``` 456 | 457 | This example shows how to: 458 | 1. Organize tools into logical groups 459 | 2. Create specialized agents 460 | 3. Define a sequence of tasks 461 | 4. Orchestrate everything with a synergy 462 | 463 | Continue to the [Tutorials](./tutorials/README.md) section for practical examples of these concepts in action. 464 | 465 | For complete examples, see our [examples directory](../examples): 466 | - [Basic Example](../examples/1_basic_example.go) 467 | - [Manager Example](../examples/2_manager_example.go) 468 | - [Tools Example](../examples/3_tools_example.go) 469 | - [Parallel Example](../examples/4_parallel_example.go) 470 | - [Search Example](../examples/5_search_example.go) 471 | - [Manager Mode Example](../examples/7_manager_mode_example.go) 472 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= 4 | github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= 5 | github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= 6 | github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= 7 | github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= 8 | github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= 9 | github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= 10 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 11 | github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= 12 | github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= 13 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 14 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 15 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 16 | github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= 17 | github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= 18 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= 19 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= 20 | github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= 21 | github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= 22 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 23 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= 27 | github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 28 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 29 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 30 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 31 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 32 | github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= 33 | github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= 34 | github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= 35 | github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= 36 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 37 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 38 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 39 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 40 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 41 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 42 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 43 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 44 | github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= 45 | github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 46 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 47 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 48 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 49 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 50 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 51 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 52 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 53 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 54 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 55 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 56 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 57 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 58 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 59 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 60 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 61 | github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= 62 | github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= 63 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 64 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 65 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 66 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 67 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 68 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 69 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 70 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 71 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 72 | github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06 h1:kacRlPN7EN++tVpGUorNGPn/4DnB7/DfTY82AOn6ccU= 73 | github.com/ledongthuc/pdf v0.0.0-20240201131950-da5b75280b06/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= 74 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 75 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 76 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 77 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 78 | github.com/milvus-io/milvus-proto/go-api/v2 v2.4.6 h1:rSkwp5Mg/7KBSUqXcrPBUgTQGZNdvYWEKB+rHo9YJtk= 79 | github.com/milvus-io/milvus-proto/go-api/v2 v2.4.6/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek= 80 | github.com/milvus-io/milvus-sdk-go/v2 v2.4.1 h1:KhqjmaJE4mSxj1a88XtkGaqgH4duGiHs1sjnvSXkwE0= 81 | github.com/milvus-io/milvus-sdk-go/v2 v2.4.1/go.mod h1:7SJxshlnVhNLksS73tLPtHYY9DiX7lyL43Rv41HCPCw= 82 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 83 | github.com/philippgille/chromem-go v0.7.0 h1:4jfvfyKymjKNfGxBUhHUcj1kp7B17NL/I1P+vGh1RvY= 84 | github.com/philippgille/chromem-go v0.7.0/go.mod h1:hTd+wGEm/fFPQl7ilfCwQXkgEUxceYh86iIdoKMolPo= 85 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 86 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 87 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 88 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 89 | github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw= 90 | github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= 91 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 92 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 93 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 94 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 95 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 96 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 97 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 98 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 99 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 100 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 101 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 102 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 103 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 104 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 105 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 106 | github.com/teilomillet/gollm v0.1.1 h1:Uhhnv8xno4WIs0gN276FrY+RShInzFa3bwoJAjVmuws= 107 | github.com/teilomillet/gollm v0.1.1/go.mod h1:JzTjRYmWJeFwC0oohHX7r+/5hgxAF7ZeBz/wH5dv1SU= 108 | github.com/teilomillet/raggo v0.0.2 h1:OFhVe4uk8jqUez5TPNS141J/H89lwtK5FNAuZ4GaVZc= 109 | github.com/teilomillet/raggo v0.0.2/go.mod h1:dVlA2pOnBw/2elOsxa+u/VUF2h9Hm0WwI3KWOMAictM= 110 | github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= 111 | github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 112 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 113 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 114 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 115 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 116 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 117 | github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= 118 | github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= 119 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 120 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 121 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 122 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 123 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 124 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 125 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 126 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 127 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 128 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 129 | golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= 130 | golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= 131 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 132 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 133 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 134 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 135 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 136 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 137 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 138 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 139 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 140 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 141 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 142 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 143 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 144 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 145 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 146 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 147 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 148 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 149 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 150 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 151 | golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= 152 | golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 153 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 154 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 155 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 156 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 157 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 160 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 162 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 163 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 169 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 170 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 171 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 172 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 173 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 174 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 175 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 176 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 177 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 178 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 179 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 180 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 181 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 182 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 183 | golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= 184 | golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= 185 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 186 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 187 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 188 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 189 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 190 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 191 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 192 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 193 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 194 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 195 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 196 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 197 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 198 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 199 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 200 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 201 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 202 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 203 | google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 204 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= 205 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 206 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 207 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 208 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 209 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 210 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 211 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= 212 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 213 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 214 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 215 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 216 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 217 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 218 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 219 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 220 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 221 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 222 | --------------------------------------------------------------------------------