Skip to content

Commit 1cb7b22

Browse files
committed
better docs
1 parent 9e9f6d4 commit 1cb7b22

1 file changed

Lines changed: 301 additions & 23 deletions

File tree

README.md

Lines changed: 301 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,6 @@ A Go SDK for building and orchestrating intelligent AI agents that seamlessly co
1919
**Version:** v0.2.0
2020
**Go Version:** 1.24+
2121

22-
## 🤔 Why Syndicate?
23-
24-
| Comparison | Syndicate | Direct API Calls | Other Solutions |
25-
| --------------------- | --------------------------------------------------- | --------------------------------- | --------------------------- |
26-
| **Modularity** | ✅ Fully modular architecture | ❌ Code coupled to a provider | ⚠️ Varies by implementation |
27-
| **Multi-agent** | ✅ Native support for orchestrating multiple agents | ❌ Requires manual implementation | ⚠️ Limited or complex |
28-
| **Memory Management** | ✅ Customizable and thread-safe | ❌ Not included | ⚠️ Generally basic |
29-
| **Tool Integration** | ✅ Automatic with schema validation | ❌ Manual | ⚠️ Often limited |
30-
| **Overhead** | ✅ Minimal, built for performance | ✅ None | ❌ Often significant |
31-
32-
## 📊 LLM Compatibility
33-
34-
| Provider | Status | Supported Models |
35-
| ------------ | ----------------- | ----------------------- |
36-
| OpenAI | ✅ Complete | GPT-4o, GPT-4, o3, etc. |
37-
| Azure OpenAI | ✅ Complete | All Azure OpenAI models |
38-
| Deepseek | ✅ Basic | DeepseekR1 |
39-
| Claude | 🔄 In development | - |
40-
41-
## 📚 Documentation
42-
43-
For a complete overview of the SDK features, see our [Quick Guide](https://github.com/Dieg0Code/syndicate-go/tree/main/examples/QuickGuide).
44-
4522
## 📦 Installation
4623

4724
```bash
@@ -125,6 +102,307 @@ For a complete step-by-step guide with tool integration and custom memory implem
125102

126103
## 🛠️ Advanced Features
127104

105+
<details>
106+
<summary><b>Tool Integration</b></summary>
107+
108+
Integrate external tools with agents using JSON schemas. The SDK automatically generates schemas from Go structures, allowing for easy validation and integration.
109+
110+
```go
111+
package main
112+
113+
import (
114+
"encoding/json"
115+
"fmt"
116+
"log"
117+
118+
syndicate "github.com/Dieg0Code/syndicate-go"
119+
)
120+
121+
// 📝 Defining the schema for menu items
122+
type MenuItemSchema struct {
123+
ItemName string `json:"item_name" description:"Menu item name" required:"true"`
124+
Quantity int `json:"quantity" description:"Quantity ordered by the user" required:"true"`
125+
Price int `json:"price" description:"Menu item price" required:"true"`
126+
}
127+
128+
// 📝 Defining the schema for the user's order
129+
type UserOrderFunctionSchema struct {
130+
MenuItems []MenuItemSchema `json:"menu_items" description:"List of ordered menu items" required:"true"`
131+
DeliveryAddress string `json:"delivery_address" description:"Order delivery address" required:"true"`
132+
UserName string `json:"user_name" description:"User's name placing the order" required:"true"`
133+
PhoneNumber string `json:"phone_number" description:"User's phone number" required:"true"`
134+
PaymentMethod string `json:"payment_method" description:"Payment method (cash or transfer only)" required:"true" enum:"cash,transfer"`
135+
}
136+
137+
func main() {
138+
// 🏗️ Generate the JSON schema
139+
schema, err := syndicate.GenerateRawSchema(UserOrderFunctionSchema{})
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
144+
// 🎨 Pretty-print the schema
145+
pretty, err := json.MarshalIndent(json.RawMessage(schema), "", " ")
146+
if err != nil {
147+
log.Fatal(err)
148+
}
149+
150+
// 📜 Display the generated schema
151+
fmt.Println("UserOrderFunction schema:")
152+
153+
fmt.Println(string(pretty))
154+
}
155+
```
156+
157+
---
158+
159+
### 🏗️ What does `GenerateRawSchema` do?
160+
161+
The function `GenerateRawSchema` returns a value of type `json.RawMessage`, which is just an alias for `[]byte`. This contains the **JSON schema** we need to define our **Tool**. 🛠️🔧
162+
163+
This structure generates the following JSON schema: 🎯
164+
165+
```json
166+
{
167+
"type": "object",
168+
"properties": {
169+
"delivery_address": {
170+
"type": "string",
171+
"description": "Order delivery address"
172+
},
173+
"menu_items": {
174+
"type": "array",
175+
"description": "List of ordered menu items",
176+
"items": {
177+
"type": "object",
178+
"properties": {
179+
"item_name": {
180+
"type": "string",
181+
"description": "Menu item name"
182+
},
183+
"price": {
184+
"type": "integer",
185+
"description": "Menu item price"
186+
},
187+
"quantity": {
188+
"type": "integer",
189+
"description": "Quantity ordered by the user"
190+
}
191+
},
192+
"required": ["item_name", "quantity", "price"],
193+
"additionalProperties": false
194+
}
195+
},
196+
"payment_method": {
197+
"type": "string",
198+
"description": "Payment method (cash or transfer only)",
199+
"enum": ["cash", "transfer"]
200+
},
201+
"phone_number": {
202+
"type": "string",
203+
"description": "User's phone number"
204+
},
205+
"user_name": {
206+
"type": "string",
207+
"description": "User's name placing the order"
208+
}
209+
},
210+
"required": [
211+
"menu_items",
212+
"delivery_address",
213+
"user_name",
214+
"phone_number",
215+
"payment_method"
216+
],
217+
"additionalProperties": false
218+
}
219+
```
220+
221+
### 🔄 Deserializing the Response
222+
223+
We can use the same Go structure to capture the response and deserialize it into a Go object. 🧑‍💻📦 This makes it easier to handle the data in your application.
224+
225+
#### Definition of Jsonschemas and Their Handlers 🚀
226+
227+
Now that we know how to create Tools for the LLM, the question arises: **How do we tell the LLM what to do with that information?** 🤔 To do that, we need to define a **`Handler`** for each `Tool`.
228+
229+
Manually creating the logic to distinguish between when the LLM responds with a normal message or with a call to a `Tool` can be tedious and error-prone. 😅 That's why `Syndicate` offers a way to define Handlers for each `Tool`, which are responsible for processing the information the LLM receives.
230+
231+
To achieve this, we have the **`Tool`** interface:
232+
233+
```go
234+
type Tool interface {
235+
GetDefinition() ToolDefinition // Returns the definition of the tool (name, description, parameters, etc.)
236+
Execute(args json.RawMessage) (interface{}, error) // Executes the tool with the given arguments
237+
}
238+
```
239+
240+
The SDK requires you to implement this interface in order to associate tools with an agent. The interface has two methods:
241+
242+
- **`GetDefinition`**: Returns the definition of the tool, which includes the name, description, parameters, and whether it's strict or not. 📜
243+
- **`Execute`**: This is the method called when the LLM makes a call to the tool. It receives the arguments for the call and returns an object that can be anything, but it must be something that can be converted to a string, since the result of calling the tool will later be passed back to the LLM for further processing. 🔄
244+
245+
Here's an example of what a `Handler` for the `SaveOrder` tool might look like: 🎯
246+
247+
```go
248+
package main
249+
250+
import (
251+
"encoding/json"
252+
"fmt"
253+
"log"
254+
255+
syndicate "github.com/Dieg0Code/syndicate-go"
256+
)
257+
258+
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"`
262+
}
263+
264+
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"`
270+
}
271+
272+
type SaveOrderTool struct {
273+
// Here you can add any necessary fields to process the call
274+
}
275+
276+
func NewSaveOrderTool() syndicate.Tool {
277+
return &SaveOrderTool{}
278+
}
279+
280+
func (s *SaveOrderTool) GetDefinition() syndicate.ToolDefinition {
281+
schema, err := syndicate.GenerateRawSchema(UserOrderFunctionSchema{})
282+
if err != nil {
283+
log.Fatal(err)
284+
}
285+
286+
return syndicate.ToolDefinition{
287+
Name: "SaveOrder",
288+
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.",
289+
Parameters: schema,
290+
Strict: true,
291+
}
292+
}
293+
294+
func (s *SaveOrderTool) Execute(args json.RawMessage) (interface{}, error) {
295+
var order UserOrderFunctionSchema
296+
if err := json.Unmarshal(args, &order); err != nil {
297+
return nil, err
298+
}
299+
300+
// You can do whatever you want with the order information here
301+
// Save it to a database, send it to an external service, etc.
302+
// It's up to you.
303+
// Usually, you'll want to inject a repo dependency into the SaveOrderTool struct and constructor
304+
// and use it here to store the information.
305+
fmt.Printf("Order received: %+v\n", order)
306+
307+
return "Order received successfully", nil
308+
}
309+
310+
func main() {
311+
// Create a new instance of the tool
312+
saveOrderTool := NewSaveOrderTool()
313+
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()
323+
if err != nil {
324+
fmt.Printf("Error creating agent: %v\n", err)
325+
}
326+
327+
// Process a sample input with the agent 🧠
328+
response, err := agent.Process(context.Background(), "Jhon Doe", "What is on the menu?")
329+
if err != nil {
330+
fmt.Printf("Error processing input: %v\n", err)
331+
}
332+
333+
fmt.Println("\nAgent Response:")
334+
fmt.Println(response)
335+
}
336+
```
337+
338+
### Key Points 💡
339+
340+
- **`GetDefinition`** returns the definition of the tool, including its name, description, and parameters that the LLM should send when it calls the tool. 📝
341+
- **`Execute`** processes the arguments passed by the LLM, allowing you to perform actions like storing the order in a database or making API calls. 🔄
342+
343+
In the `main` function, we create an agent, equip it with the `SaveOrderTool`, and process a sample input. The LLM will be able to call the tool and execute it with the provided arguments, and you can customize what happens inside the `Execute` method. 🚀
344+
345+
By simply implementing the `Tool` interface and adding the tool to the agent, you can process calls to the tool and do whatever you want with the information the LLM sends you. 🔧🤖 `Syndicate` internally handles detecting when the LLM uses a tool and uses the corresponding `Handler` to process it. 🛠️✨
346+
347+
</details>
348+
349+
<details>
350+
<summary><b>Memory Management</b></summary>
351+
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.
353+
354+
```go
355+
package main
356+
357+
358+
/* Import necessary packages */
359+
360+
type ChatMemory struct {
361+
362+
/* These fields are important for the memory system */
363+
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
369+
370+
/* Add any other fields you need for your memory system */
371+
372+
}
373+
374+
type MyMemory struct {
375+
/* Define your necessary dependencies here */
376+
}
377+
378+
func NewMyMemory(/* Dependencies */) syndicate.Memory {
379+
return &MyMemory{
380+
/* Initialize your dependencies here */
381+
}
382+
}
383+
384+
func (m *MyMemory) Get() []syndicate.Message {
385+
m.mutex.RLock()
386+
defer m.mutex.RUnlock()
387+
ctx := context.Background()
388+
389+
/* Implement the logic to retrieve messages from your memory system */
390+
391+
return messages
392+
}
393+
394+
func func (m *MyMemory) Add(message syndicate.Message) {
395+
m.mutex.Lock()
396+
defer m.mutex.Unlock()
397+
ctx := context.Background()
398+
399+
/* Implement the logic to add messages to your memory system */
400+
401+
}
402+
```
403+
404+
</details>
405+
128406
<details>
129407
<summary><b>Config Prompt Builder</b></summary>
130408

0 commit comments

Comments
 (0)