Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,13 @@ for {
log.Fatal(err)
}
acc.AddChunk(chunk)
for _, delta := range chunk.GetOutputs() {
fmt.Print(delta.GetDelta().GetContent())
for _, out := range chunk.GetOutputs() {
fmt.Print(out.GetDelta().GetContent())
}
}
if outs := acc.Response().GetOutputs(); len(outs) > 0 {
fmt.Println("\n\nFinal answer:", outs[0].GetMessage().GetContent())
}
full := acc.Response()
fmt.Println("\n\nFinal answer:", full.GetOutputs()[0].GetMessage().GetContent())
```

Prefer a callback? Drain a fresh stream with the high-level helper instead of the manual `Recv` loop:
Expand Down
23 changes: 15 additions & 8 deletions examples/streaming/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,22 @@ func main() {
log.Fatalf("create client: %v", err)
}
defer client.Close()
req := &xaiapiv1.GetCompletionsRequest{

stream, err := client.Responses.CreateStream(ctx, &xaiapiv1.GetCompletionsRequest{
Model: "grok-4.3",
Messages: []*xaiapiv1.Message{messages.UserText("Stream a haiku about databases.")},
}
stream, err := client.Responses.CreateStream(ctx, req)
})
if err != nil {
log.Fatalf("start stream: %v", err)
}

// Drain the stream, printing tokens as they arrive and accumulating the full
// response. Recv returns io.EOF at the end; any other error is a real gRPC
// status and must not be swallowed.
acc := responses.NewAccumulator()
it := stream.Iterator(ctx)
for {
chunk, ok, err := it.Next()
if errors.Is(err, io.EOF) || !ok {
chunk, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
Expand All @@ -48,6 +51,10 @@ func main() {
fmt.Print(out.GetDelta().GetContent())
}
}
full := acc.Response()
fmt.Printf("\n\nFinal answer: %s\n", full.GetOutputs()[0].GetMessage().GetContent())

outs := acc.Response().GetOutputs()
if len(outs) == 0 {
log.Fatal("stream completed without producing any output")
}
fmt.Printf("\n\nFinal answer: %s\n", outs[0].GetMessage().GetContent())
}
15 changes: 15 additions & 0 deletions integration/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build integration

package integration

import (
"os"
"testing"
)

func requireKey(t *testing.T) {
t.Helper()
if os.Getenv("XAI_API_KEY") == "" {
t.Skip("set XAI_API_KEY to run integration tests")
}
}
74 changes: 74 additions & 0 deletions integration/streaming_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//go:build integration

package integration

import (
"context"
"errors"
"io"
"strings"
"testing"

"github.com/modelrelay/xai-go"
xaiapiv1 "github.com/modelrelay/xai-go/gen/xai/api/v1"
"github.com/modelrelay/xai-go/messages"
"github.com/modelrelay/xai-go/responses"
)

const acceptModel = "grok-4.3"

func acceptReq() *xaiapiv1.GetCompletionsRequest {
return &xaiapiv1.GetCompletionsRequest{
Model: acceptModel,
Messages: []*xaiapiv1.Message{messages.UserText("Count to five.")},
}
}

// TestStreamingAcceptance_ExamplePattern mirrors examples/streaming EXACTLY
// (fresh client, Iterator, manual AddChunk), across many fresh connections,
// and reports the terminal (ok,err) whenever a run yields 0 outputs — the
// case where the example panics. Captures the error the example's loop swallows.
func TestStreamingAcceptance_ExamplePattern(t *testing.T) {
requireKey(t)
const runs = 30
fails := 0
for i := 0; i < runs; i++ {
func() {
ctx := context.Background()
client, err := xai.NewClient(ctx)
if err != nil {
t.Fatalf("run %d: new client: %v", i, err)
}
defer client.Close()
stream, err := client.Responses.CreateStream(ctx, acceptReq())
if err != nil {
t.Fatalf("run %d: CreateStream: %v", i, err)
}
acc := responses.NewAccumulator()
it := stream.Iterator(ctx)
n := 0
var termOK bool
var termErr error
for {
chunk, ok, nerr := it.Next()
termOK, termErr = ok, nerr
if errors.Is(nerr, io.EOF) || !ok {
break
}
if nerr != nil {
break
}
n++
acc.AddChunk(chunk)
}
outs := acc.Response().GetOutputs()
if len(outs) == 0 || strings.TrimSpace(outs[0].GetMessage().GetContent()) == "" {
fails++
t.Errorf("run %d FLAKE: chunks=%d outputs=%d term(ok=%v err=%v)", i, n, len(outs), termOK, termErr)
}
}()
}
if fails > 0 {
t.Fatalf("%d/%d runs hit the 0-output flake", fails, runs)
}
}