mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-10-25 17:36:29 +08:00
feat: complete the OpenAI-compatible chat API
- Select the agent with agent_id and the model with model (defaults to the agent's own model); add GET /v1/models - Stateless requests by default, server-side history with conversation_id - Report usage (with cached tokens), reasoning_content and tool_trace / cow_event for tool and teammate activity - Add the external_api_enabled switch; turning the API off keeps its key - Web and desktop settings card with key management and request examples; show a copyable agent ID on the agents page - Docs for the chat API and the custom menu under Extensions Co-authored-by: cowagent <cow@cowagent.ai>
This commit is contained in:
1 parent
e58d0819e3
commit
260b67cbda
37 files changed
+2801
-245
No files matched your search
+5
-79
@@ -13,6 +13,7 @@ The Web Console is CowAgent's default channel. It runs automatically once starte
|
||||
"web_host": "0.0.0.0",
|
||||
"web_port": 9899,
|
||||
"web_password": "",
|
||||
"external_api_enabled": false,
|
||||
"external_api_token": "",
|
||||
"enable_thinking": false
|
||||
}
|
||||
@@ -24,7 +25,8 @@ The Web Console is CowAgent's default channel. It runs automatically once starte
|
||||
| `web_host` | Web service listen address. Defaults to `127.0.0.1` (local only); set to `0.0.0.0` for public access and configure a password | `""` |
|
||||
| `web_port` | Web service listen port | `9899` |
|
||||
| `web_password` | Access password. Leave empty to disable password protection; recommended when listening on `0.0.0.0` | `""` |
|
||||
| `external_api_token` | Independent Bearer token for the OpenAI-compatible API. Leave empty to disable the API | `""` |
|
||||
| `external_api_enabled` | Turn on the [Chat API](/extensions/chat-api) | `false` |
|
||||
| `external_api_token` | API key of the [Chat API](/extensions/chat-api), kept while the API is off | `""` |
|
||||
| `web_session_expire_days` | Login session validity in days | `30` |
|
||||
| `web_file_serve_root` | Root directory the web console can directly read/send files from. Defaults to the user home dir and agent workspace only; set to `/` to allow the whole filesystem | `"~"` |
|
||||
| `enable_thinking` | Whether to enable deep thinking mode | `false` |
|
||||
@@ -42,85 +44,9 @@ After starting the project, visit:
|
||||
Ensure the server firewall and security group allow the corresponding port.
|
||||
</Note>
|
||||
|
||||
## OpenAI-Compatible API
|
||||
## Chat API
|
||||
|
||||
Set `external_api_token` to enable `POST /v1/chat/completions`. This token is
|
||||
independent from `web_password` and Web Console login sessions.
|
||||
|
||||
The first release accepts text messages and uses CowAgent's configured Agent and
|
||||
model. The request `model` is required for OpenAI client compatibility and is
|
||||
echoed in the response; it does not select a CowAgent model. The latest non-empty
|
||||
user message is submitted to the Agent. Set `conversation_id`, or `user` as a
|
||||
fallback, to reuse a stable CowAgent session across requests. Requests without
|
||||
either field use an isolated session.
|
||||
|
||||
Non-streaming request:
|
||||
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "cowagent",
|
||||
"conversation_id": "example-conversation",
|
||||
"messages": [{"role": "user", "content": "Summarize this workspace."}]
|
||||
}'
|
||||
```
|
||||
|
||||
The standard response is returned in `choices[0].message.content`. CowAgent adds
|
||||
`reasoning_content` and `tool_trace` to the message when those traces are
|
||||
available.
|
||||
|
||||
Streaming request:
|
||||
|
||||
```bash
|
||||
curl -N http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "cowagent",
|
||||
"stream": true,
|
||||
"user": "example-user",
|
||||
"messages": [{"role": "user", "content": "Inspect the workspace."}]
|
||||
}'
|
||||
```
|
||||
|
||||
Streaming content uses standard `chat.completion.chunk` objects and
|
||||
`choices[0].delta.content`. Reasoning uses the additive
|
||||
`choices[0].delta.reasoning_content` field. Reasoning and tool-process chunks
|
||||
also include a top-level `cow_event` object. The stream ends with `data: [DONE]`.
|
||||
|
||||
OpenAI Python clients can use the endpoint by setting the base URL:
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
base_url="http://localhost:9899/v1",
|
||||
api_key="your-external-api-token",
|
||||
)
|
||||
response = client.chat.completions.create(
|
||||
model="cowagent",
|
||||
messages=[{"role": "user", "content": "Hello from Python."}],
|
||||
extra_body={"conversation_id": "python-example"},
|
||||
)
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
The API returns `400` for invalid requests, `401` for invalid credentials, and
|
||||
`503` when `external_api_token` is not configured. Non-streaming Agent failures
|
||||
return a structured JSON `500` response.
|
||||
|
||||
For streaming requests, CowAgent waits up to 30 seconds for the Agent to produce
|
||||
the first event. An Agent failure before the first event returns a structured
|
||||
JSON `500` response because the SSE response has not started. If no first event
|
||||
arrives within 30 seconds, the API cancels only that request's in-flight Agent and
|
||||
returns the same JSON `500` shape with error code `timeout`.
|
||||
|
||||
After the SSE response has started, its HTTP status can no longer change.
|
||||
A later Agent failure is therefore emitted as a `cow_event.type=error` chunk,
|
||||
followed by a terminal chunk with `finish_reason=error` and then
|
||||
`data: [DONE]`.
|
||||
The Web Console's port also serves an OpenAI-compatible chat API, `/v1/chat/completions`. It is off by default; turn it on and get an API key from the Chat API card on the settings page. See [Chat API](/extensions/chat-api).
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -270,6 +270,18 @@
|
||||
{
|
||||
"tab": "Extensions",
|
||||
"groups": [
|
||||
{
|
||||
"group": "API",
|
||||
"pages": [
|
||||
"extensions/chat-api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Customization",
|
||||
"pages": [
|
||||
"extensions/custom-menu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Third-party Integrations",
|
||||
"pages": [
|
||||
@@ -531,6 +543,18 @@
|
||||
{
|
||||
"tab": "扩展",
|
||||
"groups": [
|
||||
{
|
||||
"group": "开放接口",
|
||||
"pages": [
|
||||
"zh/extensions/chat-api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "界面定制",
|
||||
"pages": [
|
||||
"zh/extensions/custom-menu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "三方集成",
|
||||
"pages": [
|
||||
@@ -791,6 +815,18 @@
|
||||
{
|
||||
"tab": "拡張",
|
||||
"groups": [
|
||||
{
|
||||
"group": "API",
|
||||
"pages": [
|
||||
"ja/extensions/chat-api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "UI カスタマイズ",
|
||||
"pages": [
|
||||
"ja/extensions/custom-menu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "サードパーティ連携",
|
||||
"pages": [
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: Chat API
|
||||
description: Call CowAgent through the OpenAI-compatible /v1/chat/completions endpoint
|
||||
---
|
||||
|
||||
> CowAgent serves a chat endpoint compatible with the OpenAI Chat Completions protocol, on the same port as the Web Console. Any app, SDK or workflow that speaks the OpenAI protocol can connect to it directly.
|
||||
|
||||
Unlike calling a model, the endpoint runs the full Agent: it uses its tools, skills, memory and knowledge base to finish the task, and returns its reasoning, tool calls and token usage along with the answer.
|
||||
|
||||
## Turn on the API
|
||||
|
||||
The API is off by default. Once it is on, every request must carry the API key.
|
||||
|
||||
### From the console
|
||||
|
||||
Open "Config → General → Chat API" and turn on the switch. After you confirm, an API key is generated, along with the base URL and ready-to-copy examples (cURL / Python / Node.js).
|
||||
|
||||
{/* TODO: screenshot - the "Chat API" card on the config page */}
|
||||
|
||||
- **Regenerate**: issues a new API key; the old one stops working at once
|
||||
- **Turn off**: the API stops serving requests. The key is kept and works again once the API is back on
|
||||
|
||||
### From the config file
|
||||
|
||||
Set these two keys in `config.json`, then restart the service:
|
||||
|
||||
```json
|
||||
{
|
||||
"external_api_enabled": true,
|
||||
"external_api_token": "sk-your-random-key"
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| `external_api_enabled` | Whether the API is on. Defaults to `false` |
|
||||
| `external_api_token` | The API key; use a long random string. It is kept while the API is off |
|
||||
|
||||
<Note>
|
||||
The API shares the Web Console's address and port. By default it listens on `127.0.0.1`, so only this machine can reach it. To call it from other machines, set `web_host` to `0.0.0.0` as described in [Web Console](/channels/web) and open the port. The API key is independent from the console password `web_password`.
|
||||
</Note>
|
||||
|
||||
## Quick start
|
||||
|
||||
The examples assume the service runs on port `9899` of this machine. They name neither a model nor an Agent, so the default Agent answers on its own model.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"messages": [{"role": "user", "content": "Summarize the files in the workspace"}]}'
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://localhost:9899/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer your-api-key"},
|
||||
json={"messages": [{"role": "user", "content": "Summarize the files in the workspace"}]},
|
||||
)
|
||||
print(resp.json()["choices"][0]["message"]["content"])
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node.js">
|
||||
```javascript
|
||||
const resp = await fetch("http://localhost:9899/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: { "Authorization": "Bearer your-api-key", "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
messages: [{ role: "user", content: "Summarize the files in the workspace" }],
|
||||
}),
|
||||
});
|
||||
const data = await resp.json();
|
||||
console.log(data.choices[0].message.content);
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
Every request carries the API key in its headers:
|
||||
|
||||
```
|
||||
Authorization: Bearer <API key>
|
||||
```
|
||||
|
||||
### Chat
|
||||
|
||||
`POST /v1/chat/completions`
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `messages` | array | Yes | The conversation. Roles `system`, `developer`, `user` and `assistant` are accepted; `content` is a string or an array of `text` parts |
|
||||
| `model` | string | No | The model to answer on. Without it the Agent uses its own model. See [List models](#list-models) |
|
||||
| `agent_id` | string | No | CowAgent extension: the Agent that answers. Without it the default Agent answers. See [Choose an Agent](#choose-an-agent) |
|
||||
| `stream` | boolean | No | Stream the response. Defaults to `false` |
|
||||
| `stream_options` | object | No | With `{"include_usage": true}`, the stream ends with the token usage |
|
||||
| `conversation_id` | string | No | CowAgent extension: the server keeps this conversation's history. See [Conversations](#conversations) |
|
||||
|
||||
The Agent uses its own tools and system prompt, so parameters such as `temperature`, `max_tokens` and `tools` are ignored.
|
||||
|
||||
### Choose an Agent
|
||||
|
||||
With [multiple Agents](/multi-agent/team) configured, `agent_id` chooses which one answers. An Agent's ID is shown under its name on the Agents page, with a button next to it to copy it. `default` always means the default Agent. An Agent that does not exist or is archived returns 404.
|
||||
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"agent_id": "research",
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "Look into recent open-source agent frameworks"}]
|
||||
}'
|
||||
```
|
||||
|
||||
`model` applies to that request only; it does not change the model set for the Agent or the conversation.
|
||||
|
||||
### List models
|
||||
|
||||
`GET /v1/models`
|
||||
|
||||
Returns the available models: the chat models of every provider configured under "Config → Models", with the provider in `owned_by`. A request naming a model that is not listed returns 404.
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{ "id": "deepseek-chat", "object": "model", "created": 0, "owned_by": "deepseek" },
|
||||
{ "id": "qwen-max", "object": "model", "created": 0, "owned_by": "dashscope" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Conversations
|
||||
|
||||
The API supports two ways of keeping a conversation:
|
||||
|
||||
- **Stateless (default)**: without `conversation_id`, send the whole `messages` list every time, as with any model. The last `user` message is this turn's input, and the `user` / `assistant` messages before it are the context. Nothing is written to CowAgent's conversation history.
|
||||
- **Stateful**: with `conversation_id`, CowAgent keeps the history, so each request only sends the new message and earlier entries in `messages` are ignored. Requests in the same conversation are handled one at a time, in order.
|
||||
|
||||
```json
|
||||
{
|
||||
"conversation_id": "order-bot-user-42",
|
||||
"messages": [{ "role": "user", "content": "Let's continue where we left off" }]
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
`system` messages in the request are ignored: the Agent always keeps its own persona, skills and memory. Either way, the Agent can read and write its workspace and memory files through its tools.
|
||||
</Note>
|
||||
|
||||
## Responses
|
||||
|
||||
### Non-streaming
|
||||
|
||||
Besides the standard OpenAI response, `message` carries the reasoning and a record of the tool calls:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-82c19c44...",
|
||||
"object": "chat.completion",
|
||||
"created": 1791861130,
|
||||
"model": "deepseek-chat",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "The workspace holds 4 core files and 7 folders...",
|
||||
"reasoning_content": "The user wants to know what is in the workspace, so list it first...",
|
||||
"tool_trace": [
|
||||
{
|
||||
"type": "tool_result",
|
||||
"id": "call_00_wYInMZ...",
|
||||
"name": "ls",
|
||||
"arguments": { "path": "." },
|
||||
"result": "AGENT.md\nMEMORY.md\n...",
|
||||
"status": "success",
|
||||
"elapsed": "0.01s"
|
||||
}
|
||||
]
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 18151,
|
||||
"completion_tokens": 180,
|
||||
"total_tokens": 18331,
|
||||
"prompt_tokens_details": { "cached_tokens": 11392 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `model` | The model that answered |
|
||||
| `message.content` | The final answer |
|
||||
| `message.reasoning_content` | The reasoning, when deep thinking is on and the model supports it |
|
||||
| `message.tool_trace` | CowAgent extension: the tool calls, each with its arguments, result, status and duration. Hand-offs to other Agents and generated files are recorded here too, in the format of the [process events](#process-events) below |
|
||||
| `usage` | Token usage. One Agent turn may call the model several times; this is the sum of all calls. `prompt_tokens_details.cached_tokens` counts input tokens served from cache. Left out when the model reports no usage |
|
||||
|
||||
### Streaming
|
||||
|
||||
With `"stream": true` the response is a stream of standard `chat.completion.chunk` events over SSE:
|
||||
|
||||
- The answer is in `choices[0].delta.content`
|
||||
- The reasoning is in `choices[0].delta.reasoning_content`
|
||||
- Process events such as tool calls are in the top-level `cow_event` field, with an empty `delta`
|
||||
- The last chunk has `finish_reason` set to `stop`. With `stream_options.include_usage`, one more chunk follows with empty `choices` and the `usage`
|
||||
- The stream ends with `data: [DONE]`
|
||||
|
||||
```
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":null}],"cow_event":{"type":"tool_start","id":"call_1","name":"ls","arguments":{"path":"."}}}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"The workspace holds"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":18151,"completion_tokens":180,"total_tokens":18331,"prompt_tokens_details":{"cached_tokens":11392}}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
Reading the stream:
|
||||
|
||||
```python
|
||||
import json
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://localhost:9899/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer your-api-key"},
|
||||
json={"messages": [{"role": "user", "content": "Summarize the files in the workspace"}], "stream": True},
|
||||
stream=True,
|
||||
)
|
||||
for line in resp.iter_lines(decode_unicode=True):
|
||||
if not line.startswith("data: ") or line == "data: [DONE]":
|
||||
continue
|
||||
chunk = json.loads(line[len("data: "):])
|
||||
event = chunk.get("cow_event")
|
||||
if event and event["type"] == "tool_start":
|
||||
print(f"\n[tool] {event['name']} {event['arguments']}")
|
||||
for choice in chunk["choices"]:
|
||||
print(choice["delta"].get("content") or "", end="", flush=True)
|
||||
```
|
||||
|
||||
### Process events
|
||||
|
||||
`cow_event.type` takes these values:
|
||||
|
||||
| Type | Description | Main fields |
|
||||
| --- | --- | --- |
|
||||
| `tool_start` | A tool call starts | `id`, `name`, `arguments` |
|
||||
| `tool_result` | A tool call finishes | `id`, `name`, `arguments`, `result`, `status`, `elapsed` |
|
||||
| `subagent_step` | A step taken by a sub-agent | `card_id`, `step_id`, `phase`, `tool`, `status` |
|
||||
| `peer_start` | The task is handed to another Agent; the content that follows is its answer | `card_id`, `agent_id`, `agent_name` |
|
||||
| `peer_end` | That Agent is done | `card_id`, `agent_id`, `status` |
|
||||
| `artifact` | A file the Agent produced | `artifact` |
|
||||
| `reasoning` | Reasoning, the same text as `delta.reasoning_content` | `delta` |
|
||||
| `error` | The run failed | `message` |
|
||||
|
||||
## Errors
|
||||
|
||||
Errors follow the OpenAI format:
|
||||
|
||||
```json
|
||||
{ "error": { "message": "Invalid authentication credentials.", "type": "api_error", "code": "invalid_api_key" } }
|
||||
```
|
||||
|
||||
| Status | `code` | Meaning |
|
||||
| --- | --- | --- |
|
||||
| 400 | `invalid_request` / `invalid_json` | Malformed request |
|
||||
| 401 | `invalid_api_key` | Wrong API key |
|
||||
| 404 | `model_not_found` | `model` is not in the [model list](#list-models) |
|
||||
| 404 | `agent_not_found` | The Agent `agent_id` names does not exist or is archived |
|
||||
| 503 | `api_disabled` | The API is off |
|
||||
| 500 | `internal_error` / `timeout` | The Agent failed or timed out |
|
||||
|
||||
A streaming request waits up to 30 seconds for the Agent to produce the first event. A failure or a timeout before the first event returns one of the JSON errors above, and a timeout cancels that request. Once the stream has started its HTTP status can no longer change, so a later failure arrives as a `cow_event.type=error` chunk, followed by a terminal chunk with `finish_reason=error` and then `data: [DONE]`.
|
||||
|
||||
## Use it from other apps
|
||||
|
||||
Any client or platform that accepts a custom OpenAI endpoint can connect. Choose the "OpenAI" or "OpenAI compatible" provider and fill in:
|
||||
|
||||
- **Base URL**: `http://<server address>:9899/v1` (some apps only want the address and port, and add `/v1` themselves)
|
||||
- **API key**: the key generated in the console
|
||||
- **Model**: pick one from the [model list](#list-models)
|
||||
|
||||
The default Agent answers requests from these apps.
|
||||
|
||||
The API allows cross-origin requests (CORS), so browser apps can call it directly.
|
||||
|
||||
<Note>
|
||||
Only text input is supported for now; images and other multimodal content are not. Caller-supplied `tools` for function calling are not supported either: the Agent finishes the task with its own tools.
|
||||
</Note>
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: Custom Menu
|
||||
description: Reorder and group the sidebar menu, and put the artifacts and web pages you use often in it
|
||||
---
|
||||
|
||||
The sidebar menu can be arranged the way you work: reorder and group it, hide the pages you rarely use, and add web pages and documents the Agent created, or any URL, so they open in one click like a built-in page. The Web Console and the desktop client share one menu.
|
||||
|
||||
{/* TODO: screenshot - a customized sidebar */}
|
||||
|
||||
## Open the menu editor
|
||||
|
||||
There are three ways in:
|
||||
|
||||
- Hover over a group title in the sidebar and click the edit icon that appears on its right
|
||||
- Click the version number at the bottom left of the sidebar (Web Console) or the "More" button (desktop client), then choose "Customize menu"
|
||||
- While previewing a web page or Markdown document on the Artifacts page, click "Add to menu"
|
||||
|
||||
{/* TODO: screenshot - the menu editor */}
|
||||
|
||||
Changes take effect once you click Save in the editor.
|
||||
|
||||
## Order and groups
|
||||
|
||||
- Hold the drag handle on the left of an entry to move it, within its group or into another one
|
||||
- Click "Add group" to create a group; its title can be edited in place
|
||||
- Before removing a group, move the built-in pages it holds to another group
|
||||
|
||||
## Built-in pages
|
||||
|
||||
Built-in pages such as Chat, Agents, Config and Capabilities can be:
|
||||
|
||||
- **Renamed and given another icon**: an empty name falls back to the default one
|
||||
- **Hidden**: click the eye icon to hide or show the page. Chat and Config cannot be hidden, so the settings are always within reach
|
||||
- **Added back**: a removed built-in page can be restored from "Add → Built-in page"
|
||||
|
||||
The Artifacts page starts hidden; show it in the editor when you need it.
|
||||
|
||||
## Add an artifact
|
||||
|
||||
Use "Add → Artifact (HTML / Markdown)" to search the web pages and Markdown documents the Agent created and put one in the menu. Clicking the entry opens it right inside the console, always at its latest version, which suits reports, dashboards and notes the Agent keeps up to date.
|
||||
|
||||
If the file is deleted, the entry says so, and you can remove it in the editor.
|
||||
|
||||
## Add a web link
|
||||
|
||||
Use "Add → Web link", enter a name and a URL starting with `http://` or `https://`, and choose how it opens:
|
||||
|
||||
- **Embed**: inside the console, which suits internal tools and dashboards
|
||||
- **New tab**: in a new browser tab
|
||||
|
||||
<Tip>
|
||||
Some sites refuse to be embedded in other pages and show up blank. Switch the entry to "New tab" instead. An embedded page also has an "Open in new tab" button at its top right.
|
||||
</Tip>
|
||||
|
||||
## Restore default
|
||||
|
||||
Click "Restore default" in the editor to go back to the built-in menu. The artifacts and links you added are removed.
|
||||
|
||||
## Menu file
|
||||
|
||||
The menu is saved in `system/menu.json` under the default Agent's workspace (`~/cow/system/menu.json` for a source deployment), and both the Web Console and the desktop client read it. A page one client does not have is skipped there and kept in the menu for the other.
|
||||
|
||||
The file can also be edited by hand in the format below. A file that cannot be parsed is ignored and the built-in menu is used.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"id": "chat",
|
||||
"title": "",
|
||||
"items": [
|
||||
{ "id": "chat", "type": "builtin", "view": "chat", "title": "", "icon": "", "hidden": false },
|
||||
{ "id": "m_report", "type": "artifact", "title": "Weekly report", "icon": "chart", "path": "/Users/me/cow/websites/report.html" },
|
||||
{ "id": "m_board", "type": "url", "title": "Dashboard", "icon": "gauge", "url": "https://example.com/board", "open": "embed" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Type | Description | Fields |
|
||||
| --- | --- | --- |
|
||||
| `builtin` | A built-in page | `view`: the page name; `hidden`: whether it is hidden |
|
||||
| `artifact` | An artifact | `path`: absolute path of the file, HTML or Markdown only |
|
||||
| `url` | A web link | `url`: the address; `open`: `embed` or `tab` |
|
||||
@@ -40,6 +40,10 @@ Web コンソールは CowAgent のデフォルトチャネルです。起動後
|
||||
サーバーのファイアウォールとセキュリティグループで該当ポートが許可されていることを確認してください。
|
||||
</Note>
|
||||
|
||||
## チャット API
|
||||
|
||||
Web コンソールのポートでは OpenAI 互換のチャット API `/v1/chat/completions` も提供しています。初期状態では無効で、設定ページの「チャット API」カードから有効にして API キーを取得できます。詳しくは [チャット API](/ja/extensions/chat-api) を参照してください。
|
||||
|
||||
## 機能紹介
|
||||
|
||||
### チャット画面
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: チャット API
|
||||
description: OpenAI 互換の /v1/chat/completions エンドポイントから CowAgent を呼び出す
|
||||
---
|
||||
|
||||
> CowAgent は OpenAI Chat Completions プロトコル互換のチャットエンドポイントを、Web コンソールと同じポートで提供します。OpenAI プロトコルに対応したアプリ、SDK、ワークフローからそのまま接続できます。
|
||||
|
||||
モデルを直接呼び出すのとは異なり、エンドポイントの背後では完全な Agent が動作します。ツール、スキル、メモリ、ナレッジベースを使ってタスクを完了し、思考過程、ツール呼び出し、トークン使用量も回答と一緒に返します。
|
||||
|
||||
## API を有効にする
|
||||
|
||||
API は初期状態では無効です。有効にした後は、リクエストに API キーが必要です。
|
||||
|
||||
### コンソールから有効にする
|
||||
|
||||
「設定 → 基本設定 → チャット API」でスイッチをオンにし、確認すると API キーが生成されます。エンドポイントのアドレスとコピーしてすぐ使える呼び出し例(cURL / Python / Node.js)も表示されます。
|
||||
|
||||
{/* TODO: スクリーンショット - 設定ページの「チャット API」カード */}
|
||||
|
||||
- **再生成**:新しい API キーを発行します。古いキーはすぐに無効になります
|
||||
- **スイッチをオフ**:API の提供を停止します。API キーは保持され、再度オンにするとそのまま使えます
|
||||
|
||||
### 設定ファイルから有効にする
|
||||
|
||||
`config.json` に次の 2 項目を設定し、サービスを再起動します:
|
||||
|
||||
```json
|
||||
{
|
||||
"external_api_enabled": true,
|
||||
"external_api_token": "sk-your-random-key"
|
||||
}
|
||||
```
|
||||
|
||||
| 設定項目 | 説明 |
|
||||
| --- | --- |
|
||||
| `external_api_enabled` | API を有効にするかどうか。デフォルトは `false` |
|
||||
| `external_api_token` | API キー。十分に長いランダムな文字列を推奨します。API を無効にしても保持されます |
|
||||
|
||||
<Note>
|
||||
API は Web コンソールと同じアドレスとポートを使います。デフォルトでは `127.0.0.1` のみで待ち受けるため、このマシンからしかアクセスできません。他のマシンから呼び出す場合は、[Web コンソール](/ja/channels/web) を参照して `web_host` を `0.0.0.0` に設定し、ポートを開放してください。API キーはコンソールのパスワード `web_password` とは独立しています。
|
||||
</Note>
|
||||
|
||||
## クイックスタート
|
||||
|
||||
以下の例はサービスがこのマシンの `9899` ポートで動作していることを前提とします。モデルも Agent も指定しないため、デフォルト Agent が自身のデフォルトモデルで回答します。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"messages": [{"role": "user", "content": "ワークスペースにあるファイルをまとめて"}]}'
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://localhost:9899/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer your-api-key"},
|
||||
json={"messages": [{"role": "user", "content": "ワークスペースにあるファイルをまとめて"}]},
|
||||
)
|
||||
print(resp.json()["choices"][0]["message"]["content"])
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node.js">
|
||||
```javascript
|
||||
const resp = await fetch("http://localhost:9899/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: { "Authorization": "Bearer your-api-key", "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
messages: [{ role: "user", content: "ワークスペースにあるファイルをまとめて" }],
|
||||
}),
|
||||
});
|
||||
const data = await resp.json();
|
||||
console.log(data.choices[0].message.content);
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## エンドポイント
|
||||
|
||||
### 認証
|
||||
|
||||
すべてのリクエストのヘッダーに API キーを含めます:
|
||||
|
||||
```
|
||||
Authorization: Bearer <API キー>
|
||||
```
|
||||
|
||||
### チャット
|
||||
|
||||
`POST /v1/chat/completions`
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `messages` | array | はい | 会話メッセージ。`system`、`developer`、`user`、`assistant` ロールに対応。`content` は文字列、または `type` が `text` のパーツ配列 |
|
||||
| `model` | string | いいえ | 回答に使うモデル。省略時は Agent のデフォルトモデル。[モデル一覧](#モデル一覧) を参照 |
|
||||
| `agent_id` | string | いいえ | CowAgent 拡張パラメータ。回答する Agent を指定します。省略時はデフォルト Agent。[Agent の指定](#agent-の指定) を参照 |
|
||||
| `stream` | boolean | いいえ | ストリーミング出力するかどうか。デフォルトは `false` |
|
||||
| `stream_options` | object | いいえ | `{"include_usage": true}` を指定すると、ストリームの最後にトークン使用量を返します |
|
||||
| `conversation_id` | string | いいえ | CowAgent 拡張パラメータ。指定するとサーバーが会話履歴を保持します。[会話の管理](#会話の管理) を参照 |
|
||||
|
||||
Agent は自身のツールとシステムプロンプトを使うため、`temperature`、`max_tokens`、`tools` などのパラメータは無視されます。
|
||||
|
||||
### Agent の指定
|
||||
|
||||
[マルチエージェント](/ja/multi-agent/team) を設定している場合、`agent_id` でどの Agent に回答させるかを指定できます。Agent ID は「エージェント」ページの名前の下に表示され、横のアイコンでコピーできます。`default` は常にデフォルト Agent を表します。存在しない、またはアーカイブ済みの Agent を指定すると 404 が返ります。
|
||||
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"agent_id": "research",
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "最近のオープンソース Agent フレームワークを調べて"}]
|
||||
}'
|
||||
```
|
||||
|
||||
`model` はそのリクエストにのみ適用され、Agent や会話のモデル設定は変わりません。
|
||||
|
||||
### モデル一覧
|
||||
|
||||
`GET /v1/models`
|
||||
|
||||
利用できるモデル、つまり「設定 → モデル設定」で設定済みのプロバイダーのチャットモデルを返します。`owned_by` はプロバイダーです。一覧にない `model` を指定すると 404 が返ります。
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{ "id": "deepseek-chat", "object": "model", "created": 0, "owned_by": "deepseek" },
|
||||
{ "id": "qwen-max", "object": "model", "created": 0, "owned_by": "dashscope" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 会話の管理
|
||||
|
||||
API は 2 つの会話方式に対応しています:
|
||||
|
||||
- **ステートレス(デフォルト)**:`conversation_id` を指定せず、通常のモデルと同じように毎回 `messages` 全体を送ります。最後の `user` メッセージが今回の入力、それ以前の `user` / `assistant` メッセージがコンテキストになります。CowAgent の会話履歴には書き込まれません。
|
||||
- **ステートフル**:`conversation_id` を指定すると、会話履歴を CowAgent が保持します。以降は新しいメッセージだけを送ればよく、`messages` 内の以前のメッセージは無視されます。同じ会話のリクエストは順番に 1 つずつ処理されます。
|
||||
|
||||
```json
|
||||
{
|
||||
"conversation_id": "order-bot-user-42",
|
||||
"messages": [{ "role": "user", "content": "前回の続きをお願い" }]
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
リクエスト内の `system` メッセージは無視され、Agent は常に自身のペルソナ、スキル、メモリを使います。どちらの方式でも、Agent はツールを通じてワークスペースとメモリファイルを読み書きできます。
|
||||
</Note>
|
||||
|
||||
## レスポンス形式
|
||||
|
||||
### 非ストリーミング
|
||||
|
||||
標準の OpenAI レスポンスに加えて、`message` に思考過程とツール呼び出しの記録が含まれます:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-82c19c44...",
|
||||
"object": "chat.completion",
|
||||
"created": 1791861130,
|
||||
"model": "deepseek-chat",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "ワークスペースには 4 つのコアファイルと 7 つのフォルダがあります……",
|
||||
"reasoning_content": "ユーザーはワークスペースの内容を知りたいので、まず一覧を取得する……",
|
||||
"tool_trace": [
|
||||
{
|
||||
"type": "tool_result",
|
||||
"id": "call_00_wYInMZ...",
|
||||
"name": "ls",
|
||||
"arguments": { "path": "." },
|
||||
"result": "AGENT.md\nMEMORY.md\n...",
|
||||
"status": "success",
|
||||
"elapsed": "0.01s"
|
||||
}
|
||||
]
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 18151,
|
||||
"completion_tokens": 180,
|
||||
"total_tokens": 18331,
|
||||
"prompt_tokens_details": { "cached_tokens": 11392 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 説明 |
|
||||
| --- | --- |
|
||||
| `model` | 回答に使われたモデル |
|
||||
| `message.content` | 最終的な回答 |
|
||||
| `message.reasoning_content` | 思考過程。ディープシンキングが有効で、モデルが対応している場合に返されます |
|
||||
| `message.tool_trace` | CowAgent 拡張フィールド。ツール呼び出しの記録で、各呼び出しの引数、結果、ステータス、所要時間を含みます。他の Agent への委任や生成されたファイルもここに記録され、形式は下記の [プロセスイベント](#プロセスイベント) と同じです |
|
||||
| `usage` | トークン使用量。Agent は 1 ターンでモデルを複数回呼び出すことがあり、ここはその合計です。`prompt_tokens_details.cached_tokens` はキャッシュから提供された入力トークン数です。モデルが使用量を返さない場合は含まれません |
|
||||
|
||||
### ストリーミング
|
||||
|
||||
`"stream": true` を指定すると、標準の `chat.completion.chunk` を SSE で返します:
|
||||
|
||||
- 回答は `choices[0].delta.content`
|
||||
- 思考過程は `choices[0].delta.reasoning_content`
|
||||
- ツール呼び出しなどのプロセスイベントはトップレベルの `cow_event` フィールドにあり、このとき `delta` は空です
|
||||
- 最後のチャンクの `finish_reason` は `stop`。`stream_options.include_usage` を指定した場合は、続けて `choices` が空で `usage` を含むチャンクが送られます
|
||||
- `data: [DONE]` で終了します
|
||||
|
||||
```
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":null}],"cow_event":{"type":"tool_start","id":"call_1","name":"ls","arguments":{"path":"."}}}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"ワークスペースには"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":18151,"completion_tokens":180,"total_tokens":18331,"prompt_tokens_details":{"cached_tokens":11392}}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
ストリームを読み取る例:
|
||||
|
||||
```python
|
||||
import json
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://localhost:9899/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer your-api-key"},
|
||||
json={"messages": [{"role": "user", "content": "ワークスペースにあるファイルをまとめて"}], "stream": True},
|
||||
stream=True,
|
||||
)
|
||||
for line in resp.iter_lines(decode_unicode=True):
|
||||
if not line.startswith("data: ") or line == "data: [DONE]":
|
||||
continue
|
||||
chunk = json.loads(line[len("data: "):])
|
||||
event = chunk.get("cow_event")
|
||||
if event and event["type"] == "tool_start":
|
||||
print(f"\n[ツール呼び出し] {event['name']} {event['arguments']}")
|
||||
for choice in chunk["choices"]:
|
||||
print(choice["delta"].get("content") or "", end="", flush=True)
|
||||
```
|
||||
|
||||
### プロセスイベント
|
||||
|
||||
`cow_event.type` は次の値を取ります:
|
||||
|
||||
| タイプ | 説明 | 主なフィールド |
|
||||
| --- | --- | --- |
|
||||
| `tool_start` | ツール呼び出しの開始 | `id`、`name`、`arguments` |
|
||||
| `tool_result` | ツール呼び出しの完了 | `id`、`name`、`arguments`、`result`、`status`、`elapsed` |
|
||||
| `subagent_step` | サブ Agent が実行したステップ | `card_id`、`step_id`、`phase`、`tool`、`status` |
|
||||
| `peer_start` | タスクを他の Agent に委任。以降の回答はその Agent によるもの | `card_id`、`agent_id`、`agent_name` |
|
||||
| `peer_end` | 委任先の Agent の処理が完了 | `card_id`、`agent_id`、`status` |
|
||||
| `artifact` | Agent が生成したファイル | `artifact` |
|
||||
| `reasoning` | 思考過程。`delta.reasoning_content` と同じ内容 | `delta` |
|
||||
| `error` | 実行エラー | `message` |
|
||||
|
||||
## エラー処理
|
||||
|
||||
エラーレスポンスは OpenAI の形式に従います:
|
||||
|
||||
```json
|
||||
{ "error": { "message": "Invalid authentication credentials.", "type": "api_error", "code": "invalid_api_key" } }
|
||||
```
|
||||
|
||||
| ステータス | `code` | 説明 |
|
||||
| --- | --- | --- |
|
||||
| 400 | `invalid_request` / `invalid_json` | リクエストの形式が不正 |
|
||||
| 401 | `invalid_api_key` | API キーが正しくない |
|
||||
| 404 | `model_not_found` | `model` が [モデル一覧](#モデル一覧) にない |
|
||||
| 404 | `agent_not_found` | `agent_id` で指定した Agent が存在しない、またはアーカイブ済み |
|
||||
| 503 | `api_disabled` | API が無効 |
|
||||
| 500 | `internal_error` / `timeout` | Agent の実行失敗またはタイムアウト |
|
||||
|
||||
ストリーミングリクエストは、Agent が最初のイベントを生成するまで最大 30 秒待機します。それまでにエラーやタイムアウトが発生した場合は上表の JSON エラーを返し、タイムアウト時はそのリクエストをキャンセルします。ストリームの開始後は HTTP ステータスを変更できないため、以降のエラーは `cow_event.type=error` のチャンクとして返され、続いて `finish_reason=error` の終了チャンクと `data: [DONE]` が送られます。
|
||||
|
||||
## 他のアプリから使う
|
||||
|
||||
カスタム OpenAI エンドポイントに対応したクライアントやプラットフォームなら、そのまま接続できます。「OpenAI」または「OpenAI 互換」タイプを選び、次のように入力します:
|
||||
|
||||
- **API アドレス**:`http://<サーバーアドレス>:9899/v1`(アドレスとポートだけを入力し、`/v1` を自動で補うアプリもあります)
|
||||
- **API キー**:コンソールで生成したキー
|
||||
- **モデル**:[モデル一覧](#モデル一覧) から選択
|
||||
|
||||
これらのアプリからのリクエストにはデフォルト Agent が回答します。
|
||||
|
||||
API はクロスオリジンリクエスト(CORS)に対応しているため、ブラウザ上のアプリからも直接呼び出せます。
|
||||
|
||||
<Note>
|
||||
現在はテキスト入力のみに対応しており、画像などのマルチモーダルコンテンツには対応していません。呼び出し側が `tools` を渡す関数呼び出しにも対応しておらず、Agent は自身のツールでタスクを完了します。
|
||||
</Note>
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: カスタムメニュー
|
||||
description: サイドバーメニューの順序とグループを調整し、よく使う成果物や Web ページをメニューに追加する
|
||||
---
|
||||
|
||||
サイドバーメニューは使い方に合わせて調整できます。並べ替えやグループ分け、あまり使わないページの非表示に加えて、Agent が作成した Web ページやドキュメント、任意の URL をメニューに追加し、組み込みページと同じようにワンクリックで開けます。Web コンソールとデスクトップクライアントは同じメニューを共有します。
|
||||
|
||||
{/* TODO: スクリーンショット - カスタマイズ後のサイドバー */}
|
||||
|
||||
## メニューエディタを開く
|
||||
|
||||
入口は 3 つあります:
|
||||
|
||||
- サイドバーのグループ名にマウスを重ね、右側に表示される編集アイコンをクリック
|
||||
- サイドバー左下のバージョン番号(Web コンソール)または「その他」ボタン(デスクトップクライアント)をクリックし、「メニューをカスタマイズ」を選択
|
||||
- 「成果物」ページで Web ページや Markdown ドキュメントをプレビュー中に「メニューに追加」をクリック
|
||||
|
||||
{/* TODO: スクリーンショット - メニューエディタ */}
|
||||
|
||||
エディタで変更した後、保存をクリックすると反映されます。
|
||||
|
||||
## 並べ替えとグループ
|
||||
|
||||
- 各項目左側のドラッグハンドルを押さえて順序を変更できます。別のグループへ移動することもできます
|
||||
- 「グループを追加」で新しいグループを作成でき、グループ名はその場で編集できます
|
||||
- グループを削除する前に、その中の組み込みページを別のグループへ移動してください
|
||||
|
||||
## 組み込みページ
|
||||
|
||||
チャット、エージェント、設定、スキルなどの組み込みページでは次の操作ができます:
|
||||
|
||||
- **名前とアイコンの変更**:名前を空にするとデフォルトの名前になります
|
||||
- **非表示**:目のアイコンで非表示・再表示を切り替えます。「チャット」と「設定」は、いつでも設定に戻れるよう非表示にできません
|
||||
- **再追加**:削除した組み込みページは「追加 → 組み込みページ」から戻せます
|
||||
|
||||
「成果物」ページは初期状態では非表示です。必要なときにエディタで表示してください。
|
||||
|
||||
## 成果物を追加
|
||||
|
||||
「追加 → 成果物(HTML / Markdown)」で Agent が作成した Web ページや Markdown ドキュメントを検索し、メニューに追加できます。メニュー項目をクリックするとコンソール内で直接開き、ファイルが更新されれば常に最新の内容が表示されます。Agent が更新し続けるレポートやダッシュボード、ナレッジドキュメントの置き場所に適しています。
|
||||
|
||||
ファイルが削除されると、メニュー項目にその旨が表示されます。エディタから削除してください。
|
||||
|
||||
## Web リンクを追加
|
||||
|
||||
「追加 → Web リンク」で名前と `http://` または `https://` で始まる URL を入力し、開き方を選びます:
|
||||
|
||||
- **埋め込み**:コンソール内で開きます。社内システムやダッシュボードに適しています
|
||||
- **新しいタブ**:ブラウザの新しいタブで開きます
|
||||
|
||||
<Tip>
|
||||
他のページへの埋め込みを禁止しているサイトは、埋め込み表示すると空白になります。その場合は「新しいタブ」に切り替えてください。埋め込みページの右上にも「新しいタブで開く」ボタンがあります。
|
||||
</Tip>
|
||||
|
||||
## デフォルトに戻す
|
||||
|
||||
エディタで「デフォルトに戻す」をクリックすると、組み込みのメニューに戻ります。追加した成果物とリンクは削除されます。
|
||||
|
||||
## メニューファイル
|
||||
|
||||
メニューはデフォルト Agent のワークスペース内の `system/menu.json` に保存され(ソースからのデプロイでは `~/cow/system/menu.json`)、Web コンソールとデスクトップクライアントの両方が読み込みます。一方のクライアントにないページはそのクライアントではスキップされ、もう一方のためにメニューに残ります。
|
||||
|
||||
ファイルは次の形式で手動編集することもできます。解析できないファイルは無視され、組み込みのメニューが使われます。
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"id": "chat",
|
||||
"title": "",
|
||||
"items": [
|
||||
{ "id": "chat", "type": "builtin", "view": "chat", "title": "", "icon": "", "hidden": false },
|
||||
{ "id": "m_report", "type": "artifact", "title": "週報", "icon": "chart", "path": "/Users/me/cow/websites/report.html" },
|
||||
{ "id": "m_board", "type": "url", "title": "ダッシュボード", "icon": "gauge", "url": "https://example.com/board", "open": "embed" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| タイプ | 説明 | フィールド |
|
||||
| --- | --- | --- |
|
||||
| `builtin` | 組み込みページ | `view`:ページ名、`hidden`:非表示かどうか |
|
||||
| `artifact` | 成果物 | `path`:ファイルの絶対パス。HTML と Markdown のみ |
|
||||
| `url` | Web リンク | `url`:アドレス、`open`:`embed`(埋め込み)または `tab`(新しいタブ) |
|
||||
@@ -40,6 +40,10 @@ Web 控制台是 CowAgent 的默认通道,启动后会自动运行,通过浏
|
||||
请确保服务器防火墙和安全组已放行对应端口。
|
||||
</Note>
|
||||
|
||||
## 对话 API
|
||||
|
||||
Web 控制台的端口同时提供 OpenAI 兼容的对话接口 `/v1/chat/completions`,默认关闭,可在「配置」页的「对话 API」卡片中开启并获取 API Key。详见 [对话 API](/zh/extensions/chat-api)。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
### 对话界面
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: 对话 API
|
||||
description: 通过 OpenAI 兼容的 /v1/chat/completions 接口调用 CowAgent
|
||||
---
|
||||
|
||||
> CowAgent 提供与 OpenAI Chat Completions 协议兼容的对话接口,复用 Web 控制台的端口。任何支持 OpenAI 协议的应用、SDK 或工作流都可以直接接入。
|
||||
|
||||
和直接调用模型不同,接口背后是完整的 Agent:它会使用工具、技能、记忆和知识库完成任务,并把思考过程、工具调用和 Token 用量一并返回。
|
||||
|
||||
## 开启接口
|
||||
|
||||
接口默认关闭,开启后需在请求中携带 API Key。
|
||||
|
||||
### 在控制台开启
|
||||
|
||||
进入「配置 → 基础配置 → 对话 API」,打开开关并确认后会自动生成 API Key,并给出接口地址和调用示例(cURL / Python / Node.js),可直接复制使用。
|
||||
|
||||
{/* TODO: 截图 - 配置页「对话 API」卡片 */}
|
||||
|
||||
- **重新生成**:生成新的 API Key,旧 Key 立即失效
|
||||
- **关闭开关**:接口停止服务,API Key 会保留,再次开启后可继续使用
|
||||
|
||||
### 在配置文件开启
|
||||
|
||||
在 `config.json` 中配置以下两项,修改后需重启服务生效:
|
||||
|
||||
```json
|
||||
{
|
||||
"external_api_enabled": true,
|
||||
"external_api_token": "sk-your-random-key"
|
||||
}
|
||||
```
|
||||
|
||||
| 配置项 | 说明 |
|
||||
| --- | --- |
|
||||
| `external_api_enabled` | 是否开启接口,默认 `false` |
|
||||
| `external_api_token` | API Key,建议使用足够长的随机字符串。关闭接口时会保留 |
|
||||
|
||||
<Note>
|
||||
接口与 Web 控制台使用同一地址和端口。默认只监听 `127.0.0.1`,仅本机可以访问;如需其他机器调用,请参考 [Web 控制台](/zh/channels/web) 将 `web_host` 设为 `0.0.0.0`,并放行端口。API Key 与控制台访问密码 `web_password` 相互独立。
|
||||
</Note>
|
||||
|
||||
## 快速开始
|
||||
|
||||
以下示例假设服务运行在本机 `9899` 端口,不指定模型和智能体,由默认智能体使用它的默认模型回答。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="cURL">
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"messages": [{"role": "user", "content": "总结一下工作空间里有哪些文件"}]}'
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://localhost:9899/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer your-api-key"},
|
||||
json={"messages": [{"role": "user", "content": "总结一下工作空间里有哪些文件"}]},
|
||||
)
|
||||
print(resp.json()["choices"][0]["message"]["content"])
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node.js">
|
||||
```javascript
|
||||
const resp = await fetch("http://localhost:9899/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: { "Authorization": "Bearer your-api-key", "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
messages: [{ role: "user", content: "总结一下工作空间里有哪些文件" }],
|
||||
}),
|
||||
});
|
||||
const data = await resp.json();
|
||||
console.log(data.choices[0].message.content);
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## 接口说明
|
||||
|
||||
### 认证
|
||||
|
||||
所有请求都需要在请求头中携带 API Key:
|
||||
|
||||
```
|
||||
Authorization: Bearer <API Key>
|
||||
```
|
||||
|
||||
### 对话
|
||||
|
||||
`POST /v1/chat/completions`
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `messages` | array | 是 | 对话消息,支持 `system`、`developer`、`user`、`assistant` 角色;`content` 可以是字符串,或 `type` 为 `text` 的内容数组 |
|
||||
| `model` | string | 否 | 本次回答使用的模型,不传时使用智能体的默认模型。可用值见 [模型列表](#模型列表) |
|
||||
| `agent_id` | string | 否 | CowAgent 扩展参数,指定由哪个智能体回答,不传时使用默认智能体,见 [指定智能体](#指定智能体) |
|
||||
| `stream` | boolean | 否 | 是否流式输出,默认 `false` |
|
||||
| `stream_options` | object | 否 | 设置 `{"include_usage": true}` 后,流式输出会在最后返回 Token 用量 |
|
||||
| `conversation_id` | string | 否 | CowAgent 扩展参数,指定后由服务端保存会话历史,见 [会话管理](#会话管理) |
|
||||
|
||||
Agent 使用自己的工具和系统提示词,因此 `temperature`、`max_tokens`、`tools` 等参数会被忽略。
|
||||
|
||||
### 指定智能体
|
||||
|
||||
配置了 [多智能体](/zh/multi-agent/team) 时,可以通过 `agent_id` 指定由哪个智能体回答。智能体 ID 显示在「智能体」页面的名称下方,点击旁边的图标即可复制。`default` 始终表示默认智能体,指定的智能体不存在或已归档时返回 404。
|
||||
|
||||
```bash
|
||||
curl http://localhost:9899/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"agent_id": "research",
|
||||
"model": "deepseek-chat",
|
||||
"messages": [{"role": "user", "content": "调研一下最近的开源 Agent 框架"}]
|
||||
}'
|
||||
```
|
||||
|
||||
`model` 只对本次请求生效,不会修改智能体或会话的模型设置。
|
||||
|
||||
### 模型列表
|
||||
|
||||
`GET /v1/models`
|
||||
|
||||
返回可用的模型,即「配置 → 模型配置」中已配置的厂商下的对话模型,`owned_by` 为所属厂商。请求中的 `model` 不在列表中时返回 404。
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{ "id": "deepseek-chat", "object": "model", "created": 0, "owned_by": "deepseek" },
|
||||
{ "id": "qwen-max", "object": "model", "created": 0, "owned_by": "dashscope" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 会话管理
|
||||
|
||||
接口支持两种会话方式:
|
||||
|
||||
- **无状态(默认)**:不传 `conversation_id`,与调用普通模型一样,每次请求带上完整的 `messages`。最后一条 `user` 消息作为本轮输入,之前的 `user` / `assistant` 消息作为上下文。本次对话不会写入 CowAgent 的会话记录。
|
||||
- **有状态**:传入 `conversation_id`,会话历史由 CowAgent 保存,之后每次只需发送新消息,`messages` 中更早的消息会被忽略。同一会话的请求按顺序依次处理。
|
||||
|
||||
```json
|
||||
{
|
||||
"conversation_id": "order-bot-user-42",
|
||||
"messages": [{ "role": "user", "content": "继续上次的话题" }]
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
请求中的 `system` 消息会被忽略,Agent 始终使用自己的人设、技能和记忆。无论哪种会话方式,Agent 都可以通过工具读写工作空间和记忆文件。
|
||||
</Note>
|
||||
|
||||
## 响应格式
|
||||
|
||||
### 非流式
|
||||
|
||||
在标准的 OpenAI 响应之外,`message` 中会附带思考过程和工具调用记录:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-82c19c44...",
|
||||
"object": "chat.completion",
|
||||
"created": 1791861130,
|
||||
"model": "deepseek-chat",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "工作空间里有 4 个核心文件和 7 个目录……",
|
||||
"reasoning_content": "用户想了解工作空间的内容,先列出目录……",
|
||||
"tool_trace": [
|
||||
{
|
||||
"type": "tool_result",
|
||||
"id": "call_00_wYInMZ...",
|
||||
"name": "ls",
|
||||
"arguments": { "path": "." },
|
||||
"result": "AGENT.md\nMEMORY.md\n...",
|
||||
"status": "success",
|
||||
"elapsed": "0.01s"
|
||||
}
|
||||
]
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 18151,
|
||||
"completion_tokens": 180,
|
||||
"total_tokens": 18331,
|
||||
"prompt_tokens_details": { "cached_tokens": 11392 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 本次回答使用的模型 |
|
||||
| `message.content` | 最终回复 |
|
||||
| `message.reasoning_content` | 思考过程,开启深度思考且模型支持时返回 |
|
||||
| `message.tool_trace` | CowAgent 扩展字段,工具调用记录,每次调用包含参数、结果、状态和耗时;委派给其他智能体、生成文件等过程也会记录在这里,格式同下方的 [过程事件](#过程事件) |
|
||||
| `usage` | Token 用量。Agent 完成一轮任务可能多次调用模型,这里是所有调用的总和;`prompt_tokens_details.cached_tokens` 为命中缓存的输入 Token。模型未返回用量时不包含此字段 |
|
||||
|
||||
### 流式
|
||||
|
||||
设置 `"stream": true` 后以 SSE 返回标准的 `chat.completion.chunk`:
|
||||
|
||||
- 回复内容在 `choices[0].delta.content`
|
||||
- 思考过程在 `choices[0].delta.reasoning_content`
|
||||
- 工具调用等过程事件在顶层的 `cow_event` 字段,此时 `delta` 为空
|
||||
- 最后一个数据块的 `finish_reason` 为 `stop`;如设置了 `stream_options.include_usage`,随后会再发送一个 `choices` 为空、带 `usage` 的数据块
|
||||
- 以 `data: [DONE]` 结束
|
||||
|
||||
```
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":null}],"cow_event":{"type":"tool_start","id":"call_1","name":"ls","arguments":{"path":"."}}}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"工作空间里有"},"finish_reason":null}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
||||
|
||||
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":18151,"completion_tokens":180,"total_tokens":18331,"prompt_tokens_details":{"cached_tokens":11392}}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
读取流式输出的示例:
|
||||
|
||||
```python
|
||||
import json
|
||||
import requests
|
||||
|
||||
resp = requests.post(
|
||||
"http://localhost:9899/v1/chat/completions",
|
||||
headers={"Authorization": "Bearer your-api-key"},
|
||||
json={"messages": [{"role": "user", "content": "总结一下工作空间里有哪些文件"}], "stream": True},
|
||||
stream=True,
|
||||
)
|
||||
for line in resp.iter_lines(decode_unicode=True):
|
||||
if not line.startswith("data: ") or line == "data: [DONE]":
|
||||
continue
|
||||
chunk = json.loads(line[len("data: "):])
|
||||
event = chunk.get("cow_event")
|
||||
if event and event["type"] == "tool_start":
|
||||
print(f"\n[调用工具] {event['name']} {event['arguments']}")
|
||||
for choice in chunk["choices"]:
|
||||
print(choice["delta"].get("content") or "", end="", flush=True)
|
||||
```
|
||||
|
||||
### 过程事件
|
||||
|
||||
`cow_event.type` 的取值如下:
|
||||
|
||||
| 类型 | 说明 | 主要字段 |
|
||||
| --- | --- | --- |
|
||||
| `tool_start` | 开始调用工具 | `id`、`name`、`arguments` |
|
||||
| `tool_result` | 工具调用完成 | `id`、`name`、`arguments`、`result`、`status`、`elapsed` |
|
||||
| `subagent_step` | 子 Agent 执行的步骤 | `card_id`、`step_id`、`phase`、`tool`、`status` |
|
||||
| `peer_start` | 任务委派给其他智能体,之后的回复内容来自该智能体 | `card_id`、`agent_id`、`agent_name` |
|
||||
| `peer_end` | 被委派的智能体处理结束 | `card_id`、`agent_id`、`status` |
|
||||
| `artifact` | Agent 生成的文件 | `artifact` |
|
||||
| `reasoning` | 思考过程,与 `delta.reasoning_content` 内容相同 | `delta` |
|
||||
| `error` | 执行出错 | `message` |
|
||||
|
||||
## 错误处理
|
||||
|
||||
错误响应遵循 OpenAI 格式:
|
||||
|
||||
```json
|
||||
{ "error": { "message": "Invalid authentication credentials.", "type": "api_error", "code": "invalid_api_key" } }
|
||||
```
|
||||
|
||||
| 状态码 | `code` | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 400 | `invalid_request` / `invalid_json` | 请求格式错误 |
|
||||
| 401 | `invalid_api_key` | API Key 错误 |
|
||||
| 404 | `model_not_found` | `model` 不在 [模型列表](#模型列表) 中 |
|
||||
| 404 | `agent_not_found` | `agent_id` 指定的智能体不存在或已归档 |
|
||||
| 503 | `api_disabled` | 接口未开启 |
|
||||
| 500 | `internal_error` / `timeout` | Agent 执行失败或超时 |
|
||||
|
||||
流式请求会等待 Agent 产生第一个事件,最长 30 秒:在此之前出错或超时,返回上表中的 JSON 错误,超时时会取消该请求。流式输出开始后 HTTP 状态码无法再修改,之后的错误会以 `cow_event.type=error` 的数据块返回,随后是 `finish_reason=error` 的结束块和 `data: [DONE]`。
|
||||
|
||||
## 接入第三方应用
|
||||
|
||||
支持自定义 OpenAI 接口的客户端和平台都可以直接接入,选择「OpenAI」或「OpenAI 兼容」类型,然后填写:
|
||||
|
||||
- **API 地址**:`http://<服务器地址>:9899/v1`(部分应用只需填到端口,会自动补全 `/v1`)
|
||||
- **API Key**:控制台生成的 Key
|
||||
- **模型**:从 [模型列表](#模型列表) 中选择
|
||||
|
||||
通过第三方应用接入时由默认智能体回答。
|
||||
|
||||
接口支持跨域访问(CORS),也可以在浏览器端的应用中直接调用。
|
||||
|
||||
<Note>
|
||||
目前仅支持文本输入,暂不支持图片等多模态内容;也不支持由调用方传入 `tools` 进行函数调用,Agent 会使用自己的工具完成任务。
|
||||
</Note>
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: 自定义菜单
|
||||
description: 调整侧边栏菜单的顺序和分组,把常用的产物和网页放进菜单
|
||||
---
|
||||
|
||||
侧边栏菜单可以按自己的习惯调整:重新排序和分组、隐藏不常用的页面,还可以把 Agent 生成的网页、文档或任意网址放进菜单,像内置页面一样一键打开。Web 控制台和桌面客户端共用同一份菜单。
|
||||
|
||||
{/* TODO: 截图 - 自定义后的侧边栏 */}
|
||||
|
||||
## 打开菜单编辑器
|
||||
|
||||
有三个入口:
|
||||
|
||||
- 鼠标悬停在侧边栏的分组标题上,点击右侧出现的编辑图标
|
||||
- 点击侧边栏左下角的版本号(Web 控制台)或「更多」按钮(桌面客户端),选择「自定义菜单」
|
||||
- 在「产物」页预览网页或 Markdown 文档时,点击「添加到菜单」
|
||||
|
||||
{/* TODO: 截图 - 菜单编辑器 */}
|
||||
|
||||
在编辑器中修改后点击保存即可生效。
|
||||
|
||||
## 排序与分组
|
||||
|
||||
- 按住每一项左侧的拖动手柄,可以调整顺序,也可以拖到其他分组
|
||||
- 点击「添加分组」新建分组,分组名称可直接修改
|
||||
- 删除分组前,需要先把其中的内置页面移到其他分组
|
||||
|
||||
## 内置页面
|
||||
|
||||
对话、智能体、配置、能力等内置页面可以:
|
||||
|
||||
- **改名和换图标**:名称留空时使用默认名称
|
||||
- **隐藏**:点击眼睛图标隐藏或重新显示。「对话」和「配置」不能隐藏,保证随时能回到设置
|
||||
- **重新加回**:移除的内置页面可以在「添加 → 内置页面」中找回
|
||||
|
||||
「产物」页面默认隐藏,需要时在编辑器中显示即可。
|
||||
|
||||
## 添加产物
|
||||
|
||||
通过「添加 → 产物(HTML / Markdown)」搜索 Agent 生成的网页或 Markdown 文档并放进菜单。点击菜单项会在控制台内直接打开,文件更新后看到的也是最新内容,适合放置 Agent 维护的报表、看板和知识文档。
|
||||
|
||||
文件被删除后,菜单项会提示文件已不存在,可在编辑器中移除。
|
||||
|
||||
## 添加网页链接
|
||||
|
||||
通过「添加 → 网页链接」填写名称和以 `http://` 或 `https://` 开头的地址,并选择打开方式:
|
||||
|
||||
- **内嵌**:在控制台内打开,适合内部系统、数据看板等
|
||||
- **新标签**:在新的浏览器标签页中打开
|
||||
|
||||
<Tip>
|
||||
部分网站禁止被其他页面内嵌,内嵌打开时会显示空白,改为「新标签」即可。内嵌页面的右上角也提供「新标签打开」按钮。
|
||||
</Tip>
|
||||
|
||||
## 恢复默认
|
||||
|
||||
在编辑器中点击「恢复默认」,菜单会恢复为内置的默认菜单,添加的产物和链接会被移除。
|
||||
|
||||
## 菜单文件
|
||||
|
||||
菜单保存在默认智能体工作空间的 `system/menu.json` 中(源码部署默认为 `~/cow/system/menu.json`),由 Web 控制台和桌面客户端共同读取。某一端没有的页面会在该端跳过,同时保留在菜单中供另一端使用。
|
||||
|
||||
文件也可以手动编辑,格式如下;无法解析时会忽略该文件并使用默认菜单。
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"id": "chat",
|
||||
"title": "",
|
||||
"items": [
|
||||
{ "id": "chat", "type": "builtin", "view": "chat", "title": "", "icon": "", "hidden": false },
|
||||
{ "id": "m_report", "type": "artifact", "title": "周报", "icon": "chart", "path": "/Users/me/cow/websites/report.html" },
|
||||
{ "id": "m_board", "type": "url", "title": "看板", "icon": "gauge", "url": "https://example.com/board", "open": "embed" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 类型 | 说明 | 字段 |
|
||||
| --- | --- | --- |
|
||||
| `builtin` | 内置页面 | `view` 页面名称,`hidden` 是否隐藏 |
|
||||
| `artifact` | 产物 | `path` 文件的绝对路径,仅支持 HTML 和 Markdown |
|
||||
| `url` | 网页链接 | `url` 网页地址,`open` 为 `embed`(内嵌)或 `tab`(新标签) |
|
||||
Reference in new issue
Block a user