chatclient

package
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 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. Other completion reasons remain identifiable as OutputCompletionError. Typed output has only a terminal value; callers that need transport deltas use Client.Stream directly.

NewToolMiddleware covers the deliberately small direct-use path: it advertises a frozen executable Tool set, validates one returned call batch, executes it serially, and performs one follow-up model call. Further tool rounds, retries, concurrency, approval, and durable execution belong to Agent.

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, chatclient.Config{})
	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 rejects a client whose only required capability is absent.
	ErrNilModel = errors.New("chatclient: nil model")
	// ErrNilClient identifies use of a zero-value Client.
	ErrNilClient = errors.New("chatclient: nil client")
	// ErrStreamingUnsupported reports that no explicit or discovered Streamer is
	// available.
	ErrStreamingUnsupported = errors.New("chatclient: streaming unsupported")
)
View Source
var (
	// ErrInvalidOutputFormat identifies a format that cannot define one request
	// contract and terminal decoder.
	ErrInvalidOutputFormat = errors.New("chatclient: invalid output format")

	// ErrInvalidOutput identifies a response that cannot be decoded under the
	// requested contract.
	ErrInvalidOutput = errors.New("chatclient: invalid output")
)
View Source
var ErrInvalidTemplate = errors.New("chatclient: invalid template")

ErrInvalidTemplate identifies a prompt template that cannot render a valid request.

View Source
var (
	// ErrInvalidToolMiddleware identifies an executable set or request that
	// cannot satisfy the middleware's single-batch ownership contract.
	ErrInvalidToolMiddleware = errors.New("chatclient: invalid tool middleware")
)

Functions

func NewToolMiddleware

func NewToolMiddleware(executables ...tool.Tool) (chat.CallMiddleware, error)

NewToolMiddleware keeps direct Client usage useful for one model-requested Tool batch. It advertises a frozen Tool set and validates every invocation before executing any Tool. The accepted batch executes serially, followed by one model call. Runtime failures do not roll back completed Tools. Further rounds and execution policy remain outside this boundary.

Types

type Client

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

Client is an immutable, concurrency-safe composition of chat capabilities and middleware. It does not make an underlying model concurrency safe; callers must still follow the model's concurrency contract.

Call and Stream accept ordinary chat.Request values directly. Client snapshots each request before invoking middleware or a provider, so those layers cannot mutate caller-owned protocol values. A configured Streamer takes precedence; otherwise New discovers the capability on the model. Stream remains lazy and reports unsupported streaming as its single terminal item.

func New

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

New snapshots middleware and discovers streaming only when Config does not provide an explicit Streamer. The returned Client has no mutable defaults.

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 OutputFormat[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, chatclient.Config{})
	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

func (Client) Stream

func (c Client) Stream(ctx context.Context, req *chat.Request) iter.Seq2[*chat.ResponseDelta, error]

Stream returns a lazy sequence; lack of streaming support is yielded as its single terminal error rather than hidden behind a capability probe.

Example
package main

import (
	"context"
	"fmt"
	"iter"

	"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("fallback"), nil
	})
	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.New(model, chatclient.Config{Streamer: 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 textResponse(text string) *chat.Response {
	message := chat.NewAssistantMessage(chat.NewTextPart(text))
	return &chat.Response{Output: &chat.Output{
		Message:      &message,
		FinishReason: chat.FinishReasonStop,
	}}
}

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

type Config

type Config struct {
	Streamer         chat.Streamer
	CallMiddleware   []chat.CallMiddleware
	StreamMiddleware []chat.StreamMiddleware
}

Config describes construction-time Client behavior. Request-specific values belong in chat.Request.

The slices are snapshotted by New, so callers may safely reuse or mutate their input after construction. The first middleware in each slice is the outermost wrapper, matching chat.Wrap and chat.WrapStream.

type JSONSchemaConfig added in v0.13.0

type JSONSchemaConfig struct {
	Name        string
	Description string
}

JSONSchemaConfig supplies the stable identity of a schema derived from T.

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 OutputFormat

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

OutputFormat couples a provider-neutral request contract with the decoder for its complete response. Pass one to Client.Output.

func JSON

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

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

func JSONSchema

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

JSONSchema returns a result format coupled to the named contract derived from T.

func Text

func Text() OutputFormat[string]

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

type Template

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

Template is an immutable, parsed prompt template safe for concurrent use. Parsing fails on missing map keys instead of emitting placeholder text; Require inspects the complete parse tree, including pipelines and branches. Message projections validate the rendered protocol value and preserve media order without retaining per-render variables in Template.

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 rejects templates whose variable paths or functions could make rendering depend on ambient state.

func (*Template) Render

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

func (*Template) Require

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

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)

Directories

Path Synopsis
Package safeguard provides fail-closed input and output screening as Core chat middleware.
Package safeguard provides fail-closed input and output screening as Core chat middleware.

Jump to

Keyboard shortcuts

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