chore: 进行一些修复和文档修改
This commit is contained in:
parent
9a0a55e392
commit
4540bb9f41
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from . import db
|
from . import db
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,8 +26,8 @@ class Conversation(db.Model):
|
||||||
temperature = db.Column(db.Float, nullable=False, default=1.0)
|
temperature = db.Column(db.Float, nullable=False, default=1.0)
|
||||||
max_tokens = db.Column(db.Integer, nullable=False, default=65536)
|
max_tokens = db.Column(db.Integer, nullable=False, default=65536)
|
||||||
thinking_enabled = db.Column(db.Boolean, default=False)
|
thinking_enabled = db.Column(db.Boolean, default=False)
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
messages = db.relationship("Message", backref="conversation", lazy="dynamic",
|
messages = db.relationship("Message", backref="conversation", lazy="dynamic",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
|
|
@ -49,7 +49,7 @@ class Message(db.Model):
|
||||||
tool_call_id = db.Column(db.String(64)) # Tool call ID (tool messages)
|
tool_call_id = db.Column(db.String(64)) # Tool call ID (tool messages)
|
||||||
name = db.Column(db.String(64)) # Tool name (tool messages)
|
name = db.Column(db.String(64)) # Tool name (tool messages)
|
||||||
|
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
class TokenUsage(db.Model):
|
class TokenUsage(db.Model):
|
||||||
|
|
@ -62,7 +62,7 @@ class TokenUsage(db.Model):
|
||||||
prompt_tokens = db.Column(db.Integer, default=0)
|
prompt_tokens = db.Column(db.Integer, default=0)
|
||||||
completion_tokens = db.Column(db.Integer, default=0)
|
completion_tokens = db.Column(db.Integer, default=0)
|
||||||
total_tokens = db.Column(db.Integer, default=0)
|
total_tokens = db.Column(db.Integer, default=0)
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.UniqueConstraint("user_id", "date", "model", name="uq_user_date_model"),
|
db.UniqueConstraint("user_id", "date", "model", name="uq_user_date_model"),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Tool helper services"""
|
"""Tool helper services"""
|
||||||
from typing import List, Dict, Optional, Any
|
from typing import List, Dict, Optional, Any
|
||||||
|
from ddgs import DDGS
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,10 +39,6 @@ class SearchService:
|
||||||
region: str
|
region: str
|
||||||
) -> List[dict]:
|
) -> List[dict]:
|
||||||
"""DuckDuckGo search"""
|
"""DuckDuckGo search"""
|
||||||
try:
|
|
||||||
from duckduckgo_search import DDGS
|
|
||||||
except ImportError:
|
|
||||||
return [{"error": "Please install duckduckgo-search: pip install duckduckgo-search"}]
|
|
||||||
|
|
||||||
with DDGS() as ddgs:
|
with DDGS() as ddgs:
|
||||||
results = list(ddgs.text(
|
results = list(ddgs.text(
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,7 @@ GET /api/conversations/:id/messages?cursor=msg_001&limit=50
|
||||||
"content": "你好",
|
"content": "你好",
|
||||||
"token_count": 2,
|
"token_count": 2,
|
||||||
"thinking_content": null,
|
"thinking_content": null,
|
||||||
|
"tool_calls": null,
|
||||||
"created_at": "2026-03-24T10:00:00Z"
|
"created_at": "2026-03-24T10:00:00Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -206,6 +207,7 @@ GET /api/conversations/:id/messages?cursor=msg_001&limit=50
|
||||||
"content": "你好!有什么可以帮你的?",
|
"content": "你好!有什么可以帮你的?",
|
||||||
"token_count": 15,
|
"token_count": 15,
|
||||||
"thinking_content": null,
|
"thinking_content": null,
|
||||||
|
"tool_calls": null,
|
||||||
"created_at": "2026-03-24T10:00:01Z"
|
"created_at": "2026-03-24T10:00:01Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -226,10 +228,17 @@ POST /api/conversations/:id/messages
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"content": "介绍一下你的能力",
|
"content": "介绍一下你的能力",
|
||||||
"stream": true
|
"stream": true,
|
||||||
|
"tools_enabled": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
| --------------- | -------- | ---------------------- |
|
||||||
|
| `content` | string | 用户消息内容 |
|
||||||
|
| `stream` | boolean | 是否流式响应,默认 `true` |
|
||||||
|
| `tools_enabled` | boolean | 是否启用工具调用,默认 `true`(可选) |
|
||||||
|
|
||||||
**流式响应 (stream=true):**
|
**流式响应 (stream=true):**
|
||||||
|
|
||||||
**普通回复示例:**
|
**普通回复示例:**
|
||||||
|
|
@ -298,6 +307,7 @@ data: {"message_id": "msg_003", "token_count": 150}
|
||||||
"content": "我是智谱AI开发的大语言模型...",
|
"content": "我是智谱AI开发的大语言模型...",
|
||||||
"token_count": 200,
|
"token_count": 200,
|
||||||
"thinking_content": "用户想了解我的能力...",
|
"thinking_content": "用户想了解我的能力...",
|
||||||
|
"tool_calls": null,
|
||||||
"created_at": "2026-03-24T10:01:00Z"
|
"created_at": "2026-03-24T10:01:00Z"
|
||||||
},
|
},
|
||||||
"usage": {
|
"usage": {
|
||||||
|
|
@ -534,13 +544,11 @@ User 1 ── * Conversation 1 ── * Message
|
||||||
| ------------------ | ------------- | ------------------------------- |
|
| ------------------ | ------------- | ------------------------------- |
|
||||||
| `id` | string (UUID) | 消息 ID |
|
| `id` | string (UUID) | 消息 ID |
|
||||||
| `conversation_id` | string | 所属会话 ID |
|
| `conversation_id` | string | 所属会话 ID |
|
||||||
| `role` | enum | `user` / `assistant` / `system` / `tool` |
|
| `role` | enum | `user` / `assistant` / `system` |
|
||||||
| `content` | string | 消息内容 |
|
| `content` | string | 消息内容 |
|
||||||
| `token_count` | integer | token 消耗数 |
|
| `token_count` | integer | token 消耗数 |
|
||||||
| `thinking_content` | string | 思维链内容(启用时) |
|
| `thinking_content` | string | 思维链内容(启用时) |
|
||||||
| `tool_calls` | string (JSON) | 工具调用请求(assistant 消息) |
|
| `tool_calls` | array (JSON) | 工具调用信息(含结果),仅 assistant 消息 |
|
||||||
| `tool_call_id` | string | 工具调用 ID(tool 消息) |
|
|
||||||
| `name` | string | 工具名称(tool 消息) |
|
|
||||||
| `created_at` | datetime | 创建时间 |
|
| `created_at` | datetime | 创建时间 |
|
||||||
|
|
||||||
#### 消息类型说明
|
#### 消息类型说明
|
||||||
|
|
@ -563,16 +571,22 @@ User 1 ── * Conversation 1 ── * Message
|
||||||
"content": "北京今天天气晴朗...",
|
"content": "北京今天天气晴朗...",
|
||||||
"token_count": 50,
|
"token_count": 50,
|
||||||
"thinking_content": "用户想了解天气...",
|
"thinking_content": "用户想了解天气...",
|
||||||
|
"tool_calls": null,
|
||||||
"created_at": "2026-03-24T10:00:01Z"
|
"created_at": "2026-03-24T10:00:01Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**3. 助手消息 - 工具调用 (role=assistant, with tool_calls)**
|
**3. 助手消息 - 含工具调用 (role=assistant, with tool_calls)**
|
||||||
|
|
||||||
|
工具调用结果直接合并到 `tool_calls` 数组中,每个调用包含 `result` 字段:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"id": "msg_003",
|
"id": "msg_003",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "",
|
"content": "北京今天天气晴朗,温度25°C,湿度60%",
|
||||||
|
"token_count": 80,
|
||||||
|
"thinking_content": "用户想知道北京天气,需要调用工具获取...",
|
||||||
"tool_calls": [
|
"tool_calls": [
|
||||||
{
|
{
|
||||||
"id": "call_abc123",
|
"id": "call_abc123",
|
||||||
|
|
@ -580,21 +594,10 @@ User 1 ── * Conversation 1 ── * Message
|
||||||
"function": {
|
"function": {
|
||||||
"name": "get_weather",
|
"name": "get_weather",
|
||||||
"arguments": "{\"city\": \"北京\"}"
|
"arguments": "{\"city\": \"北京\"}"
|
||||||
}
|
},
|
||||||
|
"result": "{\"temperature\": 25, \"humidity\": 60, \"description\": \"晴天\"}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"created_at": "2026-03-24T10:00:01Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**4. 工具返回消息 (role=tool)**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "msg_004",
|
|
||||||
"role": "tool",
|
|
||||||
"content": "{\"temperature\": 25, \"humidity\": 60, \"description\": \"晴天\"}",
|
|
||||||
"tool_call_id": "call_abc123",
|
|
||||||
"name": "get_weather",
|
|
||||||
"created_at": "2026-03-24T10:00:02Z"
|
"created_at": "2026-03-24T10:00:02Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -606,9 +609,29 @@ User 1 ── * Conversation 1 ── * Message
|
||||||
↓
|
↓
|
||||||
[msg_001] role=user, content="北京今天天气怎么样?"
|
[msg_001] role=user, content="北京今天天气怎么样?"
|
||||||
↓
|
↓
|
||||||
[msg_002] role=assistant, tool_calls=[{get_weather, args:{"city":"北京"}}]
|
[AI 调用工具 get_weather]
|
||||||
↓
|
↓
|
||||||
[msg_003] role=tool, name=get_weather, content="{...weather data...}"
|
[msg_002] role=assistant, tool_calls=[{get_weather, args:{"city":"北京"}, result="{...}"}]
|
||||||
↓
|
content="北京今天天气晴朗,温度25°C..."
|
||||||
[msg_004] role=assistant, content="北京今天天气晴朗,温度25°C..."
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**说明:**
|
||||||
|
- 工具调用结果直接存储在 `tool_calls[].result` 字段中
|
||||||
|
- 不再创建独立的 `role=tool` 消息
|
||||||
|
- 前端可通过 `tool_calls` 数组展示完整的工具调用过程
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端特性
|
||||||
|
|
||||||
|
### 工具调用控制
|
||||||
|
|
||||||
|
- 工具调用开关位于输入框右侧(扳手图标)
|
||||||
|
- 状态存储在浏览器 localStorage(`tools_enabled`)
|
||||||
|
- 默认开启,可通过请求参数 `tools_enabled` 控制每次请求
|
||||||
|
|
||||||
|
### 消息渲染
|
||||||
|
|
||||||
|
- 助手消息的 `tool_calls` 通过可折叠面板展示
|
||||||
|
- 面板显示:思考过程 → 工具调用 → 工具结果
|
||||||
|
- 每个子项可独立展开/折叠
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,8 @@ defineExpose({ scrollToBottom })
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,6 +249,7 @@ defineExpose({ scrollToBottom })
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-container::-webkit-scrollbar {
|
.messages-container::-webkit-scrollbar {
|
||||||
|
|
@ -305,6 +308,12 @@ defineExpose({ scrollToBottom })
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-bubble .message-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.streaming-content {
|
.streaming-content {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
|
@ -327,6 +336,7 @@ defineExpose({ scrollToBottom })
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.streaming-content :deep(pre code) {
|
.streaming-content :deep(pre code) {
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ function copyContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-body {
|
.message-body {
|
||||||
max-width: 720px;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ watch(() => props.streaming, (streaming) => {
|
||||||
border: 1px solid var(--border-light);
|
border: 1px solid var(--border-light);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-toggle {
|
.process-toggle {
|
||||||
|
|
@ -309,6 +310,7 @@ watch(() => props.streaming, (streaming) => {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border-top: 1px solid var(--border-light);
|
border-top: 1px solid var(--border-light);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thinking-text {
|
.thinking-text {
|
||||||
|
|
@ -357,6 +359,8 @@ watch(() => props.streaming, (streaming) => {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
border: 1px solid var(--border-light);
|
border: 1px solid var(--border-light);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-label {
|
.result-label {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@ dependencies = [
|
||||||
"pymysql>=1.1",
|
"pymysql>=1.1",
|
||||||
"requests>=2.31",
|
"requests>=2.31",
|
||||||
"pyyaml>=6.0",
|
"pyyaml>=6.0",
|
||||||
|
"duckduckgo-search>=8.0",
|
||||||
|
"ddgs>=8.0",
|
||||||
|
"beautifulsoup4>=4.12",
|
||||||
|
"httpx>=0.25",
|
||||||
|
"lxml>=5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue