@@ -4,14 +4,15 @@ import (
44 "bytes"
55 "encoding/json"
66 "fmt"
7+ "github.com/AlecAivazis/survey/v2"
8+ "github.com/BurntSushi/toml"
9+ "github.com/santhosh-tekuri/jsonschema/v5"
710 "io"
811 "net/http"
912 "os/exec"
1013 "runtime"
14+ "strings"
1115 "time"
12-
13- "github.com/AlecAivazis/survey/v2"
14- "github.com/BurntSushi/toml"
1516)
1617
1718func LoadLLMConfig (configPath string ) (* LLMConfig , error ) {
@@ -41,7 +42,7 @@ func CallOllama(
4142 // Build request
4243 req := map [string ]any {
4344 "model" : llmConfig .Model ,
44- "stream" : false ,
45+ "stream" : true ,
4546 "messages" : []map [string ]string {
4647 {"role" : "system" , "content" : basePrompt },
4748 {"role" : "user" , "content" : prompt },
@@ -79,18 +80,32 @@ func CallOllama(
7980 return "" , fmt .Errorf ("ollama API returned %s" , resp .Status )
8081 }
8182
82- // Read & decode response
83- body , err := io .ReadAll (resp .Body )
84- if err != nil {
85- return "" , err
83+ // Decode streamed JSON objects one by one
84+ decoder := json .NewDecoder (resp .Body )
85+
86+ var fullContent strings.Builder
87+ for {
88+ var chunk OllamaResponse
89+ if err := decoder .Decode (& chunk ); err != nil {
90+ if err == io .EOF {
91+ break
92+ }
93+ return "" , err
94+ }
95+
96+ fullContent .WriteString (chunk .Message .Content )
8697 }
8798
88- var ollamaResp OllamaResponse
89- if err := json .Unmarshal (body , & ollamaResp ); err != nil {
90- return "" , err
99+ output := fullContent .String ()
100+
101+ // schema validation (fail job if invalid)
102+ if schema != nil {
103+ if err := validateJSONAgainstSchema (output , schema ); err != nil {
104+ return "" , err
105+ }
91106 }
92107
93- return ollamaResp . Message . Content , nil
108+ return output , nil
94109}
95110
96111func isOllamaRunning () bool {
@@ -221,3 +236,35 @@ func waitForOllama(timeout time.Duration) error {
221236
222237 return fmt .Errorf ("ollama did not start within %s" , timeout )
223238}
239+
240+ func validateJSONAgainstSchema (output string , schema any ) error {
241+ if schema == nil {
242+ return nil
243+ }
244+
245+ schemaBytes , err := json .Marshal (schema )
246+ if err != nil {
247+ return fmt .Errorf ("failed to marshal schema: %w" , err )
248+ }
249+
250+ compiler := jsonschema .NewCompiler ()
251+ if err := compiler .AddResource ("schema.json" , bytes .NewReader (schemaBytes )); err != nil {
252+ return fmt .Errorf ("failed to add schema: %w" , err )
253+ }
254+
255+ compiledSchema , err := compiler .Compile ("schema.json" )
256+ if err != nil {
257+ return fmt .Errorf ("failed to compile schema: %w" , err )
258+ }
259+
260+ var instance any
261+ if err := json .Unmarshal ([]byte (output ), & instance ); err != nil {
262+ return fmt .Errorf ("output is not valid JSON: %w" , err )
263+ }
264+
265+ if err := compiledSchema .Validate (instance ); err != nil {
266+ return fmt .Errorf ("schema validation failed: %w" , err )
267+ }
268+
269+ return nil
270+ }
0 commit comments