Documentation
¶
Overview ¶
Package answer is the native answer engine: the bounded plan → search → read → rank → synthesize → cite → follow-up loop behind POST /v1/ask when a `mode` (search|news|research|deep) selects web grounding. It is a clean-room Hanzo implementation (NOT derived from any AGPL reference).
ONE HOME, ONE DOOR. This package is the loop's only home; /v1/ask is its only door. `mode` is a VALUE handed to that door — "deep research" is a mode, never a second route. The package registers no routes of its own: clients/ask owns the door and delegates web modes here.
THE FIVE VALUES, one home each:
plan → plan() → []string (≤ maxQueries, best-effort) search → websearch.Search → []websearch.Result (in-process, keyless) rank → rank() → []Source (dedupe URL+host, relevance, cap) read → read() → []Source (enriched) (the ONE crawl, ai/object) synthesize → synthesize() → string (streamed through the Sink)
BOUNDED. ≤3 LLM calls (1 plan + 1 synthesis + 1 follow-up), ≤maxQueries search passes, ≤maxRead page fetches, a 90s wall clock, and a token ceiling past which the optional follow-up call is skipped. It is never an open agent loop.
METERED ONCE. Every answer debits the resolved payer through the per-org ResourceMeter (Base.Bill) — the ONE revenue debit, since the in-process AI path runs on the binary's balance-exempt M2M identity.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Engine ¶
Engine is the answer engine value: the shared Base (logger + the ONE per-org meter) plus the AI plane it synthesizes with and the deployment's default model. The mounting package constructs it per request from what it already holds — the engine owns no state of its own.
func (Engine) Run ¶
Run is the bounded loop, parameterized by mode — the ONE code path for search/news/research/deep. It emits the SearchEvent envelope through out, then meters the caller ONCE. A failed step degrades (fewer sources, snippets instead of pages, an honest note) rather than aborting the stream.
func (Engine) Serve ¶
Serve answers one web-mode /v1/ask: resolve the billing subject → GATE the balance → run the bounded loop, streaming the SearchEvent envelope (SSE) or returning it as one JSON object. The caller is already gated as a validated principal by the door; here we additionally resolve the payer and gate spend BEFORE any work, so an out-of-funds caller gets a clean 402, never a half stream.
type Page ¶
Page is the crawl seam's value: the URL a page was fetched for and its LLM-ready markdown. The answer engine depends on THIS, never on the crawler's own result shape — which is what lets a test inject a fake crawl.
type Params ¶
type Params struct {
// contains filtered or unexported fields
}
Params is the fully-owned per-request plan handed to Run(): safe to use after the Ctx is recycled (SSE) and retained by the async meter.
type Request ¶
type Request struct {
Mode string // search|news|research|deep
Sources []string // @hints appended to the web query: web,news,academic,github,reddit,x
Model string // override the synthesis model
Stream *bool // force SSE (else Accept: text/event-stream / ?stream=1)
Language string // web-search language (BCP-47-ish)
MaxSources int
MaxQueries int
FollowUps *bool // default true
System string
}
Request is the answer engine's slice of the /v1/ask body. Every field is optional; mode selects the profile and the rest bound or override it.
type Sink ¶
type Sink interface {
// contains filtered or unexported methods
}
Sink receives the loop's envelope events — one method per SearchEvent variant, which keeps the loop declarative. Implemented by sseSink (streaming) and bufferSink (JSON).
There is no fail(): the loop never hard-fails. A down model degrades to an honest answer + a done frame (and is not billed), a down crawl degrades to snippets — so the client ALWAYS gets a terminal frame. The union's `error` variant is the CLIENT's (a transport failure it observes), never the server's.
type Source ¶
type Source struct {
URL string `json:"url"`
Title string `json:"title"`
Snippet string `json:"snippet"`
Engine string `json:"engine,omitempty"`
Favicon string `json:"favicon"`
}
Source is one web source backing an answer — the @hanzo/ai SearchSource shape, field for field. It is the ONE source value in this package: search produces it, read() enriches only its Snippet (never its identity), synthesis grounds on it, and the wire emits it verbatim in the `sources` and `done` frames.