Files
sm-golem-plugin/README.md
Jakub Zych cd3f0f6c1c feat: golem15.golem plugin with the OpenAI-compatible adapter and AIService.Send
- 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
2026-10-03 22:41:49 +02:00

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 ./...
```