diff --git a/application/api/answer/routes.py b/application/api/answer/routes.py index 469ea98c..2b9783f7 100644 --- a/application/api/answer/routes.py +++ b/application/api/answer/routes.py @@ -614,7 +614,7 @@ class Answer(Resource): try: question = data["question"] history = limit_chat_history( - json.loads(data.get("history", [])), gpt_model=gpt_model + json.loads(data.get("history", "[]")), gpt_model=gpt_model ) conversation_id = data.get("conversation_id") prompt_id = data.get("prompt_id", "default") diff --git a/docs/pages/Agents/_meta.json b/docs/pages/Agents/_meta.json index f5d0fe6e..857a6c30 100644 --- a/docs/pages/Agents/_meta.json +++ b/docs/pages/Agents/_meta.json @@ -2,5 +2,13 @@ "basics": { "title": "🤖 Agent Basics", "href": "/Agents/basics" + }, + "api": { + "title": "🔌 Agent API", + "href": "/Agents/api" + }, + "webhooks": { + "title": "🪝 Agent Webhooks", + "href": "/Agents/webhooks" } -} \ No newline at end of file +} diff --git a/docs/pages/Agents/api.mdx b/docs/pages/Agents/api.mdx new file mode 100644 index 00000000..18d4e763 --- /dev/null +++ b/docs/pages/Agents/api.mdx @@ -0,0 +1,227 @@ +--- +title: Interacting with Agents via API +description: Learn how to programmatically interact with DocsGPT Agents using the streaming and non-streaming API endpoints. +--- + +import { Callout, Tabs } from 'nextra/components'; + +# Interacting with Agents via API + +DocsGPT Agents can be accessed programmatically through a dedicated API, allowing you to integrate their specialized capabilities into your own applications, scripts, and workflows. This guide covers the two primary methods for interacting with an agent: the streaming API for real-time responses and the non-streaming API for a single, consolidated answer. + +When you use an API key generated for a specific agent, you do not need to pass `prompt`, `tools` etc. The agent's configuration (including its prompt, selected tools, and knowledge sources) is already associated with its unique API key. + +### API Endpoints + +- **Non-Streaming:** `http://localhost:7091/api/answer` +- **Streaming:** `http://localhost:7091/stream` + + +For DocsGPT Cloud, use `https://gptcloud.arc53.com/` as the base URL. + + +For more technical details, you can explore the API swagger documentation available for the cloud version or your local instance. + +--- + +## Non-Streaming API (`/api/answer`) + +This is a standard synchronous endpoint. It waits for the agent to fully process the request and returns a single JSON object with the complete answer. This is the simplest method and is ideal for backend processes where a real-time feed is not required. + +### Request + +- **Endpoint:** `/api/answer` +- **Method:** `POST` +- **Payload:** + - `question` (string, required): The user's query or input for the agent. + - `api_key` (string, required): The unique API key for the agent you wish to interact with. + - `history` (string, optional): A JSON string representing the conversation history, e.g., `[{\"prompt\": \"first question\", \"answer\": \"first answer\"}]`. + +### Response + +A single JSON object containing: +- `answer`: The complete, final answer from the agent. +- `sources`: A list of sources the agent consulted. +- `conversation_id`: The unique ID for the interaction. + +### Examples + + + + ```bash + curl -X POST http://localhost:7091/api/answer \ + -H "Content-Type: application/json" \ + -d '{ + "question": "your question here", + "api_key": "your_agent_api_key" + }' + ``` + + + ```python + import requests + + API_URL = "http://localhost:7091/api/answer" + API_KEY = "your_agent_api_key" + QUESTION = "your question here" + + response = requests.post( + API_URL, + json={"question": QUESTION, "api_key": API_KEY} + ) + + if response.status_code == 200: + print(response.json()) + else: + print(f"Error: {response.status_code}") + print(response.text) + ``` + + + ```javascript + const apiUrl = 'http://localhost:7091/api/answer'; + const apiKey = 'your_agent_api_key'; + const question = 'your question here'; + + async function getAnswer() { + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ question, api_key: apiKey }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + console.log(data); + } catch (error) { + console.error("Failed to fetch answer:", error); + } + } + + getAnswer(); + ``` + + + +--- + +## Streaming API (`/stream`) + +The `/stream` endpoint uses Server-Sent Events (SSE) to push data in real-time. This is ideal for applications where you want to display the response as it's being generated, such as in a live chatbot interface. + +### Request + +- **Endpoint:** `/stream` +- **Method:** `POST` +- **Payload:** Same as the non-streaming API. + +### Response (SSE Stream) + +The stream consists of multiple `data:` events, each containing a JSON object. Your client should listen for these events and process them based on their `type`. + +**Event Types:** +- `answer`: A chunk of the agent's final answer. +- `source`: A document or source used by the agent. +- `thought`: A reasoning step from the agent (for ReAct agents). +- `id`: The unique `conversation_id` for the interaction. +- `error`: An error message. +- `end`: A final message indicating the stream has concluded. + +### Examples + + + + ```bash + curl -X POST http://localhost:7091/stream \ + -H "Content-Type: application/json" \ + -H "Accept: text/event-stream" \ + -d '{ + "question": "your question here", + "api_key": "your_agent_api_key" + }' + ``` + + + ```python + import requests + import json + + API_URL = "http://localhost:7091/stream" + payload = { + "question": "your question here", + "api_key": "your_agent_api_key" + } + + with requests.post(API_URL, json=payload, stream=True) as r: + for line in r.iter_lines(): + if line: + decoded_line = line.decode('utf-8') + if decoded_line.startswith('data: '): + try: + data = json.loads(decoded_line[6:]) + print(data) + except json.JSONDecodeError: + pass + ``` + + + ```javascript + const apiUrl = 'http://localhost:7091/stream'; + const apiKey = 'your_agent_api_key'; + const question = 'your question here'; + + async function getStream() { + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream' + }, + // Corrected line: 'apiKey' is changed to 'api_key' + body: JSON.stringify({ question, api_key: apiKey }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + // Note: This parsing method assumes each chunk contains whole lines. + // For a more robust production implementation, buffer the chunks + // and process them line by line. + const lines = chunk.split('\n'); + + for (const line of lines) { + if (line.startsWith('data: ')) { + try { + const data = JSON.parse(line.substring(6)); + console.log(data); + } catch (e) { + console.error("Failed to parse JSON from SSE event:", e); + } + } + } + } + } catch (error) { + console.error("Failed to fetch stream:", error); + } + } + + getStream(); + ``` + + diff --git a/docs/pages/Agents/webhooks.mdx b/docs/pages/Agents/webhooks.mdx new file mode 100644 index 00000000..814690b5 --- /dev/null +++ b/docs/pages/Agents/webhooks.mdx @@ -0,0 +1,152 @@ +--- +title: Triggering Agents with Webhooks +description: Learn how to automate and integrate DocsGPT Agents using webhooks for asynchronous task execution. +--- + +import { Callout, Tabs } from 'nextra/components'; + +# Triggering Agents with Webhooks + +Agent Webhooks provide a powerful mechanism to trigger an agent's execution from external systems. Unlike the direct API which provides an immediate response, webhooks are designed for **asynchronous** operations. When you call a webhook, DocsGPT enqueues the agent's task for background processing and immediately returns a `task_id`. You then use this ID to poll for the result. + +This workflow is ideal for integrating with services that expect a quick initial response (e.g., form submissions) or for triggering long-running tasks without tying up a client connection. + +Each agent has its own unique webhook URL, which can be generated from the agent's edit page in the DocsGPT UI. This URL includes a secure token for authentication. + +### API Endpoints + +- **Webhook URL:** `http://localhost:7091/api/webhooks/agents/{AGENT_WEBHOOK_TOKEN}` +- **Task Status URL:** `http://localhost:7091/api/task_status` + + +For DocsGPT Cloud, use `https://gptcloud.arc53.com/` as the base URL. + + +For more technical details, you can explore the API swagger documentation available for the cloud version or your local instance. + +--- + +## The Webhook Workflow + +The process involves two main steps: triggering the task and polling for the result. + +### Step 1: Trigger the Webhook + +Send an HTTP `POST` request to the agent's unique webhook URL with the required payload. The structure of this payload should match what the agent's prompt and tools are designed to handle. + +- **Method:** `POST` +- **Response:** A JSON object with a `task_id`. `{"task_id": "a1b2c3d4-e5f6-..."}` + + + + ```bash + curl -X POST \ + http://localhost:7091/api/webhooks/agents/your_webhook_token \ + -H "Content-Type: application/json" \ + -d '{"question": "Your message to agent"}' + ``` + + + ```python + import requests + + WEBHOOK_URL = "http://localhost:7091/api/webhooks/agents/your_webhook_token" + payload = {"question": "Your message to agent"} + + try: + response = requests.post(WEBHOOK_URL, json=payload) + response.raise_for_status() + task_id = response.json().get("task_id") + print(f"Task successfully created with ID: {task_id}") + except requests.exceptions.RequestException as e: + print(f"Error triggering webhook: {e}") + ``` + + + ```javascript + const webhookUrl = 'http://localhost:7091/api/webhooks/agents/your_webhook_token'; + const payload = { question: 'Your message to agent' }; + + async function triggerWebhook() { + try { + const response = await fetch(webhookUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + if (!response.ok) throw new Error(`HTTP error! ${response.status}`); + const data = await response.json(); + console.log(`Task successfully created with ID: ${data.task_id}`); + return data.task_id; + } catch (error) { + console.error('Error triggering webhook:', error); + } + } + + triggerWebhook(); + ``` + + + +### 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`. + +- **`status`**: The current state of the task (`PENDING`, `STARTED`, `SUCCESS`, `FAILURE`). +- **`result`**: The final output from the agent, available when the status is `SUCCESS` or `FAILURE`. + + + + ```bash + # Replace the task_id with the one you received + curl http://localhost:7091/api/task_status?task_id=YOUR_TASK_ID + ``` + + + ```python + import requests + import time + + STATUS_URL = "http://localhost:7091/api/task_status" + task_id = "YOUR_TASK_ID" + + while True: + response = requests.get(STATUS_URL, params={"task_id": task_id}) + data = response.json() + status = data.get("status") + print(f"Current task status: {status}") + + if status in ["SUCCESS", "FAILURE"]: + print("Final Result:") + print(data.get("result")) + break + + time.sleep(2) + ``` + + + ```javascript + const statusUrl = 'http://localhost:7091/api/task_status'; + const taskId = 'YOUR_TASK_ID'; + + const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + + async function pollForResult() { + while (true) { + const response = await fetch(`${statusUrl}?task_id=${taskId}`); + const data = await response.json(); + const status = data.status; + console.log(`Current task status: ${status}`); + + if (status === 'SUCCESS' || status === 'FAILURE') { + console.log('Final Result:', data.result); + break; + } + await sleep(2000); + } + } + + pollForResult(); + ``` + +