-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Expand file tree
/
Copy pathmain.go
More file actions
203 lines (177 loc) · 6.03 KB
/
Copy pathmain.go
File metadata and controls
203 lines (177 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Agent Plan & Delegate — planning and multi-agent delegation
//
// This example shows the two built-in agent capabilities:
//
// - plan: the conductor records an ordered plan before doing
// multi-step work; the plan is saved to its memory.
// - delegate: the conductor hands the notification step to a
// separate "comms" agent over RPC, rather than doing it
// itself.
//
// Two services (task, notify), two agents (conductor, comms). The
// conductor manages task; comms manages notify. When asked to create
// tasks and notify someone, the conductor plans the work, creates the
// tasks with its own tools, then delegates the notification to comms —
// which is a real registered agent, so the hand-off goes over RPC.
//
// Run (needs an LLM provider key):
//
// MICRO_AI_PROVIDER=anthropic MICRO_AI_API_KEY=sk-ant-... go run main.go
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"sync"
"time"
"go-micro.dev/v6"
)
// ---------------------------------------------------------------------------
// task service
// ---------------------------------------------------------------------------
type Task struct {
ID string `json:"id" description:"Unique task identifier"`
Title string `json:"title" description:"What the task is"`
}
type AddRequest struct {
Title string `json:"title" description:"Title of the task to add (required)"`
}
type AddResponse struct {
Task *Task `json:"task" description:"The created task"`
}
type ListRequest struct{}
type ListResponse struct {
Tasks []*Task `json:"tasks" description:"All tasks"`
}
type TaskService struct {
mu sync.Mutex
tasks []*Task
nextID int
}
// Add creates a new task with the given title.
//
// @example {"title": "Design the launch page"}
func (s *TaskService) Add(ctx context.Context, req *AddRequest, rsp *AddResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
t := &Task{ID: fmt.Sprintf("task-%d", s.nextID), Title: req.Title}
s.tasks = append(s.tasks, t)
rsp.Task = t
return nil
}
// List returns all tasks.
//
// @example {}
func (s *TaskService) List(ctx context.Context, req *ListRequest, rsp *ListResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
rsp.Tasks = append(rsp.Tasks, s.tasks...)
return nil
}
// ---------------------------------------------------------------------------
// notify service
// ---------------------------------------------------------------------------
type SendRequest struct {
To string `json:"to" description:"Recipient address (required)"`
Message string `json:"message" description:"Message body (required)"`
}
type SendResponse struct {
Sent bool `json:"sent" description:"Whether the notification was sent"`
}
type NotifyService struct{}
// Send delivers a notification message to a recipient.
//
// @example {"to": "owner@acme.com", "message": "The launch plan is ready"}
func (s *NotifyService) Send(ctx context.Context, req *SendRequest, rsp *SendResponse) error {
fmt.Printf(" 📨 notify: to=%s message=%q\n", req.To, req.Message)
rsp.Sent = true
return nil
}
func main() {
provider, apiKey := detectProvider()
if apiKey == "" {
fmt.Println("No LLM key found. Set a provider key and run again, e.g.:")
fmt.Println(" export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...")
fmt.Println(" go run main.go")
return
}
fmt.Printf("Using provider %q\n", provider)
// Services.
task := micro.NewService("task")
task.Handle(new(TaskService))
go task.Run()
notify := micro.NewService("notify")
notify.Handle(new(NotifyService))
go notify.Run()
// comms is a real, registered agent that owns the notify service.
// Because it's registered, the conductor's delegate hand-off
// reaches it over RPC.
comms := micro.NewAgent("comms",
micro.AgentServices("notify"),
micro.AgentPrompt("You handle outbound notifications. Use the notify service to send messages."),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
)
go comms.Run()
// The conductor owns task. Its prompt nudges it to plan, and to
// delegate notifications to the comms agent rather than doing them.
conductor := micro.NewAgent("conductor",
micro.AgentServices("task"),
micro.AgentPrompt(
"You coordinate launch work. For multi-step requests, first call the plan tool "+
"to record your steps, then carry them out. You can create tasks yourself. "+
"For anything to do with notifying people, delegate to the \"comms\" agent "+
"using the delegate tool (to: \"comms\") — do not try to notify directly.",
),
micro.AgentProvider(provider),
micro.AgentAPIKey(apiKey),
)
// Give the services and comms agent a moment to register.
time.Sleep(2 * time.Second)
resp, err := conductor.Ask(context.Background(),
"Create three launch tasks: Design, Build, and Ship. "+
"Then make sure owner@acme.com is notified that the launch plan is ready.")
if err != nil {
fmt.Println("error:", err)
os.Exit(1)
}
fmt.Println("\n--- conductor tool calls ---")
for _, tc := range resp.ToolCalls {
args, _ := json.Marshal(tc.Input)
fmt.Printf(" → %s(%s)\n", tc.Name, args)
}
fmt.Println("\n--- conductor reply ---")
fmt.Println(resp.Reply)
}
// detectProvider picks an LLM provider and key from the environment.
// MICRO_AI_PROVIDER / MICRO_AI_API_KEY win if set; otherwise it falls
// back to the first provider-specific key it finds (ANTHROPIC_API_KEY,
// OPENAI_API_KEY, ...), so `export ANTHROPIC_API_KEY=... && go run .`
// just works.
func detectProvider() (provider, apiKey string) {
provider = os.Getenv("MICRO_AI_PROVIDER")
apiKey = os.Getenv("MICRO_AI_API_KEY")
if apiKey != "" {
if provider == "" {
provider = "anthropic"
}
return provider, apiKey
}
// provider name -> its conventional API key env var
for _, p := range []struct{ name, env string }{
{"anthropic", "ANTHROPIC_API_KEY"},
{"openai", "OPENAI_API_KEY"},
{"gemini", "GEMINI_API_KEY"},
{"groq", "GROQ_API_KEY"},
{"mistral", "MISTRAL_API_KEY"},
{"together", "TOGETHER_API_KEY"},
{"atlascloud", "ATLASCLOUD_API_KEY"},
} {
if v := os.Getenv(p.env); v != "" {
return p.name, v
}
}
return "", ""
}