387 lines
15 KiB
Markdown
387 lines
15 KiB
Markdown
# Local Backend Convenience Implementation Plan
|
|
|
|
**Status:** Complete.
|
|
|
|
## Purpose
|
|
|
|
This document is the decision-complete implementation plan for
|
|
[local backend convenience](local-backend.md). It is written for a coding
|
|
agent that will implement each stage in order.
|
|
|
|
The feature roadmap owns the motivation, consumer paths, policy choices,
|
|
compatibility requirements, non-goals, and target end state. This document
|
|
owns the exact proposed API, file-level changes, implementation sequence, test
|
|
ownership, documentation work, validation commands, and completion gates.
|
|
|
|
## Implementation Rules
|
|
|
|
- Complete the stages in order. Keep the root package compiling and its
|
|
focused tests passing at every stage boundary.
|
|
- Preserve unrelated working-tree changes. The feature roadmap may already be
|
|
uncommitted when implementation begins; retain it.
|
|
- Follow every policy under `docs/policy/`, the task-specific reading guide in
|
|
`docs/development.md`, and the accepted behavior in
|
|
`local-backend.md`.
|
|
- Keep the API in the root `promptkit` package. Do not add a public or internal
|
|
package for this feature.
|
|
- Implement the helper as a transparent constructor for the existing
|
|
`Backend` type. Do not add a second backend representation or bypass
|
|
`WithBackend`.
|
|
- Leave normalization, validation, copying, registry construction, capacity
|
|
defaulting, and duplicate detection in their existing owners.
|
|
- Do not add environment-variable discovery, package-global registration,
|
|
implicit local defaults, model selection, profile construction, or support
|
|
for non-OpenAI-compatible transports.
|
|
- Keep tests lean and behavior-focused. Do not duplicate the existing
|
|
registry and concurrency test matrices merely because the constructor
|
|
reaches those mechanisms.
|
|
- Update exact GoDoc with the exported declarations. Update current-state
|
|
consumer guidance only after the corresponding API is implemented.
|
|
- Do not update release notes, create a release, change a module version, or
|
|
tag a commit as part of this work.
|
|
|
|
## Fixed Design
|
|
|
|
### Exported API
|
|
|
|
Add these declarations to `backends.go` in package `promptkit`:
|
|
|
|
```go
|
|
// BackendLocal is the conventional ID used by LocalBackend. It is not a
|
|
// built-in or reserved backend and must be registered with WithBackend.
|
|
const BackendLocal = "local"
|
|
|
|
// LocalBackend returns a Backend for a conventional local OpenAI-compatible
|
|
// endpoint.
|
|
func LocalBackend(endpoint string, concurrencyLimit int) Backend {
|
|
return Backend{
|
|
ID: BackendLocal,
|
|
Endpoint: endpoint,
|
|
ConcurrencyLimit: concurrencyLimit,
|
|
}
|
|
}
|
|
```
|
|
|
|
The exact GoDoc may be wrapped or expanded for clarity, but it must own and
|
|
communicate all of these contract points:
|
|
|
|
- `BackendLocal` is the case-sensitive conventional ID `"local"`;
|
|
- it is neither built in nor reserved;
|
|
- calling `LocalBackend` does not register anything;
|
|
- the returned value must be supplied through `WithBackend`;
|
|
- `endpoint` and `concurrencyLimit` are copied into the corresponding fields
|
|
without normalization or validation;
|
|
- `APIKeyEnv`, `ExtraParams`, and `QueueCapacity` retain their zero values; and
|
|
- normal `NewEngine` backend validation and concurrency semantics apply after
|
|
registration.
|
|
|
|
Keep `BackendOpenRouter` unchanged. `BackendLocal` must not alias an internal
|
|
registry constant because the internal registry has no special local-backend
|
|
identity or behavior.
|
|
|
|
Place `BackendLocal` near `BackendOpenRouter`, and place `LocalBackend` after
|
|
the `Backend` declaration and before `WithBackend`. This keeps the conventional
|
|
IDs, configured value, convenience constructor, and registration option
|
|
discoverable in one file.
|
|
|
|
### Constructor Semantics
|
|
|
|
`LocalBackend` is a pure struct constructor. Its complete behavior is
|
|
equivalent to the keyed literal shown above.
|
|
|
|
In particular, the constructor must not:
|
|
|
|
- trim or parse the endpoint;
|
|
- reject blank endpoints or negative limits;
|
|
- choose a default limit;
|
|
- assign an API-key environment variable;
|
|
- allocate an empty `ExtraParams` map;
|
|
- assign a queue-capacity pointer;
|
|
- read process environment;
|
|
- mutate package-global or engine state; or
|
|
- call `WithBackend` itself.
|
|
|
|
Deferred validation is intentional. It keeps one validation path for all
|
|
`Backend` values: `WithBackend` copies the public value into engine options,
|
|
and `NewEngine` constructs the validated immutable registry. A positive limit
|
|
with a nil queue continues to select the existing default queue capacity of
|
|
1024; zero continues to mean unlimited; a negative value continues to make
|
|
`NewEngine` fail with `ErrInvalidConfig`.
|
|
|
|
The returned `Backend` remains an ordinary caller-owned value. Consumers can
|
|
modify it before passing it to `WithBackend`, although task-oriented
|
|
documentation should direct materially customized configurations to an
|
|
explicit keyed `Backend` literal.
|
|
|
|
### Identity And Compatibility
|
|
|
|
Do not add `"local"` to the internal built-in or reserved ID set. A consumer
|
|
must be able to register either:
|
|
|
|
```go
|
|
promptkit.LocalBackend(endpoint, limit)
|
|
```
|
|
|
|
or:
|
|
|
|
```go
|
|
promptkit.Backend{
|
|
ID: promptkit.BackendLocal,
|
|
Endpoint: endpoint,
|
|
}
|
|
```
|
|
|
|
through the existing `WithBackend` option. Both forms participate in ordinary
|
|
duplicate-ID detection. Existing consumers that already use the literal ID
|
|
`"local"` remain source- and behavior-compatible.
|
|
|
|
No existing `Backend`, `WithBackend`, profile, registry, execution-target, or
|
|
capacity semantics change. Do not modify `internal/backend`,
|
|
`internal/capacity`, `internal/domain`, `engine.go`, `profiles.go`, or
|
|
`types.go` for this feature.
|
|
|
|
### Test Ownership
|
|
|
|
The root external-package contract suite in `public_contract_test.go` owns the
|
|
new public behavior. Add one focused test named:
|
|
|
|
```go
|
|
func TestLocalBackendConstructsAndRegistersConventionalBackend(t *testing.T)
|
|
```
|
|
|
|
The test must:
|
|
|
|
1. call `promptkit.LocalBackend` with a test endpoint and a positive,
|
|
test-owned concurrency limit;
|
|
2. compare the returned value with this complete expected value:
|
|
|
|
```go
|
|
promptkit.Backend{
|
|
ID: promptkit.BackendLocal,
|
|
Endpoint: endpoint,
|
|
ConcurrencyLimit: limit,
|
|
}
|
|
```
|
|
|
|
A whole-value comparison is appropriate here because the exact zero-value
|
|
fields are part of this small public constructor's contract.
|
|
3. register that returned value with `WithBackend`;
|
|
4. add an in-memory profile whose `BackendID` is
|
|
`promptkit.BackendLocal`;
|
|
5. construct an engine through the existing contract-test prompt fixture;
|
|
6. call `Prepare`; and
|
|
7. assert that the prepared result exposes `BackendLocal` as the selected
|
|
backend and the supplied endpoint as the effective endpoint.
|
|
|
|
This single test protects the realistic compatibility risks: accidental field
|
|
defaults, a changed conventional ID, failure to compose with `WithBackend`,
|
|
and accidental treatment of `"local"` as reserved. It also demonstrates that
|
|
the helper uses the existing backend/profile path.
|
|
|
|
Do not add separate tests for blank endpoints, malformed endpoints, negative
|
|
limits, queue defaulting, duplicate IDs, engine isolation, runtime capacity,
|
|
or caller mutation. Those mechanisms are unchanged and already have tests at
|
|
their owning boundaries. Do not add internal-package tests for this root
|
|
facade constructor.
|
|
|
|
### Consumer Documentation
|
|
|
|
Update `docs/consumers/pkg-promptkit.md` after the API exists. Keep Go
|
|
declarations and GoDoc as the exact API owner; the guide should help consumers
|
|
choose a workflow and link to `backends.go` for precise semantics.
|
|
|
|
Restructure the backend guidance to present these paths in increasing order of
|
|
configuration:
|
|
|
|
1. **Endpoint-only profile.** Show a small in-memory `Profile` with
|
|
`Endpoint` and `Model`. Explain that this is the simplest choice when only
|
|
one profile needs the endpoint and shared backend identity or capacity
|
|
policy is unnecessary.
|
|
2. **Local convenience constructor.** Show
|
|
`WithBackend(promptkit.LocalBackend("http://localhost:8000/v1", 2))`
|
|
together with a profile using
|
|
`BackendID: promptkit.BackendLocal`. Explain briefly that the helper is
|
|
explicit, is not pre-registered, does not read environment variables, and
|
|
leaves the queue capacity at the existing default for a positive limit.
|
|
3. **Complete backend value.** Preserve an advanced example using a keyed
|
|
`Backend` literal for needs such as `APIKeyEnv`, an explicit
|
|
`QueueCapacity`, extra parameters, a custom ID, or multiple local
|
|
endpoints. Use a custom ID other than `"local"` in that example so the
|
|
distinction from the conventional helper is clear.
|
|
|
|
Keep the existing backend-routing, selected-backend identity, concurrency,
|
|
credential, and error guidance unless a small wording adjustment is required
|
|
to make the new decision path coherent. Avoid repeating the complete field
|
|
contract or registry validation rules from GoDoc.
|
|
|
|
Do not add a new maintained example, README section, format-reference entry,
|
|
integration-contract change, internal-document change, or release note. The
|
|
consumer-guide snippets are sufficient for this small convenience API, and
|
|
none of those other documents owns the affected task or contract.
|
|
|
|
## Stage 1: Add The Public Constructor And Contract Test
|
|
|
|
**Status:** Complete.
|
|
|
|
### Objective
|
|
|
|
Add the smallest public API that expresses the accepted local-backend
|
|
convention and protect its compatibility through the root public boundary.
|
|
|
|
### Implementation Prompt
|
|
|
|
1. Re-read `docs/development.md`, all files under `docs/policy/`,
|
|
`local-backend.md`, `backends.go`, the backend-related portion of
|
|
`public_contract_test.go`, and the existing backend/concurrency GoDoc before
|
|
editing.
|
|
2. Confirm the working tree and preserve the uncommitted roadmaps and any
|
|
unrelated consumer changes.
|
|
3. Add the untyped exported string constant `BackendLocal = "local"` to
|
|
`backends.go` without changing `BackendOpenRouter`.
|
|
4. Add `LocalBackend(endpoint string, concurrencyLimit int) Backend` to
|
|
`backends.go` using the exact keyed-literal implementation in the fixed
|
|
design.
|
|
5. Write complete GoDoc for both declarations. Make their conventional,
|
|
explicit, non-built-in, non-reserved, and deferred-validation semantics
|
|
unambiguous.
|
|
6. Add
|
|
`TestLocalBackendConstructsAndRegistersConventionalBackend` to
|
|
`public_contract_test.go` exactly as specified under Test Ownership. Reuse
|
|
the existing contract prompt fixture rather than adding a fixture or test
|
|
helper.
|
|
7. Do not edit internal packages. If the constructor appears to require an
|
|
internal change, stop and reconcile the implementation with the fixed
|
|
transparent-constructor design instead.
|
|
|
|
### Focused Validation
|
|
|
|
Run:
|
|
|
|
```sh
|
|
gofmt -w backends.go public_contract_test.go
|
|
go test . -run '^TestLocalBackendConstructsAndRegistersConventionalBackend$'
|
|
go test .
|
|
go vet .
|
|
go build .
|
|
git diff --check
|
|
```
|
|
|
|
Inspect the diff and confirm that this stage changes only `backends.go`,
|
|
`public_contract_test.go`, and the already-present roadmap files.
|
|
|
|
### Completion Gate
|
|
|
|
Stage 1 is complete when:
|
|
|
|
- the exported constant and constructor match the fixed API;
|
|
- the constructor returns only the three specified non-zero fields;
|
|
- the helper registers through the ordinary `WithBackend` path;
|
|
- `"local"` remains a valid consumer registration rather than a reserved
|
|
built-in;
|
|
- the focused public-contract test passes; and
|
|
- the root package test, vet, build, formatting, and whitespace checks pass.
|
|
|
|
## Stage 2: Publish Consumer Guidance And Validate The Repository
|
|
|
|
**Status:** Complete.
|
|
|
|
### Objective
|
|
|
|
Make the simplest suitable local-endpoint configuration easy to discover,
|
|
confirm the complete change across the repository, and close the temporary
|
|
roadmaps.
|
|
|
|
### Implementation Prompt
|
|
|
|
1. Re-read the implemented declarations and GoDoc before describing them.
|
|
2. Update `docs/consumers/pkg-promptkit.md` according to the three-path
|
|
structure under Consumer Documentation.
|
|
3. Keep examples illustrative, minimal, secret-free, and consistent with the
|
|
implemented declarations. Link precise semantics to `backends.go` rather
|
|
than duplicating its field-by-field contract.
|
|
4. Follow every added or changed Markdown link and confirm its target exists.
|
|
Confirm that all local repository-relative links in
|
|
`local-backend.md`, this plan, and the changed consumer guide resolve.
|
|
5. Run the complete maintainer validation sequence below.
|
|
6. Inspect the final diff for accidental internal behavior changes,
|
|
generated artifacts, credentials, local workspace files, or module
|
|
replacements.
|
|
7. After every completion gate passes, set both stage statuses, this plan's
|
|
status, and the status in `local-backend.md` to `Complete`. Do not retire or
|
|
remove the roadmaps in the implementation change; roadmap retirement
|
|
follows implementation review.
|
|
|
|
### Full Validation
|
|
|
|
Run the complete sequence from `docs/development.md`:
|
|
|
|
```sh
|
|
go test ./...
|
|
go test -race ./...
|
|
go vet ./...
|
|
go build ./...
|
|
go run ./examples/go-library/prepare
|
|
gofmt -l $(git ls-files '*.go')
|
|
git diff --check
|
|
```
|
|
|
|
The formatting command must produce no paths. Follow every added or changed
|
|
Markdown link and confirm its target and heading exist.
|
|
|
|
Also inspect:
|
|
|
|
```sh
|
|
git status --short
|
|
git diff --stat
|
|
git diff
|
|
```
|
|
|
|
Confirm that:
|
|
|
|
- production changes are limited to the root convenience API;
|
|
- no internal backend, registry, capacity, profile, or engine behavior
|
|
changed;
|
|
- `BackendLocal` is conventional and consumer-registerable, not built in or
|
|
reserved;
|
|
- `LocalBackend` performs no validation, normalization, environment lookup,
|
|
registration, allocation, or hidden default selection;
|
|
- the returned `Backend` leaves `QueueCapacity` nil so existing positive-limit
|
|
defaulting remains owned by the registry;
|
|
- existing endpoint-only profiles and complete custom backends remain
|
|
documented and supported;
|
|
- current-state documentation describes only the now-implemented API and
|
|
links to the canonical GoDoc for exact semantics;
|
|
- no `go.work`, `go.work.sum`, local module replacement, credential,
|
|
generated binary, or unrelated change was introduced; and
|
|
- the feature and implementation roadmaps contain no unresolved work marked
|
|
complete.
|
|
|
|
### Completion Gate
|
|
|
|
Stage 2 is complete when every target-end-state item in
|
|
`local-backend.md` is implemented, the consumer guide clearly presents all
|
|
three configuration paths, all complete validation commands pass, all changed
|
|
links resolve, and the roadmap statuses accurately report completion.
|
|
|
|
## Implementation Handoff
|
|
|
|
The implementation handoff should report:
|
|
|
|
- the new `BackendLocal` and `LocalBackend` public API;
|
|
- that the helper remains explicit and composes with the ordinary backend
|
|
registry;
|
|
- the focused public-contract coverage added;
|
|
- the consumer-guide decision path added;
|
|
- the complete validation commands and results; and
|
|
- any unrelated working-tree changes that were preserved.
|
|
|
|
Do not claim that a local backend is built in, pre-registered, configured from
|
|
environment variables, or assigned a default model or concurrency limit.
|
|
|
|
## Open Questions
|
|
|
|
None. The feature roadmap and this plan fix the exported API, constructor
|
|
semantics, identity treatment, compatibility behavior, test boundary,
|
|
documentation ownership, implementation sequence, validation gates, and
|
|
non-goals required for implementation.
|