-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbinary.go
More file actions
186 lines (159 loc) · 5.64 KB
/
Copy pathbinary.go
File metadata and controls
186 lines (159 loc) · 5.64 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
package zyn
import (
"context"
"fmt"
"github.com/zoobz-io/pipz"
)
// BinaryInput contains rich input structure for binary decisions.
type BinaryInput struct {
Subject string // The main item being evaluated
Context string // Background information or situation
Criteria []string // Specific criteria for evaluation
Examples []string // Examples of positive/negative cases
Constraints []string // Limitations or requirements
Temperature float32 // LLM temperature setting for this specific request
}
// BinaryResponse contains the response from a binary synapse.
type BinaryResponse struct {
Decision bool `json:"decision"` // Binary yes/no result
Confidence float64 `json:"confidence"` // 0.0 to 1.0 confidence score
Reasoning []string `json:"reasoning"` // Explanation of decision
}
// Validate checks if the response is valid.
func (r BinaryResponse) Validate() error {
if r.Confidence < 0 || r.Confidence > 1 {
return fmt.Errorf("confidence must be 0-1, got %f", r.Confidence)
}
if len(r.Reasoning) == 0 {
return fmt.Errorf("reasoning required but empty")
}
return nil
}
// BinarySynapse represents a binary (yes/no) decision synapse.
type BinarySynapse struct {
question string
schema string // Pre-computed JSON schema
defaults BinaryInput
service *Service[BinaryResponse]
}
// NewBinary creates a new binary synapse bound to a provider.
// The synapse is immediately usable and can be enhanced with options.
// Returns an error if the JSON schema cannot be generated.
func NewBinary(question string, provider Provider, opts ...Option) (*BinarySynapse, error) {
// Generate schema once at construction
schema, err := generateJSONSchema[BinaryResponse]()
if err != nil {
return nil, fmt.Errorf("binary synapse: %w", err)
}
// Apply options to build pipeline
pipeline := NewTerminal(provider)
for _, opt := range opts {
pipeline = opt(pipeline)
}
// Create service with final pipeline and default temperature
svc := NewService[BinaryResponse](pipeline, "binary", provider, DefaultTemperatureDeterministic)
return &BinarySynapse{
question: question,
schema: schema,
service: svc,
}, nil
}
// GetPipeline returns the internal pipeline for composition.
// Implements ServiceProvider interface.
func (b *BinarySynapse) GetPipeline() pipz.Chainable[*SynapseRequest] {
return b.service.GetPipeline()
}
// WithDefaults creates a new Binary with default input values.
// These are merged with user input at execution time.
func (b *BinarySynapse) WithDefaults(defaults BinaryInput) *BinarySynapse {
b.defaults = defaults
return b
}
// Fire executes the synapse against a simple string input.
// Returns only the boolean decision.
func (b *BinarySynapse) Fire(ctx context.Context, session *Session, input string) (bool, error) {
response, err := b.FireWithDetails(ctx, session, input)
if err != nil {
return false, err
}
return response.Decision, nil
}
// FireWithDetails executes the synapse and returns the full response.
func (b *BinarySynapse) FireWithDetails(ctx context.Context, session *Session, input string) (BinaryResponse, error) {
binInput := BinaryInput{Subject: input}
return b.FireWithInput(ctx, session, binInput)
}
// FireWithInput executes the synapse with rich input structure.
func (b *BinarySynapse) FireWithInput(ctx context.Context, session *Session, input BinaryInput) (BinaryResponse, error) {
// Merge defaults with user input
merged := b.mergeInputs(input)
// Build prompt
prompt := b.buildPrompt(merged)
// Execute through service with session (service handles temperature fallback)
return b.service.Execute(ctx, session, prompt, merged.Temperature)
}
// mergeInputs combines defaults with user input.
func (b *BinarySynapse) mergeInputs(input BinaryInput) BinaryInput {
merged := b.defaults
if input.Subject != "" {
merged.Subject = input.Subject
}
if input.Context != "" {
merged.Context = input.Context
}
if len(input.Criteria) > 0 {
merged.Criteria = append(merged.Criteria, input.Criteria...)
}
if len(input.Examples) > 0 {
merged.Examples = append(merged.Examples, input.Examples...)
}
if len(input.Constraints) > 0 {
merged.Constraints = append(merged.Constraints, input.Constraints...)
}
if input.Temperature != 0 && input.Temperature != TemperatureUnset {
merged.Temperature = input.Temperature
}
return merged
}
// buildPrompt constructs the prompt from the merged input.
func (b *BinarySynapse) buildPrompt(input BinaryInput) *Prompt {
prompt := &Prompt{
Task: fmt.Sprintf("Determine if %s", b.question),
Input: input.Subject,
Context: input.Context,
Schema: b.schema,
}
// Build constraints
prompt.Constraints = []string{
"decision: true or false only",
"confidence: 0.0 to 1.0",
"reasoning: ordered steps explaining decision",
}
// Add criteria as constraints if provided
for _, c := range input.Criteria {
prompt.Constraints = append(prompt.Constraints, "evaluate: "+c)
}
// Add input constraints if provided
prompt.Constraints = append(prompt.Constraints, input.Constraints...)
// Add examples if provided
if len(input.Examples) > 0 {
prompt.Examples = map[string][]string{
"examples": input.Examples,
}
}
return prompt
}
// Binary creates a new binary synapse bound to a provider.
// The synapse is immediately usable and can be enhanced with options.
// Returns an error if the JSON schema cannot be generated.
//
// Example:
//
// synapse, err := Binary("Is this valid?", provider,
// WithRetry(3),
// WithTimeout(10*time.Second),
// )
// result, err := synapse.Fire(ctx, "test@example.com")
func Binary(question string, provider Provider, opts ...Option) (*BinarySynapse, error) {
return NewBinary(question, provider, opts...)
}