Skip to content

Commit b1cfda8

Browse files
committed
feat: refactor agent creation to use functional options pattern for improved configurability
1 parent 0e04c9e commit b1cfda8

1 file changed

Lines changed: 190 additions & 73 deletions

File tree

README.md

Lines changed: 190 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -65,33 +65,28 @@ func main() {
6565
client := syndicate.NewOpenAIClient("YOUR_API_KEY")
6666

6767
// Create an order processing agent
68-
orderAgent, _ := syndicate.NewAgent().
69-
SetClient(client).
70-
SetName("OrderAgent").
71-
SetConfigPrompt("You process customer orders.").
72-
SetModel(openai.GPT4).
73-
Build()
68+
orderAgent, _ := syndicate.NewAgent(
69+
syndicate.WithClient(client),
70+
syndicate.WithName("OrderAgent"),
71+
syndicate.WithSystemPrompt("You process customer orders."),
72+
syndicate.WithModel(openai.GPT4),
73+
syndicate.WithMemory(syndicate.NewSimpleMemory()),
74+
)
7475

7576
// Create a summary agent
76-
summaryAgent, _ := syndicate.NewAgent().
77-
SetClient(client).
78-
SetName("SummaryAgent").
79-
SetConfigPrompt("You summarize order details.").
80-
SetModel(openai.GPT4).
81-
Build()
82-
83-
// Create a pipeline with both agents
84-
system := syndicate.NewSyndicate().
85-
RecruitAgent(orderAgent).
86-
RecruitAgent(summaryAgent).
87-
DefinePipeline([]string{"OrderAgent", "SummaryAgent"}).
88-
Build()
89-
90-
// Process user input
91-
response, _ := system.ExecutePipeline(
77+
summaryAgent, _ := syndicate.NewAgent(
78+
syndicate.WithClient(client),
79+
syndicate.WithName("SummaryAgent"),
80+
syndicate.WithSystemPrompt("You summarize order details."),
81+
syndicate.WithModel(openai.GPT4),
82+
syndicate.WithMemory(syndicate.NewSimpleMemory()),
83+
)
84+
85+
// Use agents directly for chat
86+
response, _ := orderAgent.Chat(
9287
context.Background(),
93-
"User",
94-
"I'd like to order two pizzas for delivery to 123 Main St."
88+
syndicate.WithUserName("User"),
89+
syndicate.WithInput("I'd like to order two pizzas for delivery to 123 Main St."),
9590
)
9691

9792
fmt.Println(response)
@@ -248,25 +243,27 @@ Here's an example of what a `Handler` for the `SaveOrder` tool might look like:
248243
package main
249244

250245
import (
246+
"context"
251247
"encoding/json"
252248
"fmt"
253249
"log"
254250

255251
syndicate "github.com/Dieg0Code/syndicate-go"
252+
openai "github.com/sashabaranov/go-openai"
256253
)
257254

258255
type MenuItemSchema struct {
259-
ItemName string `json:"item_name" description:"Menu item name" required:"true"`
260-
Quantity int `json:"quantity" description:"Quantity ordered by the user" required:"true"`
261-
Price int `json:"price" description:"Menu item price" required:"true"`
256+
ItemName string `json:"item_name" description:"Menu item name" required:"true"`
257+
Quantity int `json:"quantity" description:"Quantity ordered by the user" required:"true"`
258+
Price int `json:"price" description:"Menu item price" required:"true"`
262259
}
263260

264261
type UserOrderFunctionSchema struct {
265-
MenuItems []MenuItemSchema `json:"menu_items" description:"List of ordered menu items" required:"true"`
266-
DeliveryAddress string `json:"delivery_address" description:"Order delivery address" required:"true"`
267-
UserName string `json:"user_name" description:"User's name placing the order" required:"true"`
268-
PhoneNumber string `json:"phone_number" description:"User's phone number" required:"true"`
269-
PaymentMethod string `json:"payment_method" description:"Payment method (cash or transfer only)" required:"true" enum:"cash,transfer"`
262+
MenuItems []MenuItemSchema `json:"menu_items" description:"List of ordered menu items" required:"true"`
263+
DeliveryAddress string `json:"delivery_address" description:"Order delivery address" required:"true"`
264+
UserName string `json:"user_name" description:"User's name placing the order" required:"true"`
265+
PhoneNumber string `json:"phone_number" description:"User's phone number" required:"true"`
266+
PaymentMethod string `json:"payment_method" description:"Payment method (cash or transfer only)" required:"true" enum:"cash,transfer"`
270267
}
271268

272269
type SaveOrderTool struct {
@@ -287,7 +284,6 @@ func (s *SaveOrderTool) GetDefinition() syndicate.ToolDefinition {
287284
Name: "SaveOrder",
288285
Description: "Retrieves the user's order. The user must provide the requested menu items, delivery address, name, phone number, and payment method. The payment method can only be cash or bank transfer.",
289286
Parameters: schema,
290-
Strict: true,
291287
}
292288
}
293289

@@ -308,26 +304,38 @@ func (s *SaveOrderTool) Execute(args json.RawMessage) (interface{}, error) {
308304
}
309305

310306
func main() {
307+
// Initialize OpenAI client
308+
client := syndicate.NewOpenAIClient("YOUR_API_KEY")
309+
310+
// Create memory for the agent
311+
memory := syndicate.NewSimpleMemory()
312+
311313
// Create a new instance of the tool
312314
saveOrderTool := NewSaveOrderTool()
313315

314-
// Create a new instance of an agent
315-
agent, err := syndicate.NewAgent().
316-
SetClient(client).
317-
SetName("HelloAgent").
318-
SetConfigPrompt("<YOUR_PROMPT>").
319-
SetMemory(memoryAgentOne).
320-
SetModel(openai.GPT4).
321-
EquipTool(saveOrderTool). // Equip the tool to the agent 🧰
322-
Build()
316+
// Create agent using functional options pattern
317+
agent, err := syndicate.NewAgent(
318+
syndicate.WithClient(client),
319+
syndicate.WithName("OrderAgent"),
320+
syndicate.WithSystemPrompt("You are a helpful restaurant assistant that processes customer orders. Always collect all required information before calling the SaveOrder tool."),
321+
syndicate.WithModel(openai.GPT4),
322+
syndicate.WithMemory(memory),
323+
syndicate.WithTools(saveOrderTool), // Equip the tool to the agent 🧰
324+
)
323325
if err != nil {
324326
fmt.Printf("Error creating agent: %v\n", err)
327+
return
325328
}
326329

327-
// Process a sample input with the agent 🧠
328-
response, err := agent.Process(context.Background(), "Jhon Doe", "What is on the menu?")
330+
// ✅ Chat with the agent using functional options
331+
response, err := agent.Chat(
332+
context.Background(),
333+
syndicate.WithUserName("John Doe"),
334+
syndicate.WithInput("I want to order 2 pizzas for delivery to 123 Main St. My phone is 555-1234 and I'll pay with cash."),
335+
)
329336
if err != nil {
330337
fmt.Printf("Error processing input: %v\n", err)
338+
return
331339
}
332340

333341
fmt.Println("\nAgent Response:")
@@ -349,58 +357,167 @@ By simply implementing the `Tool` interface and adding the tool to the agent, yo
349357
<details>
350358
<summary><b>Memory Management</b></summary>
351359

352-
Implement long-term memory for the agent using the `Memory` interface. This allows the agent to retain context across conversations and improve its responses over time.
360+
Agents can remember conversations across multiple interactions using the Memory interface.
361+
362+
### Built-in Simple Memory
363+
364+
For most cases, use the built-in memory:
353365

354366
```go
355-
package main
367+
agent, _ := syndicate.NewAgent(
368+
syndicate.WithClient(client),
369+
syndicate.WithName("ChatAgent"),
370+
syndicate.WithSystemPrompt("You are a helpful assistant."),
371+
syndicate.WithModel(openai.GPT4),
372+
syndicate.WithMemory(syndicate.NewSimpleMemory()), // ✅ Remembers conversations
373+
)
356374

375+
// First conversation
376+
response1, _ := agent.Chat(ctx,
377+
syndicate.WithUserName("Alice"),
378+
syndicate.WithInput("My favorite color is blue."))
357379

358-
/* Import necessary packages */
380+
// Later conversation - agent remembers!
381+
response2, _ := agent.Chat(ctx,
382+
syndicate.WithUserName("Alice"),
383+
syndicate.WithInput("What's my favorite color?"))
384+
```
359385

360-
type ChatMemory struct {
386+
### Custom Memory Implementation
361387

362-
/* These fields are important for the memory system */
388+
Use `NewMemory` with functional options for custom storage:
363389

364-
Name string // Name of the sender
365-
Role string // Role of the sender (user or assistant)
366-
Content string // Content of the message
367-
ToolCallID string // ID of the tool call (if applicable)
368-
ToolCalls datatype.JSON // Tool calls made during the conversation
390+
```go
391+
package main
369392

370-
/* Add any other fields you need for your memory system */
393+
import (
394+
"database/sql"
395+
"encoding/json"
396+
"sync"
371397

372-
}
398+
syndicate "github.com/Dieg0Code/syndicate-go"
399+
_ "github.com/lib/pq"
400+
)
373401

374-
type MyMemory struct {
375-
/* Define your necessary dependencies here */
402+
// Database-backed memory example
403+
func NewDatabaseMemory(db *sql.DB, agentID string) (syndicate.Memory, error) {
404+
var mu sync.RWMutex
405+
406+
return syndicate.NewMemory(
407+
// Handle adding messages to database
408+
syndicate.WithAddHandler(func(message syndicate.Message) {
409+
mu.Lock()
410+
defer mu.Unlock()
411+
412+
messageData, _ := json.Marshal(message)
413+
query := `INSERT INTO agent_messages (agent_id, message_data) VALUES ($1, $2)`
414+
db.Exec(query, agentID, messageData)
415+
}),
416+
417+
// Handle retrieving messages from database
418+
syndicate.WithGetHandler(func() []syndicate.Message {
419+
mu.RLock()
420+
defer mu.RUnlock()
421+
422+
query := `SELECT message_data FROM agent_messages WHERE agent_id = $1 ORDER BY created_at`
423+
rows, err := db.Query(query, agentID)
424+
if err != nil {
425+
return []syndicate.Message{}
426+
}
427+
defer rows.Close()
428+
429+
var messages []syndicate.Message
430+
for rows.Next() {
431+
var messageData []byte
432+
if rows.Scan(&messageData) == nil {
433+
var message syndicate.Message
434+
if json.Unmarshal(messageData, &message) == nil {
435+
messages = append(messages, message)
436+
}
437+
}
438+
}
439+
return messages
440+
}),
441+
)
376442
}
377443

378-
func NewMyMemory(/* Dependencies */) syndicate.Memory {
379-
return &MyMemory{
380-
/* Initialize your dependencies here */
381-
}
444+
// Redis-backed memory example
445+
func NewRedisMemory(client *redis.Client, agentID string) (syndicate.Memory, error) {
446+
key := fmt.Sprintf("agent:%s:messages", agentID)
447+
448+
return syndicate.NewMemory(
449+
syndicate.WithAddHandler(func(message syndicate.Message) {
450+
messageData, _ := json.Marshal(message)
451+
client.LPush(context.Background(), key, messageData)
452+
}),
453+
454+
syndicate.WithGetHandler(func() []syndicate.Message {
455+
result := client.LRange(context.Background(), key, 0, -1)
456+
messagesData, _ := result.Result()
457+
458+
var messages []syndicate.Message
459+
// Reverse to maintain chronological order
460+
for i := len(messagesData) - 1; i >= 0; i-- {
461+
var message syndicate.Message
462+
if json.Unmarshal([]byte(messagesData[i]), &message) == nil {
463+
messages = append(messages, message)
464+
}
465+
}
466+
return messages
467+
}),
468+
)
382469
}
383470

384-
func (m *MyMemory) Get() []syndicate.Message {
385-
m.mutex.RLock()
386-
defer m.mutex.RUnlock()
387-
ctx := context.Background()
471+
// Usage with custom memory
472+
func main() {
473+
db, _ := sql.Open("postgres", "postgres://user:password@localhost/dbname")
474+
475+
// Create custom database memory using functional options
476+
dbMemory, err := NewDatabaseMemory(db, "agent-123")
477+
if err != nil {
478+
log.Fatal(err)
479+
}
388480

389-
/* Implement the logic to retrieve messages from your memory system */
481+
agent, _ := syndicate.NewAgent(
482+
syndicate.WithClient(client),
483+
syndicate.WithName("PersistentAgent"),
484+
syndicate.WithMemory(dbMemory), // ✅ Custom memory with functional options
485+
// ... other options
486+
)
390487

391-
return messages
488+
// Conversations persist across application restarts!
489+
response, _ := agent.Chat(ctx,
490+
syndicate.WithUserName("Bob"),
491+
syndicate.WithInput("Remember that I'm working on project X."))
392492
}
493+
```
393494

394-
func func (m *MyMemory) Add(message syndicate.Message) {
395-
m.mutex.Lock()
396-
defer m.mutex.Unlock()
397-
ctx := context.Background()
495+
### Memory Interface
398496

399-
/* Implement the logic to add messages to your memory system */
497+
All memory implementations use this interface:
400498

499+
```go
500+
type Memory interface {
501+
Add(message Message) // Store a new message
502+
Get() []Message // Retrieve all stored messages
401503
}
504+
505+
// Create custom memory with functional options
506+
memory, err := syndicate.NewMemory(
507+
syndicate.WithAddHandler(func(msg syndicate.Message) {
508+
// Your custom add logic
509+
}),
510+
syndicate.WithGetHandler(func() []syndicate.Message {
511+
// Your custom get logic
512+
return messages
513+
}),
514+
)
402515
```
403516

517+
**✅ Use `NewSimpleMemory()` for development and testing**
518+
**✅ Use `NewMemory()` with functional options for production persistence**
519+
**✅ Both handlers (WithAddHandler and WithGetHandler) are required**
520+
404521
</details>
405522

406523
<details>

0 commit comments

Comments
 (0)