Repository navigation
Expand file tree
/
Copy pathrule_engine.go
More file actions
147 lines (126 loc) · 4.47 KB
/
Copy pathrule_engine.go
File metadata and controls
147 lines (126 loc) · 4.47 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
package shield360
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
// RuleEntityType constrains the entity_type values accepted by the rule engine.
type RuleEntityType string
const (
// RuleEntityContext matches rules that return context entities.
RuleEntityContext RuleEntityType = "context"
// RuleEntityPrompt matches rules that return prompt entities.
RuleEntityPrompt RuleEntityType = "prompt"
// RuleEntityEvaluation matches rules that return evaluation entities.
RuleEntityEvaluation RuleEntityType = "evaluation"
)
// EvaluateRuleOptions holds parameters for the EvaluateRule call.
type EvaluateRuleOptions struct {
// URL is the Shield360 dashboard base URL.
// Falls back to SHIELD360_URL env var, then http://127.0.0.1:3000.
URL string
// APIKey is the Bearer token for authentication.
// Falls back to SHIELD360_API_KEY env var.
APIKey string
// EntityType specifies which kind of entities to match ("context", "prompt", or "evaluation").
EntityType RuleEntityType
// Fields are the trace attributes to evaluate against rules.
// Example: {"gen_ai.system": "openai", "gen_ai.request.model": "gpt-4"}
Fields map[string]interface{}
// IncludeEntityData requests full entity data in the response when true.
IncludeEntityData bool
// EntityInputs provides optional inputs for entity resolution (e.g. prompt variables).
EntityInputs map[string]interface{}
// Timeout for the HTTP request. Defaults to 30 seconds.
Timeout time.Duration
}
// RuleEntity represents a single matched rule-entity association.
type RuleEntity struct {
RuleID string `json:"rule_id"`
EntityType string `json:"entity_type"`
EntityID string `json:"entity_id"`
}
// EvaluateRuleResult is the parsed response from the rule engine evaluate endpoint.
type EvaluateRuleResult struct {
MatchingRuleIDs []string `json:"matchingRuleIds"`
Entities []RuleEntity `json:"entities"`
EntityData map[string]interface{} `json:"entity_data,omitempty"`
}
// EvaluateRule calls the Shield360 dashboard rule-engine evaluate endpoint.
// It sends the provided fields against configured rules and returns matching
// rule IDs, associated entities, and optionally the full entity data.
//
// This function does NOT require shield360.Init() — it is a standalone HTTP
// call, consistent with get_prompt/get_secrets in the Python and TypeScript SDKs.
//
// Example:
//
// result, err := shield360.EvaluateRule(ctx, shield360.EvaluateRuleOptions{
// EntityType: shield360.RuleEntityContext,
// Fields: map[string]interface{}{
// "gen_ai.system": "openai",
// "gen_ai.request.model": "gpt-4",
// "service.name": "my-app",
// },
// IncludeEntityData: true,
// })
func EvaluateRule(ctx context.Context, opts EvaluateRuleOptions) (*EvaluateRuleResult, error) {
// Resolve URL
url := opts.URL
if url == "" {
url = os.Getenv("SHIELD360_URL")
}
if url == "" {
url = "http://127.0.0.1:3000"
}
// Resolve API key
apiKey := opts.APIKey
if apiKey == "" {
apiKey = os.Getenv("SHIELD360_API_KEY")
}
if apiKey == "" {
return nil, fmt.Errorf("shield360: missing API key, provide APIKey or set SHIELD360_API_KEY")
}
// Build payload
payload := map[string]interface{}{
"entity_type": opts.EntityType,
"fields": opts.Fields,
"include_entity_data": opts.IncludeEntityData,
"source": "go-sdk",
}
if opts.EntityInputs != nil {
payload["entity_inputs"] = opts.EntityInputs
}
body, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("shield360: failed to marshal request: %w", err)
}
timeout := opts.Timeout
if timeout == 0 {
timeout = 30 * time.Second
}
client := &http.Client{Timeout: timeout}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url+"/api/rule-engine/evaluate", bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("shield360: failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("shield360: request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("shield360: server returned status %d", resp.StatusCode)
}
var result EvaluateRuleResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("shield360: failed to decode response: %w", err)
}
return &result, nil
}