- Prompt, Payload and AIResponse value objects ported from Golem15.Golem - OpenAIAdapter: chat completions mapping with PHP's response_format, max_completion_tokens and reasoning_effort rules - AIService.Send through fetchguard (TrustedMode for operator models, PublicOnlyMode otherwise) with PHP's failure messages - config defaults for the SSRF allowlist and the AI timeout, lang, README
101 lines
5.1 KiB
Markdown
101 lines
5.1 KiB
Markdown
# golem15.golem
|
|
|
|
The shared AI plugin for SummerCMS applications: provider adapters for the Anthropic Messages API and the OpenAI-compatible chat completions API, prompts and AI responses, and an AI service that sends them through the framework's guarded outbound client.
|
|
|
|
```go
|
|
import _ "git.golem15.com/golem15/sm-golem-plugin"
|
|
```
|
|
|
|
Module `git.golem15.com/golem15/sm-golem-plugin`, package `golem`, plugin ID `golem15.golem`.
|
|
|
|
## Overview
|
|
|
|
The plugin is a compiled SummerCMS plugin ported from the WinterCMS `Golem15.Golem` plugin. Its `init` registers it with the framework, so an application loads it by listing it in `summer.yaml`; `summer build` then emits the blank import above into the application's `plugins.gen.go`. Another plugin that sends AI requests lists `golem15.golem` in its `Requires()` and imports the `classes/...` packages.
|
|
|
|
The adapters are hand-written JSON mappings with no vendor SDK. Every request goes through a `fetchguard.Client`: a config marked `Trusted` (a model an operator configured) runs in `fetchguard.TrustedMode`, every other config (a key a user or an organisation supplied) in `fetchguard.PublicOnlyMode`, which refuses private and reserved addresses at dial time.
|
|
|
|
The plugin's config keys, lang namespace and plugin ID are a contract shared by every application that mounts it: change them only in a backwards-compatible way.
|
|
|
|
## Features
|
|
|
|
- **Prompts.** `valueobjects.Prompt` is an immutable request: a system prompt, user and assistant messages, messages with base64 images (`AddUserMessageWithImage`, `AddUserMessageWithImages`) or an uploaded file id (`AddUserMessageWithFileID`), a model override and extra options in insertion order. `ToAPIPayload` builds the OpenAI chat completions body as a `valueobjects.Payload`, a JSON object that keeps its key order.
|
|
- **Responses.** `valueobjects.AIResponse` carries the content, token usage and the provider's decoded answer (`Raw`, which keeps `finish_reason` and `stop_reason`) or the failure message. `Err` holds the Go error behind a failure for logs and tests and is never shown to a user.
|
|
- **OpenAI-compatible adapter.** `providers.OpenAIAdapter` posts to `<base>/chat/completions` with `Authorization: Bearer`, drops `response_format`, maps `max_tokens` to `max_completion_tokens` and adds `reasoning_effort: low` for o-series and gpt-5 models. It also serves compatible providers through their base URL. `providers.ForName` picks the adapter by name.
|
|
- **AI service.** `services.AIService.Send` applies the model's system prompt when the prompt has none and posts the prompt to the config's chat endpoint. It returns the WinterCMS plugin's failure messages: no model configured, no API key, no connection, and an answer that is not JSON. `services.ForApp` returns the application's shared service.
|
|
|
|
## Usage
|
|
|
|
Mount the plugin in an application as a git submodule at `plugins/golem15/golem`:
|
|
|
|
```sh
|
|
git submodule add git@git.golem15.com:golem15/sm-golem-plugin.git plugins/golem15/golem
|
|
```
|
|
|
|
Add it to the application's `go.work` and `go.mod`:
|
|
|
|
```text
|
|
use ./plugins/golem15/golem
|
|
|
|
require git.golem15.com/golem15/sm-golem-plugin v0.0.0
|
|
|
|
replace git.golem15.com/golem15/sm-golem-plugin => ./plugins/golem15/golem
|
|
```
|
|
|
|
List it in `summer.yaml` before the plugins that require it and run `summer build`:
|
|
|
|
```yaml
|
|
module: git.golem15.com/golem15/acme
|
|
binary: acme
|
|
plugins:
|
|
- id: golem15.golem
|
|
module: git.golem15.com/golem15/sm-golem-plugin
|
|
- id: acme.blog
|
|
module: git.golem15.com/golem15/acme/plugins/acme/blog
|
|
```
|
|
|
|
The plugin's own `go.mod` replaces the framework with `../../../../summercms.go`, so it expects the framework checkout next to the application checkout.
|
|
|
|
A plugin sends a prompt with a user's own key:
|
|
|
|
```go
|
|
svc := services.ForApp(app)
|
|
prompt := valueobjects.NewPrompt().
|
|
WithSystem("You catalog physical items.").
|
|
AddUserMessage("List the items.")
|
|
res := svc.Send(ctx, prompt, &services.ModelConfig{
|
|
Adapter: "openai",
|
|
APIKey: key,
|
|
Model: "gpt-4o-mini",
|
|
})
|
|
if !res.Success {
|
|
return errors.New(res.Error)
|
|
}
|
|
```
|
|
|
|
## API reference
|
|
|
|
- `golem`: `Plugin`, the `golem15.golem` plugin.
|
|
- `classes/valueobjects`: `Prompt`, `NewPrompt`, `Message`, `ContentPart`, `ImageURL`, `FileRef`, `Image`, `Payload`, `NewPayload`, `AIResponse`, `Success`, `Failure`.
|
|
- `classes/providers`: `Adapter`, `OpenAIAdapter`, `ForName`, `OpenAIBaseURL`, `AnthropicBaseURL`.
|
|
- `classes/services`: `ModelConfig`, `DefaultModelID`, `AIService`, `NewAIService`, `ForApp`, `ConfigTimeoutSeconds`, `DefaultTimeout`.
|
|
|
|
## Configuration
|
|
|
|
Defaults live in `config/config.yaml` and are merged under `golem15.golem.*`.
|
|
|
|
| Key | Default | Meaning |
|
|
|-----|---------|---------|
|
|
| `golem15.golem.ssrf.allowed_hosts` | `oaidalleapiprodscus.blob.core.windows.net`, `.openai.com` | Hosts an untrusted base URL or an AI-returned image URL may use. A leading dot matches any subdomain. |
|
|
| `golem15.golem.ai.timeout_seconds` | `120` | Timeout of every AI provider request. |
|
|
|
|
## Dependencies
|
|
|
|
- The SummerCMS framework, `git.golem15.com/golem15/summercms` (modules `backpack`, `compass`, `fetchguard`, `pact`, `party`).
|
|
- GORM, as pinned in `go.mod`.
|
|
|
|
## Testing
|
|
|
|
```sh
|
|
go test ./...
|
|
```
|