chatclient

package
v0.60.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package chatclient provides direct, optional conveniences around the minimal chat protocols and model capabilities defined by Core. Client.Output applies a provider-neutral OutputFormat and strictly decodes naturally completed text; a caller that already holds a response, such as a managed Agent, reuses that one decode rule through Format.Decode without another model call. Other completion reasons remain identifiable as OutputCompletionError. Typed output has only a terminal value; callers that need transport deltas use StreamClient.Stream with a required streaming dependency. Scope-driven tool batches and continuations use the Agent runtime; direct model calls never dispatch local tools.

Example
package main

import (
	"context"
	"fmt"

	"github.com/Tangerg/scope/core/chat"
	"github.com/Tangerg/scope/core/chatclient"
)

func main() {
	model := chat.ModelFunc(func(context.Context, *chat.Request) (*chat.Response, error) {
		return textResponse("Hello from the model"), nil
	})
	client, err := chatclient.New(model)
	if err != nil {
		panic(err)
	}
	request, err := chat.NewRequest(chat.NewUserMessage(chat.NewTextPart("Hello")))
	if err != nil {
		panic(err)
	}
	request.Options.Model = "example"
	response, err := client.Call(context.Background(), request)
	if err != nil {
		panic(err)
	}
	fmt.Println(response.Text())
}

func textResponse(text string) *chat.Response {
	message := chat.NewAssistantMessage(chat.NewTextPart(text))
	return &chat.Response{Output: &chat.Output{
		Message:      &message,
		FinishReason: chat.FinishReasonStop,
	}}
}
Output:
Hello from the model

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNilModel      = errors.New("chatclient: nil model")
	ErrInvalidClient = errors.New("chatclient: uninitialized client")
)
View Source
var (
	ErrInvalidOutputFormat = errors.New("chatclient: invalid output format")

	ErrInvalidOutput = errors.New("chatclient: invalid output")
)
View Source
var ErrInvalidTemplate = errors.New("chatclient: invalid template")
View Source
var ErrNilStreamer = errors.New("chatclient: nil streamer")

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client validates and snapshots every request before model is called and decodes typed output. Compose middleware with chat.Wrap before New. It is as safe for concurrent use as the underlying model. Streaming has its own construction boundary in StreamClient.

func New

func New(model chat.Model) (Client, error)

func (Client) Call

func (c Client) Call(ctx context.Context, req *chat.Request) (*chat.Response, error)

Call snapshots and validates req before the middleware and model boundary.

func (Client) Output

func (c Client) Output[T any](ctx context.Context, req *chat.Request, format Format[T]) (T, error)

Output asks the provider to enforce format, then strictly decodes naturally completed text. Refusal and other non-successful completion reasons return OutputCompletionError. Media and tool requests cannot become typed values. Output never repairs JSON or injects format instructions into the prompt.

Example
package main

import (
	"context"
	"fmt"

	"github.com/Tangerg/scope/core/chat"
	"github.com/Tangerg/scope/core/chatclient"
)

func main() {
	type answer struct {
		Value int `json:"value"`
	}
	model := chat.ModelFunc(func(context.Context, *chat.Request) (*chat.Response, error) {
		return textResponse(`{"value":42}`), nil
	})
	client, err := chatclient.New(model)
	if err != nil {
		panic(err)
	}
	request, err := chat.NewRequest(chat.NewUserMessage(chat.NewTextPart("What is six times seven?")))
	if err != nil {
		panic(err)
	}
	result, err := client.Output(context.Background(), request, chatclient.JSON[answer]())
	if err != nil {
		panic(err)
	}
	fmt.Println(result.Value)
}

func textResponse(text string) *chat.Response {
	message := chat.NewAssistantMessage(chat.NewTextPart(text))
	return &chat.Response{Output: &chat.Output{
		Message:      &message,
		FinishReason: chat.FinishReasonStop,
	}}
}
Output:
42

type Format added in v0.59.0

type Format[T any] struct {
	// contains filtered or unexported fields
}

Format couples a provider-neutral request contract with the decoder for its complete response. Pass one to Client.Output, or call its Decode on a response obtained elsewhere so every caller shares one decode rule.

func JSON

func JSON[T any]() Format[T]

JSON selects provider-native JSON output and rejects unknown object members when decoding T.

func JSONSchema

func JSONSchema[T any](config JSONSchemaConfig) (Format[T], error)

func Text

func Text() Format[string]

Text selects provider-native text output without post-processing.

func (Format[T]) Contract added in v0.59.0

func (f Format[T]) Contract() chat.OutputFormat

The compiled schema owns both request bytes and response admission; a retained wire copy could drift from the decoder's contract.

func (Format[T]) Decode added in v0.59.0

func (f Format[T]) Decode(response *chat.Response) (T, error)

Decode decodes an already-obtained response into T under the same rule as Client.Output, without a model call, so a caller that already holds a response, such as a managed Agent, reuses one structured-output rule instead of a weaker copy.

type JSONSchemaConfig added in v0.13.0

type JSONSchemaConfig struct {
	Name        string
	Description string
}

type OutputCompletionError added in v0.15.0

type OutputCompletionError struct {
	FinishReason chat.FinishReason
	Refusal      string
}

OutputCompletionError preserves a provider outcome that cannot become the requested typed value. It unwraps to ErrInvalidOutput; callers may inspect FinishReason and Refusal without parsing an error message.

func (*OutputCompletionError) Error added in v0.15.0

func (o *OutputCompletionError) Error() string

func (*OutputCompletionError) Unwrap added in v0.15.0

func (*OutputCompletionError) Unwrap() error

type StreamClient added in v0.17.0

type StreamClient struct {
	// contains filtered or unexported fields
}

StreamClient owns a required streaming capability; compose middleware with chat.WrapStream before NewStreamClient. It is immutable; concurrent use requires a concurrency-safe Streamer. It does not implement chat.Model or require an unused synchronous capability.

func NewStreamClient added in v0.17.0

func NewStreamClient(streamer chat.Streamer) (StreamClient, error)

func (StreamClient) Stream added in v0.17.0

func (s StreamClient) Stream(ctx context.Context, request *chat.Request) iter.Seq2[*chat.ResponseDelta, error]

Stream snapshots the request at invocation. Provider work remains lazy until iteration; stopping iteration synchronously releases the provider resources.

Example
package main

import (
	"context"
	"fmt"
	"iter"

	"github.com/Tangerg/scope/core/chat"
	"github.com/Tangerg/scope/core/chatclient"
)

func main() {
	streamer := chat.StreamerFunc(func(context.Context, *chat.Request) iter.Seq2[*chat.ResponseDelta, error] {
		return func(yield func(*chat.ResponseDelta, error) bool) {
			if !yield(delta("Hello ", ""), nil) {
				return
			}
			yield(delta("stream", chat.FinishReasonStop), nil)
		}
	})
	client, err := chatclient.NewStreamClient(streamer)
	if err != nil {
		panic(err)
	}
	request, err := chat.NewRequest(chat.NewUserMessage(chat.NewTextPart("Hello")))
	if err != nil {
		panic(err)
	}
	for response, streamErr := range client.Stream(context.Background(), request) {
		if streamErr != nil {
			panic(streamErr)
		}
		fmt.Print(response.Text())
	}
	fmt.Println()
}

func delta(text string, reason chat.FinishReason) *chat.ResponseDelta {
	return &chat.ResponseDelta{Parts: []chat.PartDelta{chat.NewTextDelta(text)}, FinishReason: reason}
}
Output:
Hello stream

type Template

type Template struct {
	// contains filtered or unexported fields
}

Template is an immutable, parsed prompt template safe for concurrent use. Rendering fails on missing map keys instead of emitting placeholder text.

Example
package main

import (
	"fmt"

	"github.com/Tangerg/scope/core/chatclient"
)

func main() {
	prompt, err := chatclient.ParseTemplate("Explain {{.Topic}} in one sentence.")
	if err != nil {
		panic(err)
	}
	message, err := prompt.UserMessage(struct{ Topic string }{Topic: "Go interfaces"})
	if err != nil {
		panic(err)
	}
	fmt.Println(message.Text())
}
Output:
Explain Go interfaces in one sentence.

func ParseTemplate

func ParseTemplate(source string) (*Template, error)

ParseTemplate compiles an immutable prompt template from a non-empty source. It rejects a blank source and any parse error, and configures rendering to fail on a missing map key rather than emit placeholder text.

func (*Template) Render

func (t *Template) Render(data any) (string, error)

func (*Template) Require

func (t *Template) Require(names ...string) error

Require counts reachable static field references that retain the Render input's identity across scopes and called templates. It follows break and continue; conditional references count without promising that each Render evaluates them. Computed lookups and function-returned aliases are not inferred.

func (*Template) Source

func (t *Template) Source() string

func (*Template) SystemMessage

func (t *Template) SystemMessage(data any) (chat.Message, error)

func (*Template) UserMessage

func (t *Template) UserMessage(data any, attachments ...*media.Media) (chat.Message, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL