jpf

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 10 Imported by: 7

README

Go Report Card Go Ref

Providing essential building blocks and robust LLM interaction interfaces, jpf enables you to craft custom AI solutions without the bloat.

Features

  • Retry and Feedback Handling: Resilient mechanisms for retrying tasks and incorporating feedback into interactions.
  • Customizable Models: Seamlessly integrate LLMs from multiple providers using unified interfaces.
  • Token Usage Tracking: Stay informed of API token consumption for cost-effective development.
  • Stream Responses: Keep your users engaged with responses that are streamed back as they are generated.
  • Easy-to-use Caching: Reduce the calls made to models by composing a caching layer onto an existing model.
  • Out-of-the-box Logging: Simply add logging messages to your models, helping you track down issues.
  • Industry Standard Context Management: All potentially slow interfaces support Go's context.Context for timeouts and cancellation.
  • Rate Limit Management: Compose models together to set local rate limits to prevent API errors.
  • Tool / Function calling: Let your models call tools with static or streamed requests.
  • MIT License: Use the code for anything, anywhere, for free.

Installation

Install jpf in your Go project via:

go get github.com/JoshPattman/jpf

Learn more about JPF in the Core Concepts section.

Quickstart

There are multiple examples available in the examples directory.

Build a model

  • A model is capable of responding to a set of messages, and are the core engine behind all of your AI features.
  • Models are built through composition, adding functionality that runs on your machine.
func BuildModel() jpf.Model {
	// Create a new gpt-4o model attached to the OpenAI API.
	model := models.NewRemote(
		models.OpenAI, // Defines the API format and the default URL (URL can be overriden)
		"gpt-4o", // Model name on API
		os.Getenv("OPENAI_KEY"), // API key
		models.WithTemperature(0.5) // Optional params - many more are supported
	)
	// Locally rate limit the model calls to 1 every 5 seconds
	model = models.RateLimit(model, rate.NewLimiter(rate.Every(time.Second*5), 1))
	// Make the model retry non-200 requests up to 5 times
	model = models.Retry(model, 5)
	// Cache model requests in memory - file and database are also supported
	cache := caches.NewRAM()
	model = models.Cache(model, cache)
	return model
}

Build a pipeline

  • A pipeline is a wrapper around a model that takes and returns structured data.
  • Pipelines may retry using various strategies when a validation error (attempting to parse the output) occurs.
  • Pipelines are particularly useful when making one / a few calls to an LLM to perform a fixed task, for example:
    • Translate some text
    • Answer a question about some text
    • Summarise a document
// Define the structured data to provide to the pipeline
type TaskInput struct {
	Name string
}
// Define the structured data to read from the pipeline
type TaskOutput struct {
	IsCelebrity bool `json:"is_celebrity"`
}

// Define a custom validator that will not accpet that santa is not a celebrity
type CustomValidator struct{}

func (c *CustomValidator) ValidateParsedResponse(in TaskInput, out TaskOutput) error {
	if strings.ToLower(in.Name) == "santa" && !out.IsCelebrity {
		return errors.New("santa is a celebrity")
	}
	return nil
}

func BuildPipeline(model jpf.Model) jpf.Pipeline[TaskInput, TaskOutput] {
	// Encode the data to system/user prompt pair, where both are a text/template
	encoder := encoders.NewTemplate[TaskInput]("The user will give you a name. Respond with a json object with a single key, 'is_celebrity'.", "{{ .Name }}")
	// Parse the output message into a struct using json
	parser := parsers.NewJson[TaskOutput]()
	// Only provide the text between { and } to the json parser - cut off extra stuff like backticks
	parser = parsers.SubstringJsonObject(parser)
	// When retrying, will provide fedback by simply formatting the error
	feedback := feedbacks.NewErrString()
	// Create a pipeline that retries up to 5 times on parsing or validation errors, providing feedback as developer
	return pipelines.NewFeedbackRetry(
		encoder,
		parser,
		&CustomValidator{}, // This is allowed to be nil if no further validation is required
		feedback,
		model,
		jpf.DeveloperRole,
		5,
	)
}

Use the pipeline

func IsCelebrity(name string) (bool, error) {
	// Realistically in production code, you would not build the models here,
	// instead you would inject them (or at least inject the builders),
	// as this allows for higher testability and customisability.
	model := BuildModel()
	pipeline := BuildPipeline(model)
	// Calling a pipeline gives result, usage, and error
	result, usage, err := pipeline.Call(context.Background(), TaskInput{name})
	if err != nil {
		return false, err
	}
	fmt.Println(usage)
	return result.IsCelebrity, nil
}

FAQ

  • I want to change my model's temperature/structured output/output tokens/... after I have built it!
    • The intention is to provide functions that need to use an LLM with a builder function instead of a built object. This way, you can use the builder function multiple times with different parameters.
    • Take a look at the examples to see this concept.
    • This design decision was made as it prevents you from injecting unnecessary LLM-related data into business logic.
  • Where are the agents?
    • Agents are built on top of LLMs, but this package is designed for LLM handling, so it lives at the level below agents.
    • Take a look at JChat or react to see how you can build an agent on top of JPF.
  • Why does this not support MCP tools on the OpenAI API / Other advanced API features?
    • Relying on API features like MCP tools (and full API agents), or vector stores is not ideal for two reasons: (a) it makes it harder to move between API/model providers (b) it gives you less flexibility and control.
    • These features are not particularly hard to add locally, so you should aim to do so to ensure your application is as robust as possible to API change.
    • If a way to abstract the advanced feature such that it becomes provider-agnostic is found, adding it into JPF will be considered.

Author

Developed by Josh Pattman. Learn more at GitHub.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidResponse = errors.New("llm produced an invalid response")
)

Functions

This section is empty.

Types

type AssistantMessage added in v0.10.0

type AssistantMessage struct {
	Content   string
	ToolCalls []ToolCall
}

AssistantMessage represents a message from the model to the user.

func (AssistantMessage) Eq added in v0.10.0

func (m AssistantMessage) Eq(other Message) bool

func (AssistantMessage) String added in v0.10.0

func (m AssistantMessage) String() string

type DeveloperMessage added in v0.10.0

type DeveloperMessage struct {
	Content string
}

DeveloperMessage represents a message from the developer (basically system) to the model.

func (DeveloperMessage) Eq added in v0.10.0

func (m DeveloperMessage) Eq(other Message) bool

func (DeveloperMessage) String added in v0.10.0

func (m DeveloperMessage) String() string

type Encoder added in v0.9.0

type Encoder[T any] interface {
	BuildInputMessages(T) ([]Message, error)
}

Encoder encodes a structured piece of data into a set of messages for an LLM.

type FeedbackGenerator added in v0.6.0

type FeedbackGenerator interface {
	FormatFeedback(AssistantMessage, error) string
}

FeedbackGenerator takes an error and converts it to a piece of text feedback to send to the LLM.

type ImageAttachment added in v0.7.0

type ImageAttachment struct {
	Source image.Image
}

ImageAttachment is an image that is attached as additional information to a message.

func (*ImageAttachment) ToBase64Encoded added in v0.7.0

func (i *ImageAttachment) ToBase64Encoded(useCompression bool) (string, error)

type Message

type Message interface {
	String() string
	Eq(Message) bool
	// contains filtered or unexported methods
}

Message is a sum type of the different messages that can be sent to Models.

type Model

type Model interface {
	// Responds to a set of input messages.
	Respond(context.Context, []Message, ...ModelResponseOpt) (ModelResponse, error)
}

Model defines an interface to an LLM.

type ModelLogger added in v0.7.0

type ModelLogger interface {
	ModelLog(ModelLoggingInfo) error
}

ModelLogger specifies a method of logging a call to a model.

type ModelLoggingInfo added in v0.6.0

type ModelLoggingInfo struct {
	InputMessages []Message
	ResultMessage AssistantMessage
	Usage         Usage
	Err           error
	Duration      time.Duration
}

ModelLoggingInfo contains all information about a model interaction to be logged. It includes input messages, output messages, usage statistics, and any error that occurred.

type ModelResponse added in v0.8.0

type ModelResponse struct {
	// The response to the input messages.
	Message AssistantMessage
	// The usage of making this call.
	// This may be the sum of multiple LLM calls.
	Usage Usage
}

func (ModelResponse) IncludingUsage added in v0.8.0

func (r ModelResponse) IncludingUsage(u Usage) ModelResponse

Utility to include another usage object in this response object

func (ModelResponse) OnlyUsage added in v0.8.0

func (r ModelResponse) OnlyUsage() ModelResponse

Utility to allow you to return the usage but 0 value messages when an error occurs.

type ModelResponseCache added in v0.6.0

type ModelResponseCache interface {
	GetCachedResponse(ctx context.Context, salt string, inputs []Message) (bool, AssistantMessage, error)
	SetCachedResponse(ctx context.Context, salt string, inputs []Message, out AssistantMessage) error
}

type ModelResponseKwargs added in v0.10.0

type ModelResponseKwargs struct {
	Streamer     ModelStreamer
	OutputFormat any
	ToolSchemas  []ToolSchema
}

func GetModelResponseKwargs added in v0.10.0

func GetModelResponseKwargs(opts ...ModelResponseOpt) ModelResponseKwargs

type ModelResponseOpt added in v0.10.0

type ModelResponseOpt func(*ModelResponseKwargs)

func WithOutputFormat added in v0.10.0

func WithOutputFormat(format any) ModelResponseOpt

Set the output format (structured output). This should be a struct, as it will be processed into the correct API model output format by the model itself.

func WithStreamResponse added in v0.9.0

func WithStreamResponse(streamer ModelStreamer) ModelResponseOpt

Stream the model's response to the streamer. This will override any previously set streamers.

func WithToolSchemas added in v0.10.0

func WithToolSchemas(schemas ...ToolSchema) ModelResponseOpt

Add the provided tool schemas (additive, not replace) to the model. If there is at least one tool schema, tool calling will be enabled, so the model needs to support tool calling in this case.

type ModelStreamer added in v0.10.0

type ModelStreamer interface {
	OnMessageBegin()
	OnMessageText(text string)
	OnMessageReset()
}

type Parser added in v0.9.0

type Parser[U any] interface {
	ParseResponseText(string) (U, error)
}

Parser converts the LLM response into a structured piece of output data. When the LLM response is invalid, it should return ErrInvalidResponse (or an error joined on that).

type Pipeline added in v0.9.0

type Pipeline[T, U any] interface {
	Call(context.Context, T) (PipelineResponse[U], error)
}

Pipeline transforms input of type T into output of type U using an LLM. It handles the encoding of input, interaction with the LLM, and decoding of output.

type PipelineResponse added in v0.10.0

type PipelineResponse[U any] struct {
	// The parsed and validated result.
	Result U
	// The usage of making this call.
	// This may be the sum of multiple LLM calls.
	Usage Usage
}

func (PipelineResponse[U]) IncludingUsage added in v0.10.0

func (r PipelineResponse[U]) IncludingUsage(u Usage) PipelineResponse[U]

Utility to include another usage object in this response object

func (PipelineResponse[U]) OnlyUsage added in v0.10.0

func (r PipelineResponse[U]) OnlyUsage() PipelineResponse[U]

Utility to allow you to return the usage but 0 value messages when an error occurs.

type SystemMessage added in v0.10.0

type SystemMessage struct {
	Content string
}

SystemMessage represents a message from the system to the model, to set up its task, personality.

func (SystemMessage) Eq added in v0.10.0

func (m SystemMessage) Eq(other Message) bool

func (SystemMessage) String added in v0.10.0

func (m SystemMessage) String() string

type ToolArg added in v0.10.0

type ToolArg struct {
	Name        string
	Description string
	Type        ToolArgType
	Required    bool
}

type ToolArgType added in v0.10.0

type ToolArgType uint8
const (
	ToolArgInt ToolArgType = iota
	ToolArgFloat
	ToolArgString
)

type ToolCall added in v0.10.0

type ToolCall struct {
	ID   string
	Tool string
	Args map[string]any
}

type ToolResultMessage added in v0.10.0

type ToolResultMessage struct {
	CallID string
	Result string
}

ToolResultMessage represents a result from calling a tool.

func (ToolResultMessage) Eq added in v0.10.0

func (m ToolResultMessage) Eq(other Message) bool

func (ToolResultMessage) String added in v0.10.0

func (m ToolResultMessage) String() string

type ToolSchema added in v0.10.0

type ToolSchema struct {
	Name        string
	Description string
	Args        []ToolArg
}

type Usage

type Usage struct {
	InputTokens     int
	OutputTokens    int
	SuccessfulCalls int
	FailedCalls     int
}

Usage defines how many tokens were used when making calls to LLMs.

func (Usage) Add

func (u Usage) Add(u2 Usage) Usage

type UserMessage added in v0.10.0

type UserMessage struct {
	Content string
	Images  []ImageAttachment
}

UserMessage represents a message from the user to the model.

func (UserMessage) Eq added in v0.10.0

func (m UserMessage) Eq(other Message) bool

func (UserMessage) String added in v0.10.0

func (m UserMessage) String() string

type Validator added in v0.9.0

type Validator[T, U any] interface {
	ValidateParsedResponse(T, U) error
}

Validator takes a parsed LLM response and validates it against the input. When the LLM response is invalid, it should return ErrInvalidResponse (or an error joined on that).

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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