Documentation
¶
Overview ¶
Package chatclient provides direct, optional conveniences around the minimal chat protocols and model capabilities defined by Core. Client.Output binds a provider-neutral OutputFormat to the decoder shared by synchronous and streaming generations. Client also discovers provider-owned input-token counting only when the post-middleware model can measure the same prepared request that Call will execute.
NewToolMiddleware covers the deliberately small direct-use path: it advertises a frozen executable Tool set, validates complete calls, executes them serially, and continues the model conversation. Agent control flow, retries, concurrency, approval, and durable execution remain outside Core.
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{Defaults: chat.Options{Model: "example"}})
if err != nil {
panic(err)
}
request, err := chat.NewRequest(chat.NewUserMessage(chat.NewTextPart("Hello")))
if err != nil {
panic(err)
}
response, err := client.Call(context.Background(), request)
if err != nil {
panic(err)
}
fmt.Println(response.Text())
}
func textResponse(text string) *chat.Response {
return response(text, chat.FinishReasonStop)
}
func response(text string, reason chat.FinishReason) *chat.Response {
message := chat.NewAssistantMessage(chat.NewTextPart(text))
return &chat.Response{Output: &chat.Output{
Message: &message,
FinishReason: reason,
}}
}
Output: Hello from the model
Index ¶
- Variables
- func NewToolMiddleware(executables ...tool.Tool) (chat.CallMiddleware, error)
- type Client
- func (c Client) Call(ctx context.Context, req *chat.Request) (*chat.Response, error)
- func (c Client) CountInputTokens(ctx context.Context, req *chat.Request) (int64, error)
- func (c Client) Output[T any](format OutputFormat[T]) Generation[T]
- func (c Client) Stream(ctx context.Context, req *chat.Request) iter.Seq2[*chat.Response, error]
- func (c Client) SupportsInputTokenCounting() bool
- type Config
- type Generation
- type OutputFormat
- type Template
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNilModel = errors.New("chatclient: nil model") ErrNilClient = errors.New("chatclient: nil client") ErrStreamingUnsupported = errors.New("chatclient: streaming unsupported") ErrInputTokenCountingUnsupported = errors.New("chatclient: input token counting unsupported") )
var ( ErrInvalidOutputFormat = errors.New("chatclient: invalid output format") ErrInvalidOutput = errors.New("chatclient: invalid output") )
var ErrInvalidTemplate = errors.New("chatclient: invalid template")
var 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 the small case where model-requested Tools only need schema validation and serial execution. Agent control flow and execution policy deliberately 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, defaults, 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 (Client) CountInputTokens ¶
CountInputTokens snapshots and resolves defaults exactly like Call, then asks the model's optional provider-owned counter to measure the complete input.
func (Client) Output ¶
func (c Client) Output[T any](format OutputFormat[T]) Generation[T]
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(chatclient.JSON[answer]()).
Call(context.Background(), request)
if err != nil {
panic(err)
}
fmt.Println(result.Value)
}
func textResponse(text string) *chat.Response {
return response(text, chat.FinishReasonStop)
}
func response(text string, reason chat.FinishReason) *chat.Response {
message := chat.NewAssistantMessage(chat.NewTextPart(text))
return &chat.Response{Output: &chat.Output{
Message: &message,
FinishReason: reason,
}}
}
Output: 42
func (Client) Stream ¶
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.Response, error] {
return func(yield func(*chat.Response, error) bool) {
if !yield(response("Hello ", ""), nil) {
return
}
yield(response("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 {
return response(text, chat.FinishReasonStop)
}
func response(text string, reason chat.FinishReason) *chat.Response {
message := chat.NewAssistantMessage(chat.NewTextPart(text))
return &chat.Response{Output: &chat.Output{
Message: &message,
FinishReason: reason,
}}
}
Output: Hello stream
func (Client) SupportsInputTokenCounting ¶
SupportsInputTokenCounting reports whether CountInputTokens observes the same prepared provider request as Call. Middleware must explicitly preserve that capability after any request transformation.
type Config ¶
type Config struct {
Defaults chat.Options
Streamer chat.Streamer
CallMiddleware []chat.CallMiddleware
StreamMiddleware []chat.StreamMiddleware
}
Config describes construction-time Client behavior. Request-specific values belong in chat.Request.
The slices and Defaults 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 Generation ¶
type Generation[T any] struct { // contains filtered or unexported fields }
Generation is an immutable Client with one typed output contract bound to it. Call and Stream both return the complete decoded result; use Client.Stream directly when individual response chunks are required.
type OutputFormat ¶
type OutputFormat[T any] struct { // contains filtered or unexported fields }
OutputFormat couples a provider-neutral request contract with the decoder that consumes its response stream. Pass one to Client.Output.
func JSON ¶
func JSON[T any]() OutputFormat[T]
func JSONSchema ¶
func JSONSchema[T any](name string) (OutputFormat[T], error)
JSONSchema returns a result format coupled to the named contract derived from T.
func Text ¶
func Text() OutputFormat[string]
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.