Multi-Turn Conversations
Patterns for building complex workflows using session context.
Customer Support Agent
Multi-step support workflow with context:
package main
import (
"context"
"fmt"
"github.com/zoobz-io/zyn"
)
type SupportAgent struct {
extractor *zyn.ExtractionSynapse[CustomerIssue]
classifier *zyn.ClassificationSynapse
responder *zyn.TransformSynapse
resolver *zyn.BinarySynapse
}
type CustomerIssue struct {
Name string `json:"name"`
Issue string `json:"issue"`
ProductArea string `json:"product_area"`
}
func (c CustomerIssue) Validate() error {
if c.Issue == "" {
return fmt.Errorf("issue description required")
}
return nil
}
func NewSupportAgent(provider zyn.Provider) (*SupportAgent, error) {
extractor, _ := zyn.Extract[CustomerIssue]("Extract customer name, issue, and product area", provider)
classifier, _ := zyn.Classification("Issue severity", []string{"low", "medium", "high", "critical"}, provider)
responder, _ := zyn.Transform("Generate empathetic customer service response", provider)
resolver, _ := zyn.Binary("Can this issue be resolved with available information?", provider)
return &SupportAgent{
extractor: extractor,
classifier: classifier,
responder: responder,
resolver: resolver,
}, nil
}
type SupportResponse struct {
CustomerName string
Severity string
Response string
NeedsHuman bool
}
func (a *SupportAgent) HandleTicket(ctx context.Context, customerMessage string) (*SupportResponse, error) {
// Shared session for full context
session := zyn.NewSession()
// Step 1: Extract issue details
issue, err := a.extractor.Fire(ctx, session, customerMessage)
if err != nil {
return nil, fmt.Errorf("extraction failed: %w", err)
}
// Step 2: Classify severity (sees extraction context)
severity, err := a.classifier.Fire(ctx, session, issue.Issue)
if err != nil {
return nil, fmt.Errorf("classification failed: %w", err)
}
// Step 3: Check if resolvable (sees full context)
resolvable, err := a.resolver.Fire(ctx, session, "Based on the issue and severity, can this be resolved automatically?")
if err != nil {
return nil, fmt.Errorf("resolution check failed: %w", err)
}
// Step 4: Generate response (sees all previous context)
prompt := fmt.Sprintf("Respond to %s about their %s severity issue regarding %s",
issue.Name, severity, issue.ProductArea)
response, err := a.responder.Fire(ctx, session, prompt)
if err != nil {
return nil, fmt.Errorf("response generation failed: %w", err)
}
return &SupportResponse{
CustomerName: issue.Name,
Severity: severity,
Response: response,
NeedsHuman: !resolvable || severity == "critical",
}, nil
}
Interview Assistant
Conduct a multi-question interview:
type InterviewAssistant struct {
questioner *zyn.TransformSynapse
evaluator *zyn.ClassificationSynapse
summarizer *zyn.TransformSynapse
}
type InterviewResult struct {
Questions []string
Answers []string
Evaluations []string
Summary string
}
func (a *InterviewAssistant) Conduct(ctx context.Context, topic string, numQuestions int) (*InterviewResult, error) {
session := zyn.NewSession()
result := &InterviewResult{}
// Initialize context
initPrompt := fmt.Sprintf("You are conducting an interview about %s. Ask probing questions.", topic)
session.Append(zyn.RoleSystem, initPrompt)
for i := 0; i < numQuestions; i++ {
// Generate question based on previous context
question, err := a.questioner.Fire(ctx, session, fmt.Sprintf("Ask question %d of %d", i+1, numQuestions))
if err != nil {
return nil, err
}
result.Questions = append(result.Questions, question)
// Simulate getting answer (in real app, this comes from user)
answer := getAnswer(question) // placeholder
result.Answers = append(result.Answers, answer)
// Add answer to session for context
session.Append(zyn.RoleUser, answer)
// Evaluate answer
evaluation, _ := a.evaluator.Fire(ctx, session, "Evaluate the quality of the last answer")
result.Evaluations = append(result.Evaluations, evaluation)
}
// Final summary with full context
result.Summary, _ = a.summarizer.Fire(ctx, session, "Summarize the interview and key findings")
return result, nil
}
Iterative Refinement
Refine output through multiple iterations:
type RefiningWriter struct {
writer *zyn.TransformSynapse
critic *zyn.TransformSynapse
improver *zyn.TransformSynapse
}
func (r *RefiningWriter) Write(ctx context.Context, prompt string, iterations int) (string, error) {
session := zyn.NewSession()
// Initial draft
draft, err := r.writer.Fire(ctx, session, prompt)
if err != nil {
return "", err
}
for i := 0; i < iterations; i++ {
// Get critique (sees previous drafts)
critique, err := r.critic.Fire(ctx, session, fmt.Sprintf("Critique this draft: %s", draft))
if err != nil {
return draft, nil // Return current draft on error
}
// Improve based on critique (sees draft and critique)
improved, err := r.improver.Fire(ctx, session, fmt.Sprintf("Improve the draft based on this feedback: %s", critique))
if err != nil {
return draft, nil
}
draft = improved
}
return draft, nil
}
Branching Conversations
Handle different paths based on responses:
type DecisionTree struct {
decider *zyn.ClassificationSynapse
handlers map[string]*zyn.TransformSynapse
}
func (d *DecisionTree) Process(ctx context.Context, input string) (string, error) {
session := zyn.NewSession()
// Decide which branch to take
decision, err := d.decider.Fire(ctx, session, input)
if err != nil {
return "", err
}
// Get appropriate handler
handler, ok := d.handlers[decision]
if !ok {
return "", fmt.Errorf("no handler for decision: %s", decision)
}
// Process with context from decision
return handler.Fire(ctx, session, "Process based on the decision")
}
Context Window Management
Handle long conversations:
type ConversationManager struct {
synapse *zyn.TransformSynapse
summarizer *zyn.TransformSynapse
maxMessages int
}
func (m *ConversationManager) Chat(ctx context.Context, session *zyn.Session, userMessage string) (string, error) {
// Check if we need to compress history
if session.Len() > m.maxMessages {
if err := m.compressHistory(ctx, session); err != nil {
return "", err
}
}
return m.synapse.Fire(ctx, session, userMessage)
}
func (m *ConversationManager) compressHistory(ctx context.Context, session *zyn.Session) error {
// Get messages to summarize (keep last few)
messages := session.Messages()
toSummarize := messages[:len(messages)-4] // Keep last 4
// Create summary
summarySession := zyn.NewSession()
summarySession.SetMessages(toSummarize)
summary, err := m.summarizer.Fire(ctx, summarySession, "Summarize this conversation")
if err != nil {
return err
}
// Replace with summary + recent messages
session.SetMessages(append(
[]zyn.Message{{Role: zyn.RoleSystem, Content: "Previous context: " + summary}},
messages[len(messages)-4:]...,
))
return nil
}
Parallel Processing with Shared Context
Process multiple aspects in parallel, then combine:
type ParallelAnalyzer struct {
sentiment *zyn.SentimentSynapse
topics *zyn.ExtractionSynapse[Topics]
entities *zyn.ExtractionSynapse[Entities]
summarizer *zyn.TransformSynapse
}
type AnalysisResult struct {
Sentiment string
Topics []string
Entities []string
Summary string
}
func (a *ParallelAnalyzer) Analyze(ctx context.Context, text string) (*AnalysisResult, error) {
var wg sync.WaitGroup
var sentiment zyn.SentimentResponse
var topics Topics
var entities Entities
var errs []error
var mu sync.Mutex
// Parallel analysis with independent sessions
wg.Add(3)
go func() {
defer wg.Done()
s := zyn.NewSession()
result, err := a.sentiment.FireWithDetails(ctx, s, text)
mu.Lock()
if err != nil {
errs = append(errs, err)
} else {
sentiment = result
}
mu.Unlock()
}()
go func() {
defer wg.Done()
s := zyn.NewSession()
result, err := a.topics.Fire(ctx, s, text)
mu.Lock()
if err != nil {
errs = append(errs, err)
} else {
topics = result
}
mu.Unlock()
}()
go func() {
defer wg.Done()
s := zyn.NewSession()
result, err := a.entities.Fire(ctx, s, text)
mu.Lock()
if err != nil {
errs = append(errs, err)
} else {
entities = result
}
mu.Unlock()
}()
wg.Wait()
if len(errs) > 0 {
return nil, fmt.Errorf("analysis errors: %v", errs)
}
// Combine results with summarizer (new session with combined context)
combineSession := zyn.NewSession()
combinedContext := fmt.Sprintf("Sentiment: %s\nTopics: %v\nEntities: %v",
sentiment.Overall, topics.Items, entities.Items)
summary, _ := a.summarizer.Fire(ctx, combineSession, "Summarize these analysis results: "+combinedContext)
return &AnalysisResult{
Sentiment: sentiment.Overall,
Topics: topics.Items,
Entities: entities.Items,
Summary: summary,
}, nil
}
Next Steps
- Error Handling - Robust error management
- Sessions Guide - Session fundamentals