imagine

package module
v0.0.0-...-3750862 Latest Latest
Warning

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

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 13 Imported by: 0

README

imagine-rag

Go Reference CI Go Report Card

Official Go client SDK for the imagine.bo RAG platform.

The frontend never calls the RAG server directly. All communication goes through this library:

Frontend  →  Your Backend (uses this library)  →  RAG Server

Installation

go get github.com/imagineBo/imagine-rag

Requires Go 1.24 or later. No external dependencies — only the standard library.


Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    imagine "github.com/imagineBo/imagine-rag"
)

func main() {
    ctx := context.Background()

    // 1. Create a client — WithBaseURL is required.
    //    Reuse this client across your whole application.
    client := imagine.New(
        "sk-your-api-key",
        imagine.WithBaseURL("https://your-server.com"),
    )

    // 2. Create a knowledge base and set it as the default.
    kb, err := client.CreateKB(ctx, "Product Docs", imagine.KBOpts{})
    if err != nil {
        log.Fatal(err)
    }
    client.SetActiveKB(kb.KBID)

    // 3. Ingest a PDF — processing is async on the server.
    result, err := client.IngestFile(ctx, "./manual.pdf", imagine.IngestOpts{})
    if err != nil {
        log.Fatal(err)
    }

    // 4. Wait until the file is ready (polls every 2 s by default).
    file, err := client.WaitForFile(ctx, result.FileID, 0)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("status:", file.Status) // "ready"

    // 5. Ask a question — tokens stream back as they are generated.
    stream, err := client.Query(ctx, "What is the return policy?", imagine.QueryOpts{})
    if err != nil {
        log.Fatal(err)
    }
    for chunk := range stream {
        if chunk.Err != nil {
            log.Fatal(chunk.Err)
        }
        fmt.Print(chunk.Text)
        if chunk.Done {
            break
        }
    }
}

How the Library Calls the Server

Every request the library makes goes to the RAG server over HTTP. You never need to handle this yourself — it is all done internally.

Request format
POST /v1/<endpoint>
Authorization: Bearer <api-key>
Content-Type: application/json
X-Imagine-SDK: go/1.0.0
Response envelope

Every successful response from the server is wrapped in:

{ "success": true, "data": <payload> }

The library unwraps this automatically. When you call client.QuerySync(...) you get back a QueryResponse directly — not the envelope.

Error envelope

Every error response from the server looks like:

{ "success": false, "error": { "code": "NOT_FOUND", "message": "..." } }

The library parses this into a typed *imagine.Error so you can handle it with the helper functions (IsNotFound, IsUnauthorized, etc.).

Streaming (SSE)

For client.Query(...) the library sends "stream": true and reads the response as Server-Sent Events:

data: {"text":"Hello","done":false,"sources":[]}
data: {"text":" world","done":false,"sources":[]}
data: {"text":"","done":true,"sources":[{"file_id":"...","score":0.91,...}]}
data: [DONE]

Each event is decoded into a StreamChunk and sent to the channel you receive.


Client Configuration

WithBaseURL is requiredNew() panics without it. All other options are optional:

client := imagine.New(
    "sk-your-api-key",
    imagine.WithBaseURL("https://your-server.com"), // required
    imagine.WithTimeout(120 * time.Second),          // optional
    imagine.WithKB("kb_abc123"),                     // optional
)
Option Required Default Description
WithBaseURL(url) yes Your hosted imagine.bo server URL
WithTimeout(d) no 30s HTTP request timeout (0 = no timeout, useful for streams)
WithHTTPClient(hc) no built-in Replace the underlying http.Client (proxies, test mocks)
WithKB(kbID) no "" Default knowledge base used when KBID is not set in opts

Function Reference

Knowledge Bases

Manage isolated document collections.

// Create a new KB.
kb, err := client.CreateKB(ctx, "Product Docs", imagine.KBOpts{
    Description: "All public documentation",
})
client.SetActiveKB(kb.KBID) // set as default

// List all KBs.
kbs, err := client.ListKBs(ctx)

// Delete a KB and everything inside it.
err = client.DeleteKB(ctx, kb.KBID)

Ingest

Add content to a knowledge base. All calls return immediately — processing is async.

// Upload a local file (PDF, DOCX, PPTX, TXT, MD, CSV).
result, err := client.IngestFile(ctx, "./docs/manual.pdf", imagine.IngestOpts{})

// Fetch and process a single web page.
result, err = client.IngestURL(ctx, "https://docs.acme.com/faq", imagine.IngestOpts{})

// Send a raw string directly.
result, err = client.IngestText(ctx, "Our policy is 30 days...", "return-policy", imagine.IngestOpts{})

// Submit multiple URLs at once — server processes them in parallel.
results, err := client.IngestURLBatch(ctx, []string{
    "https://docs.acme.com/page1",
    "https://docs.acme.com/page2",
}, imagine.IngestOpts{})

Files

Inspect and remove ingested documents.

// Poll until processing finishes (default interval: 2 s).
file, err := client.WaitForFile(ctx, result.FileID, 0)

// Check status without blocking.
file, err = client.GetFileStatus(ctx, fileID)

// List all files in a KB.
files, err := client.ListFiles(ctx, kbID)

// Delete a file and all its indexed chunks.
err = client.DeleteFile(ctx, fileID)

Crawl

Spider an entire website into a knowledge base.

// Start a crawl — returns immediately with a job ID.
job, err := client.CrawlURL(ctx, "https://docs.acme.com", imagine.CrawlOpts{
    MaxPages:   200,
    MaxDepth:   4,
    SameDomain: true,
})

// Wait until the crawl finishes (polls every 5 s).
// Terminal statuses: "done", "failed", "cancelled"
done, err := client.WaitForCrawl(ctx, job.JobID, 5*time.Second)
fmt.Printf("%d pages ingested\n", done.PagesDone)

// Check progress without blocking.
status, err := client.GetCrawlStatus(ctx, job.JobID)

// Stop a running crawl (already-ingested pages are kept).
err = client.CancelCrawl(ctx, job.JobID)

CrawlJob.Status values:

Value Meaning
queued Accepted, waiting for a crawler worker
running Actively fetching pages
done All pages processed successfully
failed Crawler encountered an unrecoverable error
cancelled Stopped by CancelCrawl

Query

Ask questions against your knowledge base.

// Streaming — tokens arrive as they are generated.
stream, err := client.Query(ctx, "How do I reset my password?", imagine.QueryOpts{
    TopK:    5,
    History: history, // []imagine.Message from your DB
})
for chunk := range stream {
    if chunk.Err != nil { log.Fatal(chunk.Err) }
    fmt.Print(chunk.Text)
    if chunk.Done { break }
}

// Synchronous — waits for the full answer.
resp, err := client.QuerySync(ctx, "What is the refund policy?", imagine.QueryOpts{})
fmt.Println(resp.Answer)
fmt.Println(resp.Sources) // document chunks used to answer

// Collect a stream into a QueryResponse.
stream, _ = client.Query(ctx, message, opts)
resp, err = imagine.CollectStream(stream)

Sessions

The server stores the full message history per session, so users can return to a previous chat at any time. See Session Workflows below for detailed frontend patterns.

// Create a session when a user starts a new chat.
session, err := client.CreateSession(ctx, imagine.SessionOpts{Name: "Alice"})

// Load all messages for a session (chronological order).
history, err := client.GetHistory(ctx, session.SessionID)

// Query with session — server appends the new message pair automatically.
resp, err := client.QuerySync(ctx, userMessage, imagine.QueryOpts{
    History:   history,
    SessionID: session.SessionID,
})

// List all sessions for this tenant (for a "past chats" sidebar).
sessions, err := client.ListSessions(ctx)

// Re-fetch session metadata.
session, err = client.GetSession(ctx, sessionID)

// Delete a session and all its messages.
err = client.DeleteSession(ctx, sessionID)

Session Workflows

This section covers every session-related action a frontend user can take.

1. User starts a new chat

Call once when the user opens a fresh conversation. Save the returned SessionID in your own database — you will need it for every subsequent operation on this chat.

session, err := client.CreateSession(ctx, imagine.SessionOpts{
    Name:     "Support chat",                       // shown in the sidebar
    Metadata: map[string]string{"user_id": "u_42"}, // your own data
})
if err != nil {
    // handle error
}

// Persist to your DB:
//   INSERT INTO chats (session_id, user_id, name) VALUES (?, ?, ?)
//   session.SessionID, userID, session.Name

Then send the first message immediately:

stream, err := client.Query(ctx, firstMessage, imagine.QueryOpts{
    SessionID: session.SessionID, // server stores user message + reply
})
// stream tokens to the frontend...

2. User opens the "Past Chats" sidebar

Fetch all sessions for the current tenant, ordered by most-recently-updated first. Render each one as a row in the sidebar.

sessions, err := client.ListSessions(ctx)
if err != nil {
    // handle error
}

// Each imagine.Session contains:
//   session.SessionID  — use as the key when the user clicks a row
//   session.Name       — display label
//   session.UpdatedAt  — "last active" timestamp
//   session.CreatedAt  — creation timestamp
//   session.Metadata   — any extra data you stored at creation time

for _, s := range sessions {
    fmt.Printf("%s  |  %s  |  last active: %s\n",
        s.SessionID, s.Name, s.UpdatedAt.Format("Jan 2, 2006"))
}

3. User opens an older session

Two calls: one to get session metadata (name, timestamps) and one to load the full message history. Render the messages in chronological order, then let the user continue the conversation.

// Step A — re-fetch metadata (name may have been updated, UpdatedAt reflects last activity).
session, err := client.GetSession(ctx, sessionID)
if err != nil {
    if imagine.IsNotFound(err) {
        // session was deleted — show an error in the UI
    }
    // handle other errors
}

// Step B — load the full message history.
history, err := client.GetHistory(ctx, sessionID)
if err != nil {
    // handle error
}

// history is []imagine.Message in chronological order:
//   []{Role:"user", Content:"..."}, {Role:"assistant", Content:"..."}}, ...
//
// Render each message in your chat UI.
for _, msg := range history {
    fmt.Printf("[%s] %s\n", msg.Role, msg.Content)
}

4. User sends a message in an existing session

Load history first (so the model has context), then call Query or QuerySync with both History and SessionID. The server automatically appends the new user message and the assistant reply to the session — no extra call needed.

// Load the latest history before each message.
history, err := client.GetHistory(ctx, sessionID)
if err != nil {
    // handle error
}

// Stream the answer back to the frontend.
stream, err := client.Query(ctx, userMessage, imagine.QueryOpts{
    SessionID: sessionID, // persists the new pair to the session
    History:   history,   // gives the model conversation context
    TopK:      5,
})
if err != nil {
    // handle error
}

for chunk := range stream {
    if chunk.Err != nil {
        // stream broke — handle error
        break
    }
    // send chunk.Text to the frontend (WebSocket, SSE, etc.)
    fmt.Print(chunk.Text)
    if chunk.Done {
        // chunk.Sources contains the document chunks used to answer
        fmt.Printf("\nsources: %d\n", len(chunk.Sources))
        break
    }
}

If you don't need streaming (e.g. a REST endpoint returning a full answer), use QuerySync instead:

resp, err := client.QuerySync(ctx, userMessage, imagine.QueryOpts{
    SessionID: sessionID,
    History:   history,
})
// resp.Answer — full answer string
// resp.Sources — document chunks used

5. User deletes a session

Permanently removes the session and all its stored messages. Show a confirmation dialog in the UI before calling this.

err := client.DeleteSession(ctx, sessionID)
if err != nil {
    if imagine.IsNotFound(err) {
        // already deleted — treat as success
        return nil
    }
    // handle other errors
}

// Remove from your local state / DB, redirect to a new chat.

Complete session lifecycle (reference)
User action                     Library call
──────────────────────────────────────────────────────────────────
New chat button clicked      →  CreateSession(ctx, opts)
                                  └─ save SessionID to your DB

Sidebar opens                →  ListSessions(ctx)
                                  └─ render session list

User clicks a past chat      →  GetSession(ctx, sessionID)
                                  └─ show name / timestamps
                             →  GetHistory(ctx, sessionID)
                                  └─ render message bubbles

User sends a message         →  GetHistory(ctx, sessionID)   [refresh]
                             →  Query(ctx, msg, QueryOpts{
                                    SessionID: sessionID,
                                    History:   history,
                                })
                                  └─ stream tokens to UI

User deletes the chat        →  DeleteSession(ctx, sessionID)
                                  └─ remove from sidebar

Error Handling

All non-2xx responses are returned as *imagine.Error. The server sends errors in this shape:

{ "success": false, "error": { "code": "NOT_FOUND", "message": "..." } }

The library parses this automatically:

_, err := client.QuerySync(ctx, message, opts)
switch {
case err == nil:
    // success
case imagine.IsUnauthorized(err):
    // bad or expired API key
case imagine.IsQuotaExceeded(err):
    // monthly plan limit reached — upgrade on imagine.bo
case imagine.IsRateLimited(err):
    // too many requests — add back-off and retry
case imagine.IsNotFound(err):
    // KB / file / session does not exist
default:
    var apiErr *imagine.Error
    if errors.As(err, &apiErr) {
        fmt.Println(apiErr.StatusCode, apiErr.Code, apiErr.Message)
        fmt.Println("request ID:", apiErr.RequestID) // include in support reports
    }
}
Error code constants
Constant Server value HTTP status
imagine.ErrCodeUnauthorized unauthorized 401
imagine.ErrCodeNotFound NOT_FOUND 404
imagine.ErrCodeInvalidRequest INVALID_REQUEST 400
imagine.ErrCodeServerError INTERNAL_ERROR 500
imagine.ErrCodeRateLimited rate_limited 429
imagine.ErrCodeQuotaExceeded quota_exceeded
imagine.ErrCodeFileTooLarge file_too_large
imagine.ErrCodeUnsupportedType unsupported_type

Running the Example

git clone https://github.com/imagineBo/imagine-rag
cd imagine-rag/example
API_KEY=sk-your-key SERVER_URL=https://your-server.com go run .

Development

make test          # run all tests
make test-verbose  # run tests with -v
make cover         # generate and open HTML coverage report
make vet           # run go vet
make lint          # run golangci-lint (must be installed separately)
make build         # compile library + example

License

MIT

Documentation

Index

Constants

View Source
const (
	// ErrCodeUnauthorized is returned when the API key is missing or invalid.
	ErrCodeUnauthorized = "unauthorized"

	// ErrCodeNotFound is returned when the requested resource (KB, file, session)
	// does not exist or has already been deleted.
	ErrCodeNotFound = "NOT_FOUND"

	// ErrCodeFileTooLarge is returned when an uploaded file exceeds the plan limit.
	ErrCodeFileTooLarge = "file_too_large"

	// ErrCodeUnsupportedType is returned when a file's MIME type is not accepted.
	ErrCodeUnsupportedType = "unsupported_type"

	// ErrCodeQuotaExceeded is returned when the tenant's monthly usage quota is
	// exhausted. Upgrade the plan on the imagine.bo dashboard to continue.
	ErrCodeQuotaExceeded = "quota_exceeded"

	// ErrCodeRateLimited is returned when too many requests are sent in a short
	// window. Back off and retry with exponential delay.
	ErrCodeRateLimited = "rate_limited"

	// ErrCodeInvalidRequest is returned when the request body is missing required
	// fields or contains invalid values.
	ErrCodeInvalidRequest = "INVALID_REQUEST"

	// ErrCodeServerError is the catch-all for unexpected 5xx responses.
	ErrCodeServerError = "INTERNAL_ERROR"
)

Error code constants — use these in switch statements or comparisons instead of hard-coding raw strings.

Variables

This section is empty.

Functions

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is an *Error with code ErrCodeNotFound.

file, err := client.GetFileStatus(ctx, id)
if imagine.IsNotFound(err) {
    // File was deleted or the ID is wrong.
}

func IsQuotaExceeded

func IsQuotaExceeded(err error) bool

IsQuotaExceeded reports whether err is an *Error with code ErrCodeQuotaExceeded.

When true, the tenant's monthly plan limit has been reached. Upgrade the plan on the imagine.bo dashboard to continue ingesting or querying.

func IsRateLimited

func IsRateLimited(err error) bool

IsRateLimited reports whether err is an *Error with code ErrCodeRateLimited.

When true, the server received too many requests too quickly. Implement exponential back-off before retrying.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err is an *Error with code ErrCodeUnauthorized.

This typically means the API key is wrong, expired, or was revoked.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the entry point for all API calls.

Create one with New and reuse it for the lifetime of your application — it holds an HTTP connection pool and is safe for concurrent use.

func New

func New(apiKey string, opts ...Option) *Client

New creates and returns a new Client.

Both apiKey and WithBaseURL are required — New panics if either is missing. Obtain your API key from the imagine.bo dashboard. All other settings are optional and can be overridden with Option functions.

client := imagine.New(
    "sk-your-api-key",
    imagine.WithBaseURL("https://your-server.com"),
)

// With additional options:
client := imagine.New(
    "sk-your-api-key",
    imagine.WithBaseURL("https://your-server.com"),
    imagine.WithTimeout(60*time.Second),
    imagine.WithKB("kb_abc123"),
)

func (*Client) CancelCrawl

func (c *Client) CancelCrawl(ctx context.Context, jobID string) error

CancelCrawl stops a running crawl job.

Pages that have already been fetched and ingested remain in the knowledge base. Only the remaining queued pages are discarded.

err := client.CancelCrawl(ctx, jobID)

func (*Client) CrawlURL

func (c *Client) CrawlURL(ctx context.Context, seedURL string, opts CrawlOpts) (CrawlJob, error)

CrawlURL starts a crawl job that spiders a website and ingests each page into a knowledge base as a separate document.

The call returns immediately with a CrawlJob containing the JobID — the crawl runs asynchronously on the server. Use Client.WaitForCrawl to block until it finishes, or poll Client.GetCrawlStatus manually.

job, err := client.CrawlURL(ctx, "https://docs.acme.com", imagine.CrawlOpts{
    MaxPages:   200,
    MaxDepth:   4,
    SameDomain: true, // stay on docs.acme.com
})
if err != nil {
    log.Fatal(err)
}
fmt.Println("crawl started:", job.JobID)

func (*Client) CreateKB

func (c *Client) CreateKB(ctx context.Context, name string, opts KBOpts) (KnowledgeBase, error)

CreateKB creates a new knowledge base and returns its metadata.

A knowledge base is an isolated collection of document chunks. You typically create one KB per product, language, or customer. After creation, set it as the default on the client so you don't have to pass KBID on every call:

kb, err := client.CreateKB(ctx, "Product Docs", imagine.KBOpts{
    Description: "Public documentation for Acme v2",
})
if err != nil {
    log.Fatal(err)
}
client.SetActiveKB(kb.KBID) // all future ingest/query calls use this KB

func (*Client) CreateSession

func (c *Client) CreateSession(ctx context.Context, opts SessionOpts) (Session, error)

CreateSession creates a new chat session on the server and returns its metadata, including the SessionID you must save in your own database.

Sessions let the server store the full message history for a conversation, so your users can leave and return to the same chat without you having to manage message storage yourself.

Typical first-message flow:

  1. Call CreateSession to get a SessionID.

  2. Save the SessionID in your DB tied to your user/chat record.

  3. Pass QueryOpts.SessionID on every Query call — the server appends both the user message and the assistant reply automatically.

    session, err := client.CreateSession(ctx, imagine.SessionOpts{ Name: "Alice — billing support", }) if err != nil { log.Fatal(err) } // Persist session.SessionID → your DB here. fmt.Println("session:", session.SessionID)

func (*Client) DeleteFile

func (c *Client) DeleteFile(ctx context.Context, fileID string) error

DeleteFile permanently removes a file and all its indexed chunks from the knowledge base. This is irreversible.

After deletion, the file's content is no longer returned in query results.

err := client.DeleteFile(ctx, fileID)
if err != nil && !imagine.IsNotFound(err) {
    log.Fatal(err)
}

func (*Client) DeleteKB

func (c *Client) DeleteKB(ctx context.Context, kbID string) error

DeleteKB permanently deletes a knowledge base along with all its files and indexed chunks. This is irreversible — deleted content cannot be recovered.

After deletion, any query that targets this KB will return a not-found error.

err := client.DeleteKB(ctx, kb.KBID)
if err != nil && !imagine.IsNotFound(err) {
    log.Fatal(err)
}

func (*Client) DeleteSession

func (c *Client) DeleteSession(ctx context.Context, sessionID string) error

DeleteSession permanently removes the session and all its stored messages from the server.

This is irreversible. After deletion, Client.GetSession and Client.GetHistory will return a not-found error for this ID.

err := client.DeleteSession(ctx, sessionID)
if err != nil && !imagine.IsNotFound(err) {
    log.Fatal(err)
}

func (*Client) GetActiveKB

func (c *Client) GetActiveKB() string

GetActiveKB returns the current default knowledge base ID. Returns an empty string if none has been set.

func (*Client) GetCrawlStatus

func (c *Client) GetCrawlStatus(ctx context.Context, jobID string) (CrawlJob, error)

GetCrawlStatus fetches the current state of a crawl job without blocking.

Check CrawlJob.Status to see whether the crawl is still running or has reached a terminal state ("completed", "failed", or "cancelled").

job, err := client.GetCrawlStatus(ctx, jobID)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("pages done: %d / %d\n", job.PagesDone, job.PagesTotal)

func (*Client) GetFileStatus

func (c *Client) GetFileStatus(ctx context.Context, fileID string) (File, error)

GetFileStatus fetches the current state of an ingested file.

The File.Status field transitions from "processing" → "ready" (success) or "processing" → "failed" (error). Use Client.WaitForFile to block until the transition happens.

file, err := client.GetFileStatus(ctx, fileID)
if err != nil {
    log.Fatal(err)
}
fmt.Println("status:", file.Status)

func (*Client) GetHistory

func (c *Client) GetHistory(ctx context.Context, sessionID string) ([]Message, error)

GetHistory returns all Message values stored for a session, ordered chronologically (oldest first).

Call this when a user reopens an old chat to render prior messages, or pass the result directly to QueryOpts.History to give the model context.

history, err := client.GetHistory(ctx, sessionID)
if err != nil {
    log.Fatal(err)
}
// Render messages in your UI, or feed them into the next Query call.
resp, err := client.QuerySync(ctx, userMessage, imagine.QueryOpts{
    History:   history,
    SessionID: sessionID,
})

func (*Client) GetSession

func (c *Client) GetSession(ctx context.Context, sessionID string) (Session, error)

GetSession fetches the metadata for an existing session.

Use this to display session details (name, created date) in your UI without loading the full message history. To load messages, call Client.GetHistory.

session, err := client.GetSession(ctx, sessionID)
if err != nil {
    log.Fatal(err)
}
fmt.Println("session name:", session.Name)
fmt.Println("last activity:", session.UpdatedAt)

func (*Client) IngestFile

func (c *Client) IngestFile(ctx context.Context, filePath string, opts IngestOpts) (IngestResult, error)

IngestFile uploads a local file to be parsed, chunked, embedded, and stored in a knowledge base.

Supported formats: PDF, DOCX, PPTX, TXT, MD, CSV.

The call returns as soon as the server accepts the file — processing happens asynchronously. Use Client.WaitForFile to block until the file is ready, or poll Client.GetFileStatus manually.

result, err := client.IngestFile(ctx, "./docs/manual.pdf", imagine.IngestOpts{})
if err != nil {
    log.Fatal(err)
}
// Wait until the server finishes processing.
file, err := client.WaitForFile(ctx, result.FileID, 0)

func (*Client) IngestText

func (c *Client) IngestText(ctx context.Context, content, name string, opts IngestOpts) (IngestResult, error)

IngestText sends raw string content directly to the knowledge base.

name is a human-readable label shown in the admin panel (e.g. "Q3 release notes"). Use this for dynamically generated content or content that does not live in a file.

result, err := client.IngestText(ctx,
    "Our return policy allows returns within 30 days.",
    "return-policy-v2",
    imagine.IngestOpts{},
)

func (*Client) IngestURL

func (c *Client) IngestURL(ctx context.Context, url string, opts IngestOpts) (IngestResult, error)

IngestURL tells the server to fetch and process a single publicly accessible web page.

The call returns immediately — fetching and processing happen asynchronously. Use Client.WaitForFile or Client.GetFileStatus to check progress.

result, err := client.IngestURL(ctx, "https://docs.acme.com/faq", imagine.IngestOpts{})

func (*Client) IngestURLBatch

func (c *Client) IngestURLBatch(ctx context.Context, urls []string, opts IngestOpts) ([]IngestResult, error)

IngestURLBatch submits multiple URLs in a single request.

The server fetches and processes them in parallel. The returned slice has one IngestResult per URL in the same order as the input slice.

results, err := client.IngestURLBatch(ctx, []string{
    "https://docs.acme.com/page1",
    "https://docs.acme.com/page2",
    "https://docs.acme.com/page3",
}, imagine.IngestOpts{})

func (*Client) ListFiles

func (c *Client) ListFiles(ctx context.Context, kbID string) ([]File, error)

ListFiles returns all files in the specified knowledge base.

Pass an empty kbID to list files across all knowledge bases for this tenant. If kbID is empty and a default KB is set on the client (Client.SetActiveKB), only that KB's files are returned.

files, err := client.ListFiles(ctx, "kb_abc123")
if err != nil {
    log.Fatal(err)
}
for _, f := range files {
    fmt.Printf("%s  %-12s  %s\n", f.FileID, f.Status, f.Name)
}

func (*Client) ListKBs

func (c *Client) ListKBs(ctx context.Context) ([]KnowledgeBase, error)

ListKBs returns all knowledge bases belonging to this tenant's API key.

Use this to let your users choose which knowledge base to query, or to find an existing KB by name before deciding whether to create a new one.

kbs, err := client.ListKBs(ctx)
if err != nil {
    log.Fatal(err)
}
for _, kb := range kbs {
    fmt.Printf("%s  %s  (%d files)\n", kb.KBID, kb.Name, kb.FileCount)
}

func (*Client) ListSessions

func (c *Client) ListSessions(ctx context.Context) ([]Session, error)

ListSessions returns all sessions belonging to this tenant's API key, ordered by most recently updated first.

Use this to build a "past chats" list in your UI.

sessions, err := client.ListSessions(ctx)
if err != nil {
    log.Fatal(err)
}
for _, s := range sessions {
    fmt.Printf("%s  %s  (last active: %s)\n", s.SessionID, s.Name, s.UpdatedAt.Format(time.RFC3339))
}

func (*Client) Query

func (c *Client) Query(ctx context.Context, message string, opts QueryOpts) (<-chan StreamChunk, error)

Query sends a message to the RAG pipeline and returns a channel of streaming tokens. The channel is closed automatically when the stream ends.

Each StreamChunk in the channel carries incremental text. On the final chunk (Chunk.Done == true), StreamChunk.Sources is populated with the document chunks used to produce the answer.

Always check StreamChunk.Err on each iteration — a non-nil error means the connection broke and the answer is incomplete.

Passing history from your own database gives the model conversation context:

// Load the last N messages from your DB.
history, _ := client.GetHistory(ctx, sessionID)

stream, err := client.Query(ctx, "How do I reset my password?", imagine.QueryOpts{
    History:   history,
    SessionID: sessionID, // server appends the new pair automatically
    TopK:      5,
})
if err != nil {
    log.Fatal(err)
}

for chunk := range stream {
    if chunk.Err != nil {
        log.Fatal(chunk.Err)
    }
    fmt.Print(chunk.Text)
    if chunk.Done {
        fmt.Printf("\n\nSources used: %d\n", len(chunk.Sources))
        break
    }
}

func (*Client) QuerySync

func (c *Client) QuerySync(ctx context.Context, message string, opts QueryOpts) (QueryResponse, error)

QuerySync sends a message to the RAG pipeline and waits for the complete answer before returning.

Use this instead of Client.Query when you don't need streaming — for example in background jobs, webhooks, or APIs where you return the full answer in a single HTTP response.

resp, err := client.QuerySync(ctx, "What is the refund policy?", imagine.QueryOpts{
    History: history,
})
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.Answer)
for _, src := range resp.Sources {
    fmt.Printf("  - %s (score: %.2f)\n", src.FileName, src.Score)
}

func (*Client) SetActiveKB

func (c *Client) SetActiveKB(kbID string)

SetActiveKB changes the default knowledge base ID at runtime.

Equivalent to constructing the client with WithKB. Useful when you create a new KB after the client was already initialised.

kb, _ := client.CreateKB(ctx, "Docs", imagine.KBOpts{})
client.SetActiveKB(kb.KBID)

func (*Client) WaitForCrawl

func (c *Client) WaitForCrawl(ctx context.Context, jobID string, interval time.Duration) (CrawlJob, error)

WaitForCrawl polls Client.GetCrawlStatus at the given interval until the crawl reaches a terminal state ("completed", "failed", or "cancelled"), then returns the final CrawlJob.

Pass interval = 0 to use the default of 3 seconds. Cancel the context to stop waiting early — the crawl itself keeps running on the server; call Client.CancelCrawl to stop it.

// Wait up to 10 minutes, polling every 5 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()

job, err := client.WaitForCrawl(ctx, jobID, 5*time.Second)
if err != nil {
    log.Fatal(err) // context deadline or API error
}
fmt.Printf("crawl %s — %d pages ingested\n", job.Status, job.PagesDone)

func (*Client) WaitForFile

func (c *Client) WaitForFile(ctx context.Context, fileID string, interval time.Duration) (File, error)

WaitForFile polls Client.GetFileStatus at the given interval until the file reaches a terminal state ("ready" or "failed"), then returns the final File.

Pass interval = 0 to use the default of 2 seconds. Cancel the context to stop waiting early.

// Ingest and wait.
result, _ := client.IngestFile(ctx, "./manual.pdf", imagine.IngestOpts{})

file, err := client.WaitForFile(ctx, result.FileID, 0)
if err != nil {
    log.Fatal(err)
}
if file.Status == "failed" {
    log.Fatal("processing failed — check the dashboard for details")
}
fmt.Println("file is ready:", file.Name)

type CrawlJob

type CrawlJob struct {
	// JobID uniquely identifies the crawl. Use it with [Client.GetCrawlStatus],
	// [Client.WaitForCrawl], and [Client.CancelCrawl].
	JobID string `json:"job_id"`

	// Status is the current state of the crawl.
	//
	//   - "queued"    — accepted, waiting for a crawler worker
	//   - "running"   — actively fetching pages
	//   - "done"      — all pages processed
	//   - "failed"    — crawler encountered an unrecoverable error
	//   - "cancelled" — stopped by [Client.CancelCrawl]
	Status string `json:"status"`

	// SeedURL is the starting URL provided to [Client.CrawlURL].
	SeedURL string `json:"seed_url"`

	// PagesDone is the number of pages successfully fetched and ingested.
	PagesDone int `json:"pages_done"`

	// PagesTotal is the estimated total number of pages to crawl.
	// This may be 0 until the crawler has discovered the full link graph.
	PagesTotal int `json:"pages_total"`

	// KBID is the knowledge base all crawled pages are stored in.
	KBID string `json:"kb_id"`

	// CreatedAt is when the crawl job was created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is the last time the job state changed.
	UpdatedAt time.Time `json:"updated_at"`
}

CrawlJob represents the state of a crawl job returned by Client.CrawlURL, Client.GetCrawlStatus, and Client.WaitForCrawl.

type CrawlOpts

type CrawlOpts struct {
	// KBID is the knowledge base to store crawled pages in.
	// Defaults to the client's active KB when empty.
	KBID string `json:"kb_id,omitempty"`

	// MaxPages is the upper limit on pages to crawl.
	// Server default is 100. Set higher for large documentation sites.
	MaxPages int `json:"max_pages,omitempty"`

	// MaxDepth is the maximum number of link hops from the seed URL.
	// Server default is 3.
	MaxDepth int `json:"max_depth,omitempty"`

	// SameDomain restricts the crawler to URLs on the same domain as the
	// seed URL. Strongly recommended to prevent runaway crawls.
	SameDomain bool `json:"same_domain,omitempty"`

	// Tags are arbitrary key/value metadata attached to every page ingested
	// by this crawl.
	Tags map[string]string `json:"tags,omitempty"`
}

CrawlOpts controls how a website is crawled. All fields are optional — leave them zero to use server defaults.

type Error

type Error struct {
	// StatusCode is the HTTP status code returned by the server (e.g. 401, 404).
	StatusCode int `json:"status_code"`

	// Code is a machine-readable error identifier (one of the ErrCode* constants).
	Code string `json:"code"`

	// Message is a human-readable description of what went wrong.
	Message string `json:"message"`

	// RequestID is the server-assigned trace ID. Include it in support requests.
	RequestID string `json:"request_id"`
}

Error is returned for every non-2xx response from the API.

You can inspect it directly or use the typed helper functions (IsNotFound, IsUnauthorized, IsQuotaExceeded, IsRateLimited) to avoid matching on the raw code strings yourself.

if err != nil {
    var apiErr *imagine.Error
    if errors.As(err, &apiErr) {
        fmt.Println("HTTP", apiErr.StatusCode, "–", apiErr.Message)
        fmt.Println("request ID:", apiErr.RequestID) // useful for support
    }
}

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

type File

type File struct {
	// FileID uniquely identifies the file.
	FileID string `json:"file_id"`

	// Name is the original filename or the label passed to [Client.IngestText].
	Name string `json:"name"`

	// Status is the current processing state.
	//
	//   - "processing" — the file is being parsed, chunked, and embedded
	//   - "ready"      — chunks are indexed and the file can be queried
	//   - "failed"     — processing failed; check the dashboard for details
	Status string `json:"status"`

	// KBID is the knowledge base this file belongs to.
	KBID string `json:"kb_id"`

	// Size is the file size in bytes (0 for text ingested via [Client.IngestText]).
	Size int64 `json:"size"`

	// Tags are the metadata key/value pairs set in [IngestOpts.Tags].
	Tags map[string]string `json:"tags"`

	// CreatedAt is when the file was submitted for ingestion.
	CreatedAt time.Time `json:"created_at"`
}

File represents an ingested document stored in a knowledge base. Returned by Client.ListFiles, Client.GetFileStatus, and Client.WaitForFile.

type IngestOpts

type IngestOpts struct {
	// KBID is the knowledge base to ingest into.
	// Defaults to the client's active KB ([Client.SetActiveKB]) when empty.
	KBID string `json:"kb_id,omitempty"`

	// Tags are arbitrary key/value metadata attached to every chunk produced
	// from this document. Useful for tenant isolation or filtering.
	Tags map[string]string `json:"tags,omitempty"`

	// ChunkSize is the target number of tokens per chunk.
	// Leave 0 to use the server default (typically 512).
	ChunkSize int `json:"chunk_size,omitempty"`

	// ChunkOverlap is the number of tokens shared between adjacent chunks.
	// Leave 0 to use the server default (typically 64).
	ChunkOverlap int `json:"chunk_overlap,omitempty"`
}

IngestOpts controls how a document is chunked and stored. All fields are optional — leave them zero to use server defaults.

type IngestResult

type IngestResult struct {
	// FileID uniquely identifies the ingested document.
	// Store this if you want to check status or delete the file later.
	FileID string `json:"file_id"`

	// Status is the initial processing state, usually "processing".
	// It transitions to "ready" or "failed" once the server finishes.
	Status string `json:"status"` // "processing" | "ready" | "failed"

	// CreatedAt is when the ingest job was accepted by the server.
	CreatedAt time.Time `json:"created_at"`
}

IngestResult is returned immediately when a document is submitted.

Document processing (parsing, chunking, embedding) happens asynchronously on the server. Poll Client.GetFileStatus or call Client.WaitForFile to wait until the file is ready to query.

type KBOpts

type KBOpts struct {
	// Description is a human-readable summary of what this KB contains.
	Description string `json:"description,omitempty"`

	// Tags are arbitrary key/value labels (e.g. {"env": "production"}).
	Tags map[string]string `json:"tags,omitempty"`
}

KBOpts are optional fields when creating a knowledge base with Client.CreateKB.

type KnowledgeBase

type KnowledgeBase struct {
	// KBID is the unique identifier. Pass it to [Client.SetActiveKB] or
	// include it in [IngestOpts.KBID] / [QueryOpts.KBID].
	KBID string `json:"kb_id"`

	// Name is the label provided to [Client.CreateKB].
	Name string `json:"name"`

	// Description is the optional summary provided to [Client.CreateKB].
	Description string `json:"description"`

	// Tags are the labels provided to [Client.CreateKB].
	Tags map[string]string `json:"tags"`

	// FileCount is the total number of documents currently in this KB.
	FileCount int `json:"file_count"`

	// CreatedAt is when the knowledge base was created.
	CreatedAt time.Time `json:"created_at"`
}

KnowledgeBase holds metadata for a knowledge base. Returned by Client.CreateKB, Client.ListKBs, and related calls.

type Message

type Message struct {
	// Role is either "user" or "assistant".
	Role string `json:"role"`

	// Content is the text of the message.
	Content string `json:"content"`
}

Message represents a single turn in a conversation.

Build a slice of Messages from your own database and pass it to [Query] or [QuerySync] via QueryOpts.History so the model has conversation context.

history := []imagine.Message{
    {Role: "user",      Content: "Hi, I need help."},
    {Role: "assistant", Content: "Sure! What can I do for you?"},
}

type Option

type Option func(*Client)

Option is a functional option for New. Pass one or more options to customise the client at construction time.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL sets the base URL of your hosted imagine.bo server.

This option is required. New panics if no base URL is provided. Every API call is sent to this server, so set it once and reuse the client.

client := imagine.New(apiKey, imagine.WithBaseURL("https://your-server.com"))

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient replaces the underlying net/http.Client entirely.

Use this when you need a custom transport (mutual TLS, corporate proxy, request signing) or want to inject a test double.

client := imagine.New(apiKey, imagine.WithHTTPClient(myCustomHTTPClient))

func WithKB

func WithKB(kbID string) Option

WithKB sets a default knowledge base ID on the client.

Every function that accepts IngestOpts or QueryOpts will use this KB when the caller leaves the KBID field empty. You can also change the default at runtime with Client.SetActiveKB.

client := imagine.New(apiKey, imagine.WithKB("kb_abc123"))

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the HTTP request timeout applied to every call.

The default is 30 s. For [Query] (streaming) you may want a longer timeout or 0 (no timeout) so the connection stays open for the full response stream.

// Never time out — useful for long-running streams.
client := imagine.New(apiKey, imagine.WithTimeout(0))

type QueryOpts

type QueryOpts struct {
	// KBID restricts retrieval to a single knowledge base.
	// Defaults to the client's active KB when empty.
	KBID string `json:"kb_id,omitempty"`

	// History is the conversation context passed to the model.
	// Fetch it from your DB ([Client.GetHistory]) or maintain it yourself.
	// The server appends both the new user message and its reply automatically
	// when SessionID is set.
	History []Message `json:"history,omitempty"`

	// SessionID tells the server to store the new message pair in this session.
	// When set, the server persists both the user message and the assistant
	// reply so future calls to [Client.GetHistory] return them.
	SessionID string `json:"session_id,omitempty"`

	// SystemPrompt overrides the default system instruction sent to the LLM.
	SystemPrompt string `json:"system_prompt,omitempty"`

	// TopK is the number of document chunks retrieved from the KB to use as
	// context. Higher values give more context but increase latency and cost.
	// Server default is 5.
	TopK int `json:"top_k,omitempty"`

	// Temperature controls the randomness of the generated answer (0.0–1.0).
	// Lower values are more deterministic; higher values are more creative.
	// Server default is 0.2.
	Temperature float64 `json:"temperature,omitempty"`
}

QueryOpts controls retrieval and generation behaviour. All fields are optional — sensible defaults are applied server-side.

type QueryResponse

type QueryResponse struct {
	// Answer is the full generated answer.
	Answer string `json:"answer"`

	// Sources lists the document chunks used to produce the answer.
	// Use these to render citations or "sources used" panels in your UI.
	Sources []Source `json:"sources"`
}

QueryResponse is the result of a Client.QuerySync call.

func CollectStream

func CollectStream(ch <-chan StreamChunk) (QueryResponse, error)

CollectStream drains a Client.Query stream into a QueryResponse.

It is a convenience wrapper for callers that want to stream tokens to a user (e.g. via WebSocket) while still getting a final QueryResponse with the full assembled answer and Sources list.

stream, _ := client.Query(ctx, message, opts)
resp, err := imagine.CollectStream(stream)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Full answer:", resp.Answer)

type Session

type Session struct {
	// SessionID is the unique identifier. Save this in your own DB.
	SessionID string `json:"session_id"`

	// Name is the human-readable label set at creation time.
	Name string `json:"name"`

	// Metadata is the arbitrary key/value data provided at creation time.
	Metadata map[string]string `json:"metadata"`

	// CreatedAt is when the session was created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is the last time a message was added to the session.
	UpdatedAt time.Time `json:"updated_at"`
}

Session holds metadata for a single chat session stored on the server.

Typical flow:

  1. Call Client.CreateSession when a user starts a new chat.
  2. Save Session.SessionID in your own database tied to your user.
  3. On subsequent messages, call Client.GetHistory to load past messages, then pass them to Client.QuerySync via QueryOpts.History.
  4. Call Client.DeleteSession when the user deletes the chat.

type SessionOpts

type SessionOpts struct {
	// Name is a human-readable label for the session (e.g. the user's name or
	// the first message preview). Purely cosmetic — not used during querying.
	Name string `json:"name,omitempty"`

	// Metadata is arbitrary key/value data you want to store alongside the
	// session (e.g. your internal user ID, locale, subscription tier).
	Metadata map[string]string `json:"metadata,omitempty"`
}

SessionOpts are optional fields when creating a session with Client.CreateSession.

type Source

type Source struct {
	// FileID is the ID of the parent document.
	FileID string `json:"file_id"`

	// FileName is the human-readable name of the parent document.
	FileName string `json:"file_name"`

	// ChunkText is the raw text of the retrieved chunk.
	ChunkText string `json:"chunk_text"`

	// Score is the semantic similarity between the query and this chunk
	// (higher is more relevant, typically between 0 and 1).
	Score float64 `json:"score"`
}

Source is a document chunk that the server used to answer the query. Inspect these to show citations in your UI or audit retrieval quality.

type StreamChunk

type StreamChunk struct {
	// Text contains incremental token(s) appended to the answer so far.
	Text string `json:"text"`

	// Done is true on the last chunk. Sources is populated at this point.
	Done bool `json:"done"`

	// Sources lists the document chunks used. Only set when Done == true.
	Sources []Source `json:"sources"`

	// Err is non-nil if the stream broke before completing.
	// Always check this field; a non-nil Err means the answer is incomplete.
	Err error `json:"-"`
}

StreamChunk is a single event in a Client.Query streaming response.

Read chunks from the channel returned by Client.Query until Err is non-nil or Done is true:

for chunk := range stream {
    if chunk.Err != nil { /* handle error */ }
    fmt.Print(chunk.Text)
    if chunk.Done {
        // chunk.Sources is populated on the final chunk.
        break
    }
}

Jump to

Keyboard shortcuts

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