Docs revamp

This commit is contained in:
Pavel committed 2026-06-26 11:56:20 +04:00
1 parent 1acb836332
commit 5c19d972d2
27 files changed
+1101 -39

No files matched your search

+4
View File
@@ -15,6 +15,10 @@ export default {
"title": "🪝 Agent Webhooks",
"href": "/Agents/webhooks"
},
"notifications": {
"title": "📡 Realtime Events",
"href": "/Agents/notifications"
},
"nodes": {
"title": "🧩 Workflow Nodes",
"href": "/Agents/nodes"
+48
View File
@@ -345,3 +345,51 @@ curl -X POST http://localhost:7091/stream \
- If the selected model/provider does not support a file type natively, DocsGPT falls back to parsed text content.
- For providers that support images but not native PDF file attachments, DocsGPT can convert PDF pages to images (synthetic PDF support).
- Attachments are user-scoped. Upload and query must be done under the same user context (same API key owner or same JWT user).
## Agent Portability (Export & Import)
Agents can be exported to a portable YAML file and imported into another DocsGPT instance (or back into the same one). This is how you move an agent between environments or share a reproducible definition.
| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/api/export_agent?id=<agent_id>` | Download the agent as a `*.agent.yaml` file. |
| `POST` | `/api/import_agent/plan` | Dry-run an import: parse a YAML file and return a resolution plan without creating anything. |
| `POST` | `/api/import_agent` | Import a YAML file, creating the agent as a **draft**. |
```bash
# Export
curl -L "https://your-docsgpt/api/export_agent?id=<agent_id>" \
-H "Authorization: Bearer <token>" -o my-agent.agent.yaml
# Preview what an import would do
curl -X POST https://your-docsgpt/api/import_agent/plan \
-H "Authorization: Bearer <token>" \
--data-binary @my-agent.agent.yaml
# Import (creates a draft agent)
curl -X POST https://your-docsgpt/api/import_agent \
-H "Authorization: Bearer <token>" \
--data-binary @my-agent.agent.yaml
```
Notes:
- **Secrets are stripped on export** — API keys and tool credentials are never written into the YAML, so you re-enter them after import.
- The **plan** endpoint resolves references (sources, tools, prompts) and reports what will be created or matched, so you can review before committing.
- Imported tool URLs are validated against SSRF protections, the same as when creating tools normally.
- **Workflow agents cannot be exported yet** — exporting one returns `400`.
## Searching Conversations
Search across your conversations by name and message content:
```text
GET /api/search_conversations?q=<query>&limit=30
```
| Parameter | Default | Description |
| --- | --- | --- |
| `q` | — (required) | Case-insensitive substring to search for. |
| `limit` | `30` | Max results (max `100`). |
Each result includes a `match_field` (`name`, `prompt`, or `response`) and a `match_snippet` showing the matched text in context, in addition to the fields returned by `/api/get_conversations`.
+91
View File
@@ -0,0 +1,91 @@
---
title: Realtime Events & Notifications (SSE)
description: Subscribe to DocsGPT's server-sent events channel for live notifications — ingestion progress, tool approvals, MCP OAuth completion — and reconnect to an in-flight chat answer.
---
import { Callout } from 'nextra/components'
# Realtime Events & Notifications
DocsGPT pushes realtime updates to the browser over **Server-Sent Events (SSE)**. This is what powers the upload toasts, tool-approval prompts, and other live notifications in the UI. There are two channels:
- **User events** — `GET /api/events`: a per-user notification stream (ingestion progress, tool approvals, MCP OAuth completion, …).
- **Chat reconnect** — `GET /api/messages/<message_id>/events`: resume an answer stream that was interrupted mid-generation.
<Callout type="info" emoji="ℹ️">
Both channels require Redis (already a DocsGPT dependency). The publisher can be turned off instance-wide with `ENABLE_SSE_PUSH=false`.
</Callout>
## User events channel
Open an SSE connection to receive notifications for the authenticated user:
```text
GET /api/events
Accept: text/event-stream
Authorization: Bearer <token>
```
Each event is a JSON object with a `type` field, for example:
```text
id: 1718900000000-0
data: {"type":"source.ingest.queued","scope":{"id":"<source_id>"}, ...}
```
Common event types:
| Type | Meaning |
| --- | --- |
| `source.ingest.queued` | A source ingestion task was enqueued (drives the upload toast). |
| `mcp.oauth.completed` | An MCP server's OAuth handshake finished. |
| tool-approval events | An agent is requesting approval to run a tool. |
| `backlog.truncated` | The client's cursor slid off the retained backlog window — clear your cursor and refetch state. |
### Reconnecting and backlog replay
Events are journaled per user in a Redis Stream so a client that reconnects can catch up on what it missed. Send the last id you processed and DocsGPT replays everything after it:
```text
GET /api/events
Last-Event-ID: 1718900000000-0
```
(You may also pass it as a `last_event_id` query parameter.) Each delivered event carries its own `id:`, so your cursor advances as you read. If you fall a long way behind, the snapshot is delivered across several reconnects rather than all at once.
A few bounded behaviors to be aware of:
- The backlog is capped at `EVENTS_STREAM_MAXLEN` entries (default 1000). If your `Last-Event-ID` is older than the oldest retained entry, you receive a `backlog.truncated` event — reset your cursor and refetch current state.
- Each snapshot is capped at `EVENTS_REPLAY_MAX_PER_REQUEST` entries per request (default 200); reconnect to continue.
- There is a per-user cap on simultaneous connections (`SSE_MAX_CONCURRENT_PER_USER`, default 8) and a windowed replay budget. Exceeding either returns **HTTP 429** — back off and retry.
## Chat answer reconnect
When an answer is streaming and the connection drops, resume it without losing the in-progress generation:
```text
GET /api/messages/<message_id>/events
```
This replays the message's events past your last-seen sequence number and tails the rest live. It is backed by the Postgres `message_events` journal (retained for `MESSAGE_EVENTS_RETENTION_DAYS`, default 14).
<Callout type="warning" emoji="⚠️">
The chat reconnect endpoint is a native-async route served by the ASGI entrypoint. Under a plain `flask run` dev server it returns `404`; run the backend via the ASGI app (`uvicorn application.asgi:asgi_app`) or the production gunicorn uvicorn worker to use it. See the [Development Environment](/Deploying/Development-Environment) guide.
</Callout>
## Settings
| Setting | Default | Purpose |
| --- | --- | --- |
| `ENABLE_SSE_PUSH` | `true` | Master switch for the publisher and channel. |
| `EVENTS_STREAM_MAXLEN` | `1000` | Per-user backlog cap (approximate). |
| `SSE_KEEPALIVE_SECONDS` | `15` | Keepalive comment-frame cadence (keep below your proxy's idle timeout). |
| `SSE_MAX_CONCURRENT_PER_USER` | `8` | Max simultaneous SSE connections per user (`0` disables the cap). |
| `EVENTS_REPLAY_MAX_PER_REQUEST` | `200` | Max backlog entries per replay request. |
| `EVENTS_REPLAY_BUDGET_REQUESTS_PER_WINDOW` | `30` | Per-user replay requests per window (`0` disables). |
| `EVENTS_REPLAY_BUDGET_WINDOW_SECONDS` | `60` | Replay budget window length. |
| `MESSAGE_EVENTS_RETENTION_DAYS` | `14` | Retention for the chat-stream `message_events` journal. |
<Callout type="info" emoji="ℹ️">
Operators debugging delivery issues ("the toast never appeared", "the answer didn't reconnect") can follow the SSE notifications runbook in the repository at `docs/runbooks/sse-notifications.md`.
</Callout>
+154 -8
View File
@@ -1,6 +1,6 @@
---
title: OpenAI-Compatible API
description: Connect any OpenAI-compatible client to DocsGPT Agents via /v1/chat/completions.
description: Connect any OpenAI-compatible client to DocsGPT Agents via /v1/chat/completions — streaming, structured output, multimodal, tool calling, reasoning, and idempotent retries.
---
import { Callout, Tabs } from 'nextra/components';
@@ -58,7 +58,7 @@ Authenticate with `Authorization: Bearer <agent_api_key>`.
## Streaming
Set `"stream": true`. You'll receive SSE chunks with `choices[0].delta.content`. DocsGPT-specific events (sources, tool calls) arrive as extra frames with a `docsgpt` key — standard clients ignore them.
Set `"stream": true`. You'll receive SSE chunks with `choices[0].delta.content`. DocsGPT-specific events (sources, tool calls) arrive as extra frames that carry a top-level `docsgpt` key on an otherwise-empty chunk — standard clients ignore them.
```python
stream = client.chat.completions.create(
@@ -70,6 +70,135 @@ for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)
```
## Sampling Parameters
Standard OpenAI sampling parameters are forwarded to the model. When omitted, the agent's configured defaults apply. Supported: `temperature`, `max_tokens` (or `max_completion_tokens`), `top_p`, `frequency_penalty`, `presence_penalty`, `stop`, `seed`.
```json
{
"model": "docsgpt-agent",
"messages": [{"role": "user", "content": "Write a haiku about search"}],
"temperature": 0.2,
"max_tokens": 256,
"seed": 42
}
```
## Structured Output
You can force the model to return JSON matching a schema, using either the OpenAI `response_format` field or the `response_schema` convenience field.
<Tabs items={['response_format', 'response_schema']}>
<Tabs.Tab>
```json
{
"model": "docsgpt-agent",
"messages": [{"role": "user", "content": "Extract the order id and total"}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "order",
"strict": true,
"schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"total": {"type": "number"}
},
"required": ["order_id", "total"]
}
}
}
}
```
</Tabs.Tab>
<Tabs.Tab>
```json
{
"model": "docsgpt-agent",
"messages": [{"role": "user", "content": "Extract the order id and total"}],
"response_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"total": {"type": "number"}
},
"required": ["order_id", "total"]
}
}
```
</Tabs.Tab>
</Tabs>
- `response_format` follows OpenAI Structured Outputs. `strict` defaults to `true`; set `strict: false` to relax enforcement.
- `response_format: {"type": "json_object"}` requests JSON without a fixed schema (the model is steered by the prompt).
- `response_schema` is a DocsGPT convenience: pass a raw JSON Schema object (or a `{"schema": {...}}` wrapper) directly.
## Multimodal Input (text + images)
User messages may use OpenAI typed-content arrays with `image_url` parts. Images are forwarded to vision-capable models.
```json
{
"model": "docsgpt-agent",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this screenshot?"},
{"type": "image_url", "image_url": {"url": "https://example.com/shot.png"}}
]
}
]
}
```
## Tool Calling (client-side, stateless)
You can register your own tools and execute them on the client. The flow is stateless — OpenAI clients that don't carry a `conversation_id` re-send the full message history each turn, and DocsGPT rebuilds the agent from it.
1. Send a request with a `tools` array.
2. If the agent decides to call a tool, the response comes back with `finish_reason: "tool_calls"` and a `tool_calls` array (and `content: null`).
3. Execute the tool(s) on your side, then **re-POST the full message history** with the assistant's `tool_calls` message followed by `role: "tool"` result messages.
4. DocsGPT continues the run and returns the final answer.
```json
{
"model": "docsgpt-agent",
"messages": [
{"role": "user", "content": "What's the weather in Paris?"},
{"role": "assistant", "tool_calls": [
{"id": "call_1", "type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}}
]},
{"role": "tool", "tool_call_id": "call_1", "content": "18°C, clear"}
],
"tools": [ { "type": "function", "function": { "name": "get_weather", "...": "..." } } ]
}
```
## Reasoning
For models that emit reasoning ("thinking") tokens, the response surfaces them in a non-standard `reasoning_content` field (a `reasoning_content` delta when streaming). Standard clients ignore it; clients that understand it can display the model's thinking separately from the answer.
## Idempotent Retries
Add an `Idempotency-Key` header so a retried request returns the *stored first response* instead of re-running the agent (which would duplicate the answer and double-bill tokens).
```bash
curl -X POST http://localhost:7091/v1/chat/completions \
-H "Authorization: Bearer your_agent_api_key" \
-H "Idempotency-Key: 8f1c...unique-per-request" \
-H "Content-Type: application/json" \
-d '{"model":"docsgpt-agent","messages":[{"role":"user","content":"hi"}]}'
```
- **Opt-in** — no header means today's behavior (every request runs).
- **Non-streaming only** — streaming replay is not supported.
- A completed key **replays the cached body** (and status) for **24 hours**.
- A request with a key whose first attempt is **still in flight** returns **HTTP 409**.
- Keys are scoped per agent and capped at **256 characters** (oversized keys are rejected).
## System Prompt Override
System messages are **dropped by default** — the agent's configured prompt is used. To allow callers to override it, enable **Allow prompt override** in the agent's Advanced settings.
@@ -80,13 +209,30 @@ When an override is active, the agent's prompt template is replaced wholesale
## Conversation Persistence
Conversations are **always persisted** server-side, and the response includes
`docsgpt.conversation_id`. They never appear in the agent owner's sidebar —
`/v1` traffic is stored hidden, so external clients can't clutter the owner's
conversation list.
Conversations are **always persisted** server-side, and the response includes `docsgpt.conversation_id`. They never appear in the agent owner's sidebar — `/v1` traffic is stored hidden, so external clients can't clutter the owner's conversation list.
The legacy `{ "docsgpt": { "save_conversation": true } }` opt-in from older
releases (when `/v1` was stateless by default) is deprecated and ignored.
Stateless tool continuations (no `conversation_id`, e.g. opencode) skip persistence by default to avoid writing orphan rows; set `docsgpt.persist` to override. The legacy `docsgpt.save_conversation` flag from older releases is deprecated and ignored.
## DocsGPT Extension Fields
DocsGPT adds an optional `docsgpt` object to both requests and responses for features outside the OpenAI schema.
**Request** (`docsgpt.*`):
| Field | Description |
| --- | --- |
| `attachments` | List of attachment IDs to include as context for this turn. |
| `persist` | Force-enable/disable conversation persistence (mainly for stateless tool continuations). |
**Response** (`docsgpt.*`):
| Field | Description |
| --- | --- |
| `conversation_id` | Server-side conversation ID for this exchange. |
| `sources` | RAG sources used to answer. |
| `tool_calls` | Completed tool-call results from the run. |
When streaming, these arrive on otherwise-empty chunks that carry a top-level `docsgpt` key, so strict OpenAI clients still validate each frame.
## When to Use Native Endpoints Instead
+22
View File
@@ -88,6 +88,28 @@ Send an HTTP `POST` request to the agent's unique webhook URL with the required
</Tabs.Tab>
</Tabs>
### Triggering with GET (query parameters)
The webhook listener also accepts `GET` requests, mapping the query string to the payload. This is handy for systems that can only issue a simple `GET` (for example a "ping this URL" integration):
```bash
curl "http://localhost:7091/api/webhooks/agents/your_webhook_token?question=Your+message+to+agent"
```
As with `POST`, the response is a JSON object containing the `task_id`.
### Preventing duplicate triggers (Idempotency-Key)
To make a trigger safe to retry, send an optional `Idempotency-Key` header. If the same key is seen again within 24 hours, DocsGPT does not enqueue a second task — it returns the **original** `task_id` instead. This prevents a retried or double-fired webhook from running the agent twice.
```bash
curl -X POST \
http://localhost:7091/api/webhooks/agents/your_webhook_token \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4567-created" \
-d '{"question": "Your message to agent"}'
```
### Step 2: Poll for the Result
Once you have the `task_id`, periodically send a `GET` request to the `/api/task_status` endpoint until the task `status` is `SUCCESS` or `FAILURE`.