CookbookStream in Go

Stream in Go

stream_options.include_usage is Preview (not available yet). Until it ships, leave it out; the code below simply prints no usage line.

Print a reply as it is generated, using only the standard library.

package main
 
import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
)
 
func main() {
	body, _ := json.Marshal(map[string]any{
		"model":          "Amara S1.2",
		"stream":         true,
		"stream_options": map[string]bool{"include_usage": true},
		"messages":       []map[string]string{{"role": "user", "content": "Explain idempotency keys in three sentences."}},
	})
	req, _ := http.NewRequest("POST", "https://app.syntic.ai/v1/chat/completions", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SYNTIC_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
 
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		var e struct{ Error struct{ Code, Message string } }
		json.NewDecoder(resp.Body).Decode(&e)
		panic(fmt.Sprintf("%d %s: %s", resp.StatusCode, e.Error.Code, e.Error.Message))
	}
 
	scanner := bufio.NewScanner(resp.Body)
	scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "data: ") {
			continue
		}
		payload := strings.TrimPrefix(line, "data: ")
		if payload == "[DONE]" {
			break
		}
		var chunk struct {
			Choices []struct {
				Delta struct{ Content string } `json:"delta"`
			} `json:"choices"`
			Usage *struct{ PromptTokens, CompletionTokens int } `json:"usage"`
			Error *struct{ Code, Message string }             `json:"error"`
		}
		if err := json.Unmarshal([]byte(payload), &chunk); err != nil {
			continue
		}
		if chunk.Error != nil {
			panic("stream interrupted: " + chunk.Error.Message)
		}
		if len(chunk.Choices) > 0 {
			fmt.Print(chunk.Choices[0].Delta.Content)
		}
		if chunk.Usage != nil {
			fmt.Printf("\n[%d in / %d out tokens]\n", chunk.Usage.PromptTokens, chunk.Usage.CompletionTokens)
		}
	}
}

What the code handles

  • Lines that start with data: carry JSON chunks; other lines (blank lines, : keep-alive comments) are skipped.
  • data: [DONE] ends the stream.
  • A chunk with an error object means the stream broke after it started; the text so far may be incomplete, so retry the whole request.
  • The last chunk before [DONE] carries usage when you send stream_options.include_usage.

See Stream format for every field.

Was this page helpful?