Syndicate SDK is a powerful, agile, and extensible toolkit for crafting intelligent conversational agents in Golang. Designed with both flexibility and simplicity in mind, it empowers developers to build AI-driven agents capable of sophisticated prompt engineering, seamless tool integration, dynamic memory management, and orchestrating complex workflows. Whether you're prototyping or building production-grade solutions, Syndicate SDK lets you unleash the full potential of conversational AI with a lightweight framework thatβs both hacker-friendly and enterprise-ready. π
For a complete summary of the SDK features, head over to our π Quick Guide π πβ¨
To install Syndicate SDK, use Go modules:
go get github.com/Dieg0Code/syndicate-go
Ensure that you have Go installed and properly configured.
What it is:
Agents are the core of the Syndicate SDK. They are self-contained AI entities, each with its own personality, knowledge, and optional tools.
Why it's useful:
- Customization: Tailor each agent's behavior and configuration using custom system prompts.
- Scalability: Easily add or modify agents based on evolving requirements.
- Modularity: Agents can be combined to form complex workflows (pipelines) that mirror real-world processes.
Example: Use one agent to process an order, and another to provide a summaryβeach handling different tasks for a comprehensive experience! π
What it is:
Prompt engineering involves crafting detailed and structured instructions that govern how agents respond.
Why it's useful:
- Clarity: Creating nested sections and structured prompts ensures the agent clearly understands its role.
- Consistency: Maintain a standard set of behaviors and responses, which is crucial for reliability in production scenarios.
- Adaptability: Easily update prompts to adapt to new requirements or contextual changes.
Fun fact: This helps guide the AI to focus on specific details, ensuring conversations stay on point! β¨
What it is:
Tool schemas are generated JSON definitions from Go structures. They describe how external tools interact with the agents.
Why it's useful:
- Validation: Automatically validates inputs, ensuring only correct and safe data is passed to tools.
- Integration: Seamlessly integrates with external services or databases to perform specialized tasks.
- Automation: Reduces manual configuration, making development faster and less error-prone.
Imagine: Your agent can now reliably call external services with a clearly defined contract! π‘
What it is:
Memory components allow agents to record, retrieve, and clear conversational context. This can be as simple as an in-memory store or an implementation using databases like SQLite.
Why it's useful:
- Context Preservation: Maintain important details across multiple interactions, helping agents provide more informed answers.
- Customization: Developers can implement custom memory solutions to match performance or persistence requirements.
- Scalability: Enhances the conversational flow by keeping track of conversation history.
For example: Using a custom memory lets you record previous orders or context for follow-up questions! π
What it is:
Syndicate is the engine that recruits agents and establishes pipelinesβdictating the order in which agents perform tasks.
Why it's useful:
- Workflow Management: Orchestrate complex processes by chaining multiple agents together.
- Simplicity: Simplifies the process of switching between agents or forming multi-step conversations.
- Flexibility: Adapt to different workflows without reworking the core logic each time.
In practice: Easily create pipelines where one agent processes the order and another provides a summary. Coordination made simple! π€
What it is:
The SDK is designed to be unopinionated, meaning it can be easily integrated into a variety of projects without forcing rigid architectural decisions.
Why it's useful:
- Flexibility: Choose your own tools, memory implementations, and configuration strategies.
- Integration: Seamlessly integrate into existing projects without major rework.
- Future-Proofing: Adapt the SDK to new requirements, features, or third-party integrations.
The outcome: A robust toolkit that adapts to your needs whether youβre building a prototype or a production-grade system! π
For a hands-on demo, follow these steps to create agents, define a tool, implement custom memory, and run the processing pipeline.
-
Define the Order Data Structures:
Create the structsOrderItem
andOrderSchema
to represent a customer's order. -
Implement the Order Tool:
Create a new tool that processes the order:
package main
import (
"encoding/json"
"fmt"
"log"
syndicate "github.com/Dieg0Code/syndicate-go"
)
// OrderItem defines a single item in a customer's order.
type OrderItem struct {
ItemName string `json:"item_name" description:"Name of the menu item" required:"true"`
Quantity int `json:"quantity" description:"Number of items ordered" required:"true"`
Price int `json:"price" description:"Price in cents" required:"true"`
}
// OrderSchema defines the complete order structure.
type OrderSchema struct {
Items []OrderItem `json:"items" description:"List of ordered items" required:"true"`
DeliveryAddress string `json:"delivery_address" description:"Delivery address" required:"true"`
CustomerName string `json:"customer_name" description:"Customer name" required:"true"`
PhoneNumber string `json:"phone_number" description:"Customer phone number" required:"true"`
PaymentMethod string `json:"payment_method" description:"Payment method (cash or transfer)" required:"true" enum:"cash,transfer"`
}
// OrderTool simulates an order processing tool.
type OrderTool struct{}
// NewOrderTool returns a new OrderTool instance.
func NewOrderTool() syndicate.Tool {
return &OrderTool{}
}
// GetDefinition generates the tool definition.
func (ot *OrderTool) GetDefinition() syndicate.ToolDefinition {
schema, err := syndicate.GenerateRawSchema(OrderSchema{})
if err != nil {
log.Fatal(err)
}
return syndicate.ToolDefinition{
Name: "OrderProcessor",
Description: "Processes orders by saving details (items, address, customer info, and payment method).",
Parameters: schema,
Strict: true,
}
}
// Execute processes the order.
func (ot *OrderTool) Execute(args json.RawMessage) (interface{}, error) {
var order OrderSchema
if err := json.Unmarshal(args, &order); err != nil {
return nil, err
}
fmt.Printf("Processing Order: %+v\n", order)
return "Order processed successfully", nil
}
Implement a simple custom memory using SQLite for persisting messages:
package main
import (
"fmt"
"sync"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
syndicate "github.com/Dieg0Code/syndicate-go"
)
// CustomMemory stores messages using SQLite.
type CustomMemory struct {
db *gorm.DB
mutex sync.RWMutex
}
// NewCustomMemory initializes CustomMemory with a Gorm DB.
func NewCustomMemory(db *gorm.DB) syndicate.Memory {
return &CustomMemory{
db: db,
}
}
// Add saves a message.
func (m *CustomMemory) Add(message syndicate.Message) {
m.mutex.Lock()
defer m.mutex.Unlock()
fmt.Printf("Memory Add: %+v\n", message)
// Optionally, save to db here.
}
// Get retrieves stored messages (returns nil for simplicity).
func (m *CustomMemory) Get() []syndicate.Message {
m.mutex.RLock()
defer m.mutex.RUnlock()
return nil
}
// Clear erases all messages.
func (m *CustomMemory) Clear() {
m.mutex.Lock()
defer m.mutex.Unlock()
fmt.Println("Memory cleared")
}
Combine everything by creating two agents (OrderAgent and SummaryAgent) and a pipeline:
package main
import (
"context"
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
syndicate "github.com/Dieg0Code/syndicate-go"
openai "github.com/sashabaranov/go-openai"
)
func main() {
// Initialize OpenAI client.
client := syndicate.NewOpenAIClient("YOUR_OPENAI_API_KEY")
// Set up SQLite connection.
db, err := gorm.Open(sqlite.Open("memory.db"), &gorm.Config{})
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
customMemory := NewCustomMemory(db)
// Step 3A: Create the Order Tool.
orderTool := NewOrderTool()
// Step 3B: Build the OrderAgent.
orderAgent, err := syndicate.NewAgent().
SetClient(client).
SetName("OrderAgent").
SetConfigPrompt("You are an agent processing customer orders. If the input contains order details, call the OrderProcessor tool.").
SetMemory(customMemory).
SetModel(openai.GPT4).
EquipTool(orderTool). // Equip OrderTool
Build()
if err != nil {
log.Fatalf("Error building OrderAgent: %v", err)
}
// Step 3C: Build the SummaryAgent.
summaryAgent, err := syndicate.NewAgent().
SetClient(client).
SetName("SummaryAgent").
SetConfigPrompt("You are an agent providing the final summary of the order.").
SetMemory(customMemory).
SetModel(openai.GPT4).
Build()
if err != nil {
log.Fatalf("Error building SummaryAgent: %v", err)
}
// Step 3D: Define the pipeline with both agents.
syndicateSystem := syndicate.NewSyndicate().
RecruitAgent(orderAgent).
RecruitAgent(summaryAgent).
DefinePipeline([]string{"OrderAgent", "SummaryAgent"}).
Build()
// Simulate user input.
userName := "Alice"
input := `Hi, I'd like to place an order. I want two Margherita Pizzas and one Coke. Please deliver them to 123 Main Street in Springfield. My name is Alice, my phone number is 555-1234, and I'll pay with cash.`
// Execute the pipeline.
response, err := syndicateSystem.ExecutePipeline(context.Background(), userName, input)
if err != nil {
log.Fatalf("Error executing pipeline: %v", err)
}
fmt.Println("\nFinal Syndicate Response:")
fmt.Println(response)
}
- Replace
"YOUR_OPENAI_API_KEY"
with your actual API key. - Build and run your application.
- Watch as the agents process and summarize the order step-by-step!
Config Prompt Builder
The Config Prompt Builder is a utility that simplifies the creation of agent configuration prompts. It allows you to define a structured prompt using a fluent API, making it easier to configure agents with specific instructions.
package main
import (
"fmt"
"time"
syndicate "github.com/Dieg0Code/syndicate-go"
)
type MenuItem struct {
Name string
Description string
Pricing int
}
// NewConfigPrompt generates the system prompt in XML format.
func NewConfigPrompt(name string, additionalContext MenuItem) string {
configPrompt := syndicate.NewPromptBuilder().
// Introduction section
CreateSection("Introduction").
AddText("Introduction", "You are an agent who provides detailed information about the menu, dishes, and key restaurant data using a semantic search system to enrich responses with relevant context.").
// Agent identity
CreateSection("Identity").
AddText("Identity", "This section defines your name and persona identity.").
AddSubSection("Name", "Identity").
AddTextF("Name", name).
AddSubSection("Persona", "Identity").
AddText("Persona", "You act as an AI assistant in the restaurant, interacting with customers in a friendly and helpful manner to improve their dining experience.").
// Capabilities and behavior
CreateSection("CapabilitiesAndBehavior").
AddListItem("CapabilitiesAndBehavior", "Respond in a clear and friendly manner, tailoring your answer to the user's query.").
AddListItem("CapabilitiesAndBehavior", "Provide details about dishes (ingredients, preparation, allergens) and suggest similar options if appropriate.").
AddListItem("CapabilitiesAndBehavior", "Promote the restaurant positively, emphasizing the quality of dishes and the dining experience.").
AddListItem("CapabilitiesAndBehavior", "Be cheerful, polite, and respectful at all times; use emojis if appropriate.").
AddListItem("CapabilitiesAndBehavior", "Register or cancel orders but do not update them; inform the user accordingly.").
AddListItem("CapabilitiesAndBehavior", "Remember only the last 5 interactions with the user.").
// Additional context
CreateSection("AdditionalContext").
AddText("AdditionalContext", "This section contains additional information about the available dishes used to answer user queries based on semantic similarity.").
AddSubSection("Menu", "AdditionalContext").
AddTextF("Menu", additionalContext).
AddSubSection("CurrentDate", "AdditionalContext").
AddTextF("CurrentDate", time.Now().Format(time.RFC3339)).
AddListItem("AdditionalContext", "Select dishes based on similarity without mentioning it explicitly.").
AddListItem("AdditionalContext", "Use context to enrich responses, but do not reveal it.").
AddListItem("AdditionalContext", "Offer only dishes available on the menu.").
// Limitations and directives
CreateSection("LimitationsAndDirectives").
AddListItem("LimitationsAndDirectives", "Do not invent data or reveal confidential information.").
AddListItem("LimitationsAndDirectives", "Redirect unrelated topics to relevant restaurant topics.").
AddListItem("LimitationsAndDirectives", "Strictly provide information only about the restaurant and its menu.").
AddListItem("LimitationsAndDirectives", "Offer only available menu items; do not invent dishes.").
// Response examples
CreateSection("ResponseExamples").
AddListItem("ResponseExamples", "If asked about a dish, provide details only if it is on the menu.").
AddListItem("ResponseExamples", "If asked for recommendations, suggest only from the available menu.").
AddListItem("ResponseExamples", "If asked for the menu, list only available dishes.").
// Final considerations
CreateSection("FinalConsiderations").
AddText("FinalConsiderations", "**You must follow these directives to ensure an optimal user experience, otherwise you will be dismissed.**").
Build()
return configPrompt
}
func main() {
// Define the additional context for the agent.
additionalContext := MenuItem{
Name: "Spaghetti Carbonara",
Description: "A classic Italian pasta dish consisting of eggs, cheese, pancetta, and black pepper.",
Pricing: 15,
}
// Generate the system prompt for the agent.
configPrompt := NewConfigPrompt("Bob", additionalContext)
fmt.Println(configPrompt)
}
- sashabaranov/go-openai - Licensed under Apache License 2.0
- cohesion-org/deepseek-go - Licensed under MIT License
Refer to their repositories for full license texts.
Contributions are welcome! Feel free to open issues or submit pull requests on GitHub.
Happy coding! π©βπ»π¨βπ»