A gRPC-native Go client for xAI's Grok API, generated directly from xAI's official protobuf definitions. It speaks Grok's native gRPC surface with first-class, strongly-typed Go types and streaming — chat, responses, embeddings, images, documents, tokenize, models, and auth.
Unofficial. A community client, not affiliated with, authorized, or endorsed by xAI. "xAI" and "Grok" are trademarks of their respective owner. Generated from xAI's public protobuf definitions (
xai-org/xai-proto) and maintained on a best-effort basis by ModelRelay. Licensed under Apache-2.0.
third_party/xai-protois tracked as a git submodule.- Run
make proto(optionally overridingBUF) to regenerategen/xai/api/v1. - Generation is scoped to
xai/api(the public inference API). xAI'smanagement_apiandshared(billing, analytics) surfaces are intentionally excluded — seePROTO_PATHin theMakefile. - Buf managed mode ensures go package paths live under
github.com/modelrelay/xai-go/gen/xai/api/v1.
ctx := context.Background()
client, err := xai.NewClient(ctx, xai.WithAPIKey(os.Getenv("XAI_API_KEY")))
if err != nil {
log.Fatal(err)
}
defer client.Close()
resp, err := client.Chat.GetCompletion(ctx, &xaiapiv1.GetCompletionsRequest{
Model: "grok-4.3",
Messages: []*xaiapiv1.Message{
{Role: xaiapiv1.MessageRole_ROLE_USER, Content: []*xaiapiv1.Content{
{Content: &xaiapiv1.Content_Text{Text: "Hello Grok!"}},
}},
},
})
if err != nil {
log.Fatal(err)
}
if len(resp.GetOutputs()) == 0 {
log.Fatal("no outputs returned")
}
fmt.Println(resp.GetOutputs()[0].GetMessage().GetContent())stream, err := client.Responses.CreateStream(ctx, &xaiapiv1.GetCompletionsRequest{
Model: "grok-4.3",
ReasoningEffort: xaiapiv1.ReasoningEffort_EFFORT_LOW.Enum(),
Messages: []*xaiapiv1.Message{
{Role: xaiapiv1.MessageRole_ROLE_USER, Content: []*xaiapiv1.Content{
{Content: &xaiapiv1.Content_Text{Text: "Stream something fancy."}},
}},
},
})
if err != nil {
log.Fatal(err)
}
acc := responses.NewAccumulator()
for {
chunk, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
acc.AddChunk(chunk)
for _, out := range chunk.GetOutputs() {
fmt.Print(out.GetDelta().GetContent())
}
}
outs := acc.Response().GetOutputs()
if len(outs) == 0 {
log.Fatal("stream produced no output")
}
fmt.Println("\n\nFinal answer:", outs[0].GetMessage().GetContent())For reasoning models, reasoning_effort is the primary latency lever. Choose
EFFORT_LOW, EFFORT_MEDIUM, or EFFORT_HIGH according to the workload. The
enum also includes EFFORT_NONE for models that support disabling reasoning,
but it is not universally accepted: always-reasoning models such as grok-4.5
reject it, making EFFORT_LOW their minimal setting. If the field is omitted,
the server default is EFFORT_MEDIUM.
Prefer a callback? Create a stream and drain it with the high-level helper instead of the manual Recv loop (a stream can only be consumed once):
stream, err := client.Responses.CreateStream(ctx, &xaiapiv1.GetCompletionsRequest{/* same request as above */})
if err != nil {
log.Fatal(err)
}
err = stream.ForEachChunk(ctx, func(chunk *xaiapiv1.GetChatCompletionChunk) error {
fmt.Printf("\nChunk %s", chunk.GetId())
return nil
})
if err != nil && !errors.Is(err, io.EOF) {
log.Fatal(err)
}completion_tokens counts generated text tokens only. reasoning_tokens is a
separate, disjoint counter: it is not included in completion_tokens, but it is
included in total_tokens along with prompt tokens.
A live grok-4.5 response with a one-token visible answer reported
completion_tokens=1 and roughly 1,100 reasoning tokens, with the reasoning
tokens contributing to total_tokens rather than completion_tokens.
As a short accounting example, a response with prompt_tokens=24,
completion_tokens=1, and reasoning_tokens=1,100 has 1 visible output token,
1,101 generated tokens, and total_tokens=1,125:
usage := resp.GetUsage()
contentTokens := usage.GetCompletionTokens() // visible text only
reasoningTokens := usage.GetReasoningTokens() // disjoint from contentTokens
generatedTokens := responses.GeneratedTokens(usage) // content + reasoning
allTokens := usage.GetTotalTokens() // prompt + content + reasoningUse completion_tokens when measuring visible content, and total_tokens when
accounting for all tokens processed by the request. Do not add
reasoning_tokens to total_tokens; it is already included there.
Most Grok clients consume the OpenAI-compatible HTTP endpoint, where a stream is
a sequence of Server-Sent Events — data: {json}\n\n frames you split,
JSON-decode, and terminate on a [DONE] sentinel. This client speaks Grok's
native gRPC surface instead, which changes what streaming feels like from Go:
- Typed chunks, no frame parsing. Every
stream.Recv()returns a fully-typed*GetChatCompletionChunk— length-prefixed protobuf framing and decoding are handled for you. No SSE delimiter splitting, partial-JSON reassembly, or[DONE]sentinel to special-case. - Structured end-of-stream. A stream ends with a gRPC status: success is a
clean
io.EOF, and a mid-stream failure arrives as a typedstatus.Code(via HTTP/2 trailers) — not a truncated body or an in-band error event you have to sniff for. - Real cancellation. Cancel the
contextand the underlying HTTP/2 stream is reset, signaling the server to stop generating; deadlines propagate the same way. - One connection, many streams. HTTP/2 multiplexes concurrent requests over a single connection, without per-call connection setup.
For plain one-directional token streaming, SSE works fine and is simpler in the browser — a chat completion doesn't exercise gRPC's bidirectional streaming. The win here is consuming the stream as typed, framed, status-terminated messages from Go, with fewer parsing edge cases.
deferred, err := client.Responses.StartDeferred(ctx, &xaiapiv1.GetCompletionsRequest{/* ... */})
if err != nil {
log.Fatal(err)
}
resp, err := client.Responses.PollDeferredCompletion(ctx, deferred.GetRequestId(), 0)
if err != nil {
log.Fatal(err)
}
if len(resp.GetOutputs()) == 0 {
log.Fatal("no outputs returned")
}
fmt.Println(resp.GetOutputs()[0].GetMessage().GetContent())client.Embeddings.Embed– generate text/image embeddings.client.Images.GenerateImage– create images from prompts.client.Documents.Search– query uploaded document collections.client.Tokenize.TokenizeText– tokenize using Grok models.client.Models.*– list or inspect available models.client.Auth.GetAPIKeyInfo– inspect current API key metadata.client.Batch.*– create and manage batch jobs and their results.client.Files.*– upload, list, retrieve, and delete files (with streaming upload/content).client.Video.*– generate and extend videos, and poll deferred results.
Chat requests now include MaxTurns to bound agentic tool-calling loops server-side; set req.MaxTurns on GetCompletionsRequest when you need a hard stop.
tracker := responses.NewToolCallTracker()
stream, err := client.Responses.CreateStream(ctx, req)
if err != nil {
log.Fatal(err)
}
for {
chunk, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
for _, event := range tracker.ConsumeChunk(chunk) {
if event.ArgumentsDelta != "" {
fmt.Printf("tool %s args += %s\n", event.CallID, event.ArgumentsDelta)
}
if event.Complete {
fmt.Printf("tool %s ready: %s\n", event.CallID, event.Call.GetFunction().GetArguments())
}
}
}messages.UserText/AssistantText/SystemTexthelp build chat inputs with minimal boilerplate.documents.CollectionSource+documents.SearchRequestsimplify document search configuration.tools.FunctionTool,tools.WebSearchTool, etc. build validated tool definitions for Requests payloads.search.Parameters+search.WebSource/XSource/RssSourcemake it easy to configure live search without manual proto juggling.documents.ToolResponseMessageturns document search matches into ROLE_TOOL messages for follow-up calls.toolruntime.Registryconverts completed tool events into ROLE_TOOL messages using registered handlers.encrypted.DecryptResponse/DecryptChunkhelp you plug in custom decryptors whenuse_encrypted_contentis enabled.Responses.RetrieveAndDeleteandRequireStoredRequestssimplify stored-response lifecycles.responses.GeneratedTokensadds the disjoint text and reasoning counters without double-counting prompt tokens.config.Configoffers a declarative data map for client instantiation (e.g., load from YAML/JSON and pass toConfig.NewClient).
fnTool, err := tools.FunctionTool("lookup_weather", "Fetch weather", map[string]any{
"type": "object",
"properties": map[string]any{
"city": map[string]any{"type": "string"},
},
})
if err != nil {
log.Fatal(err)
}
webSource, err := search.WebSource(search.WebAllow("example.com"))
if err != nil {
log.Fatal(err)
}
params, err := search.Parameters(
search.WithMode(xaiapiv1.SearchMode_ON_SEARCH_MODE),
search.WithSources(webSource),
)
if err != nil {
log.Fatal(err)
}
req := &xaiapiv1.GetCompletionsRequest{
Model: "grok-4.3",
Messages: []*xaiapiv1.Message{messages.UserText("What's up in Example City?")},
Tools: []*xaiapiv1.Tool{fnTool},
SearchParameters: params,
}
matches, err := client.Documents.Search(ctx, documents.SearchRequest("Example City history", documents.CollectionSource("city-archive")))
if err != nil {
log.Fatal(err)
}
toolMsg, err := documents.ToolResponseMessage("call_weather_docs", matches.GetMatches(), 3)
if err != nil {
log.Fatal(err)
}
_ = toolMsg // send as ROLE_TOOL message when replying to the model.
registry := toolruntime.NewRegistry()
registry.Register("lookup_weather", func(ctx context.Context, fn *xaiapiv1.FunctionCall) (any, error) {
return map[string]any{"echo": fn.GetArguments()}, nil
})
msg, err := registry.Handle(ctx, responses.ToolCallEvent{
CallID: "call_weather_docs",
Complete: true,
Call: &xaiapiv1.ToolCall{
Tool: &xaiapiv1.ToolCall_Function{Function: &xaiapiv1.FunctionCall{Name: "lookup_weather", Arguments: "{\"city\":\"Example\"}"}}},
},
})
if err != nil {
log.Fatal(err)
}
_ = msg // append ROLE_TOOL message into your conversation- Use
tools.WithBearerToken/tools.WithAuthorizationto set MCP auth headers. - Add custom headers via
tools.WithExtraHeaderwhen MCP servers require proprietary metadata. - Prefer the search builders above so you never mix mutually exclusive fields (allowed/excluded domains, handles, etc.), keeping requests valid.
- When storing responses (
store_messages=true) useresponses.RequireStoredRequeststo setprevious_response_idconsistently, andResponses.RetrieveAndDeleteto clean up stored history once consumed. - Load multi-environment settings via
config.Configso deployments can express addresses/API keys in plain data rather than scatteringWith*calls.
examples/streamingdemonstrates a streaming chat session, draining the stream withstream.Recv. Run withgo run ./examples/streamingafter settingXAI_API_KEY.examples/tool_callshows how to react to tool-call events, issue document searches, and feed ROLE_TOOL messages back into the conversation.- Integration tests live under
integration/and are guarded by theintegrationbuild tag. Run them withXAI_API_KEY=... go test -tags=integration ./integration/.... - Tutorials:
docs/tutorials/streaming.md– streaming basics with iterators.docs/tutorials/tool-doc-search.md– combines streaming, tool calls, and document search.docs/tutorials/config.md– shows how to load settings from JSON/YAML.
- Guides:
docs/guides/responses.md– full walkthrough covering unary vs streaming, tools, encrypted content, deferred/stored responses.
XAI_API_KEY– required unlessxai.WithAPIKeyis provided.XAI_GRPC_ADDRESS– optional override for the gRPC endpoint.
(Default user and user-agent are set in code via xai.WithDefaultUser / xai.WithUserAgent.)
make proto– regenerates Go stubs from the pinned proto definitions. DefaultBUFcommand can be overridden if the buf binary is unavailable.make tidy– runsgo fmtacross the repo andgo mod tidy.make ci– convenience alias forgofmtcheck +go test ./...+buf lint(also run in CI).