chore: 进行一些修复和文档修改

This commit is contained in:
ViperEkura 2026-03-24 22:34:14 +08:00
parent 9a0a55e392
commit 4540bb9f41
7 changed files with 73 additions and 34 deletions

View File

@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone
from . import db
@ -26,8 +26,8 @@ class Conversation(db.Model):
temperature = db.Column(db.Float, nullable=False, default=1.0)
max_tokens = db.Column(db.Integer, nullable=False, default=65536)
thinking_enabled = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
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",
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)
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):
@ -62,7 +62,7 @@ class TokenUsage(db.Model):
prompt_tokens = db.Column(db.Integer, default=0)
completion_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__ = (
db.UniqueConstraint("user_id", "date", "model", name="uq_user_date_model"),

View File

@ -1,5 +1,6 @@
"""Tool helper services"""
from typing import List, Dict, Optional, Any
from ddgs import DDGS
import re
@ -38,10 +39,6 @@ class SearchService:
region: str
) -> List[dict]:
"""DuckDuckGo search"""
try:
from duckduckgo_search import DDGS
except ImportError:
return [{"error": "Please install duckduckgo-search: pip install duckduckgo-search"}]
with DDGS() as ddgs:
results = list(ddgs.text(

View File

@ -197,6 +197,7 @@ GET /api/conversations/:id/messages?cursor=msg_001&limit=50
"content": "你好",
"token_count": 2,
"thinking_content": null,
"tool_calls": null,
"created_at": "2026-03-24T10:00:00Z"
},
{
@ -206,6 +207,7 @@ GET /api/conversations/:id/messages?cursor=msg_001&limit=50
"content": "你好!有什么可以帮你的?",
"token_count": 15,
"thinking_content": null,
"tool_calls": null,
"created_at": "2026-03-24T10:00:01Z"
}
],
@ -226,10 +228,17 @@ POST /api/conversations/:id/messages
```json
{
"content": "介绍一下你的能力",
"stream": true
"stream": true,
"tools_enabled": true
}
```
| 参数 | 类型 | 说明 |
| --------------- | -------- | ---------------------- |
| `content` | string | 用户消息内容 |
| `stream` | boolean | 是否流式响应,默认 `true` |
| `tools_enabled` | boolean | 是否启用工具调用,默认 `true`(可选) |
**流式响应 (stream=true)**
**普通回复示例:**
@ -298,6 +307,7 @@ data: {"message_id": "msg_003", "token_count": 150}
"content": "我是智谱AI开发的大语言模型...",
"token_count": 200,
"thinking_content": "用户想了解我的能力...",
"tool_calls": null,
"created_at": "2026-03-24T10:01:00Z"
},
"usage": {
@ -534,13 +544,11 @@ User 1 ── * Conversation 1 ── * Message
| ------------------ | ------------- | ------------------------------- |
| `id` | string (UUID) | 消息 ID |
| `conversation_id` | string | 所属会话 ID |
| `role` | enum | `user` / `assistant` / `system` / `tool` |
| `role` | enum | `user` / `assistant` / `system` |
| `content` | string | 消息内容 |
| `token_count` | integer | token 消耗数 |
| `thinking_content` | string | 思维链内容(启用时) |
| `tool_calls` | string (JSON) | 工具调用请求assistant 消息) |
| `tool_call_id` | string | 工具调用 IDtool 消息) |
| `name` | string | 工具名称tool 消息) |
| `tool_calls` | array (JSON) | 工具调用信息(含结果),仅 assistant 消息 |
| `created_at` | datetime | 创建时间 |
#### 消息类型说明
@ -563,16 +571,22 @@ User 1 ── * Conversation 1 ── * Message
"content": "北京今天天气晴朗...",
"token_count": 50,
"thinking_content": "用户想了解天气...",
"tool_calls": null,
"created_at": "2026-03-24T10:00:01Z"
}
```
**3. 助手消息 - 工具调用 (role=assistant, with tool_calls)**
**3. 助手消息 - 含工具调用 (role=assistant, with tool_calls)**
工具调用结果直接合并到 `tool_calls` 数组中,每个调用包含 `result` 字段:
```json
{
"id": "msg_003",
"role": "assistant",
"content": "",
"content": "北京今天天气晴朗温度25°C湿度60%",
"token_count": 80,
"thinking_content": "用户想知道北京天气,需要调用工具获取...",
"tool_calls": [
{
"id": "call_abc123",
@ -580,21 +594,10 @@ User 1 ── * Conversation 1 ── * Message
"function": {
"name": "get_weather",
"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"
}
```
@ -606,9 +609,29 @@ User 1 ── * Conversation 1 ── * Message
[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_004] role=assistant, content="北京今天天气晴朗温度25°C..."
[msg_002] role=assistant, tool_calls=[{get_weather, args:{"city":"北京"}, result="{...}"}]
content="北京今天天气晴朗温度25°C..."
```
**说明:**
- 工具调用结果直接存储在 `tool_calls[].result` 字段中
- 不再创建独立的 `role=tool` 消息
- 前端可通过 `tool_calls` 数组展示完整的工具调用过程
---
## 前端特性
### 工具调用控制
- 工具调用开关位于输入框右侧(扳手图标)
- 状态存储在浏览器 localStorage`tools_enabled`
- 默认开启,可通过请求参数 `tools_enabled` 控制每次请求
### 消息渲染
- 助手消息的 `tool_calls` 通过可折叠面板展示
- 面板显示:思考过程 → 工具调用 → 工具结果
- 每个子项可独立展开/折叠

View File

@ -139,6 +139,8 @@ defineExpose({ scrollToBottom })
height: 100vh;
background: var(--bg-secondary);
min-width: 0;
max-width: 100%;
overflow: hidden;
transition: background 0.2s;
}
@ -247,6 +249,7 @@ defineExpose({ scrollToBottom })
flex: 1;
overflow-y: auto;
padding: 0 24px;
width: 100%;
}
.messages-container::-webkit-scrollbar {
@ -305,6 +308,12 @@ defineExpose({ scrollToBottom })
color: white;
}
.message-bubble .message-body {
flex: 1;
min-width: 0;
overflow: hidden;
}
.streaming-content {
font-size: 15px;
line-height: 1.7;
@ -327,6 +336,7 @@ defineExpose({ scrollToBottom })
padding: 16px;
overflow-x: auto;
margin: 8px 0;
max-width: 100%;
}
.streaming-content :deep(pre code) {

View File

@ -106,7 +106,7 @@ function copyContent() {
}
.message-body {
max-width: 720px;
flex: 1;
min-width: 0;
}

View File

@ -180,6 +180,7 @@ watch(() => props.streaming, (streaming) => {
border: 1px solid var(--border-light);
overflow: hidden;
background: var(--bg-secondary);
max-width: 100%;
}
.process-toggle {
@ -309,6 +310,7 @@ watch(() => props.streaming, (streaming) => {
padding: 12px;
background: var(--bg-primary);
border-top: 1px solid var(--border-light);
overflow: hidden;
}
.thinking-text {
@ -357,6 +359,8 @@ watch(() => props.streaming, (streaming) => {
color: var(--text-secondary);
overflow-x: auto;
border: 1px solid var(--border-light);
white-space: pre-wrap;
word-break: break-word;
}
.result-label {

View File

@ -10,6 +10,11 @@ dependencies = [
"pymysql>=1.1",
"requests>=2.31",
"pyyaml>=6.0",
"duckduckgo-search>=8.0",
"ddgs>=8.0",
"beautifulsoup4>=4.12",
"httpx>=0.25",
"lxml>=5.0",
]
[build-system]