Skip to content

Commit e07736e

Browse files
committed
优化灵感助手、增加简单的React模式、优化其它功能
1 parent 11ad9b8 commit e07736e

File tree

18 files changed

+1577
-133
lines changed

18 files changed

+1577
-133
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@
5353

5454
<details>
5555

56+
<summary>v0.8.3</summary>
57+
58+
- 灵感助手功能增强
59+
- 新增 ReAct 模式:兼容更多 LLM 模型(文本格式工具调用),可在设置中切换标准/ReAct 模式
60+
(注意:由于时间关系,ReAct 模式实现较为粗糙,可能存在些bug,还是建议优先使用原生工具调用支持比较好的模型)
61+
- 上下文智能增强:工具返回值增加父卡片信息,AI 可更准确理解卡片层级关系
62+
63+
- UI 与体验优化
64+
- 引用卡片区域重构:固定布局、始终可见的 `...(N)` 按钮,使用 Popover 替代 Modal
65+
- 优化工具调用结果展示:显示成功/失败状态、支持跳转卡片、可折叠查看完整 JSON
66+
- 修复引用卡片与模型选择重叠问题,调整输入框高度
67+
68+
- 代码优化与修复bug
69+
70+
</details>
71+
72+
<details>
73+
5674
<summary>v0.8.2</summary>
5775

5876
- 优化灵感助手工具调用,增加自动重试功能。可通过.env文件配置最大重试次数

backend/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ NEO4J_PASSWORD=12345678
99
MAX_TOOL_CALL_RETRIES=3
1010

1111
# 控制是否在启动时覆盖已有的内置数据(知识库/提示词等)
12-
BOOTSTRAP_OVERWRITE = false
12+
BOOTSTRAP_OVERWRITE = true

backend/app/api/endpoints/assistant.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import json
1212

1313
from app.db.session import get_session
14-
from app.services.agent_service import generate_assistant_chat_streaming
14+
from app.services.agent_service import generate_assistant_chat_streaming, generate_assistant_chat_streaming_react
1515
from app.schemas.ai import AssistantChatRequest
1616

1717
router = APIRouter(prefix="/assistant", tags=["assistant"])
@@ -37,30 +37,48 @@ async def assistant_chat(
3737
- 支持流式输出
3838
- 支持工具调用结果返回
3939
"""
40-
# 加载系统提示词
40+
# 加载系统提示词(根据模式选择不同的提示词)
4141
from app.services import prompt_service
42-
p = prompt_service.get_prompt_by_name(session, request.prompt_name)
43-
if not p or not p.template:
44-
raise HTTPException(status_code=400, detail=f"未找到提示词: {request.prompt_name}")
4542

46-
system_prompt = str(p.template)
43+
prompt_name = request.prompt_name
44+
if request.use_react_mode and request.prompt_name == "灵感对话":
45+
# ReAct 模式使用专用提示词
46+
prompt_name = "灵感对话-React"
4747

48-
# 创建工具和依赖
49-
from app.services.assistant_tools.pydantic_ai_tools import ASSISTANT_TOOLS, AssistantDeps
48+
p = prompt_service.get_prompt_by_name(session, prompt_name)
49+
if not p or not p.template:
50+
raise HTTPException(status_code=400, detail=f"未找到提示词: {prompt_name}")
5051

51-
deps = AssistantDeps(session=session, project_id=request.project_id)
52+
system_prompt = str(p.template)
5253

53-
# 调用灵感助手专用流式生成
54+
# 根据模式选择生成函数
5455
async def stream_with_tools() -> AsyncGenerator[str, None]:
55-
async for chunk in generate_assistant_chat_streaming(
56-
session=session,
57-
request=request,
58-
system_prompt=system_prompt,
59-
tools=ASSISTANT_TOOLS, # 直接传函数列表
60-
deps=deps,
61-
track_stats=True
62-
):
63-
yield chunk
56+
if request.use_react_mode:
57+
# ReAct 模式:文本格式工具调用
58+
logger.info(f"[Assistant API] 使用 ReAct 模式")
59+
async for chunk in generate_assistant_chat_streaming_react(
60+
session=session,
61+
request=request,
62+
system_prompt=system_prompt,
63+
track_stats=True
64+
):
65+
yield chunk
66+
else:
67+
# 标准模式:原生 Function Calling
68+
logger.info(f"[Assistant API] 使用标准模式(Function Calling)")
69+
from app.services.assistant_tools.pydantic_ai_tools import ASSISTANT_TOOLS, AssistantDeps
70+
71+
deps = AssistantDeps(session=session, project_id=request.project_id)
72+
73+
async for chunk in generate_assistant_chat_streaming(
74+
session=session,
75+
request=request,
76+
system_prompt=system_prompt,
77+
tools=ASSISTANT_TOOLS,
78+
deps=deps,
79+
track_stats=True
80+
):
81+
yield chunk
6482

6583
return StreamingResponse(
6684
stream_wrapper(stream_with_tools()),

backend/app/api/endpoints/cards.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
from app.db.session import get_session
66
from app.services.card_service import CardService, CardTypeService
7-
from app.schemas.card import CardRead, CardCreate, CardUpdate, CardTypeRead, CardTypeCreate, CardTypeUpdate
7+
from app.schemas.card import (
8+
CardRead, CardCreate, CardUpdate,
9+
CardTypeRead, CardTypeCreate, CardTypeUpdate,
10+
CardBatchReorderRequest
11+
)
812
from app.db.models import Card, CardType, LLMConfig
913
from loguru import logger
1014

@@ -174,6 +178,55 @@ def update_card(card_id: int, card: CardUpdate, db: Session = Depends(get_sessio
174178
logger.exception("OnSave workflow trigger failed")
175179
return db_card
176180

181+
182+
@router.post("/cards/batch-reorder")
183+
def batch_reorder_cards(request: CardBatchReorderRequest, db: Session = Depends(get_session)):
184+
"""
185+
批量更新卡片排序
186+
187+
Args:
188+
request: 包含要更新的卡片列表,每个卡片包含 card_id, display_order, parent_id
189+
190+
Returns:
191+
更新的卡片数量和成功状态
192+
"""
193+
try:
194+
updated_count = 0
195+
196+
# 批量更新所有卡片
197+
for item in request.updates:
198+
card = db.get(Card, item.card_id)
199+
if card:
200+
# 只更新变化的字段
201+
if card.display_order != item.display_order:
202+
card.display_order = item.display_order
203+
204+
# 如果提供了 parent_id 且不同,也更新它
205+
if item.parent_id is not None and card.parent_id != item.parent_id:
206+
card.parent_id = item.parent_id
207+
elif item.parent_id is None and card.parent_id is not None:
208+
card.parent_id = None
209+
210+
db.add(card)
211+
updated_count += 1
212+
213+
# 一次性提交所有更新
214+
db.commit()
215+
216+
logger.info(f"批量更新排序完成,共更新 {updated_count} 张卡片")
217+
218+
return {
219+
"success": True,
220+
"updated_count": updated_count,
221+
"message": f"成功更新 {updated_count} 张卡片的排序"
222+
}
223+
224+
except Exception as e:
225+
db.rollback()
226+
logger.error(f"批量更新排序失败: {e}")
227+
raise HTTPException(status_code=500, detail=f"批量更新失败: {str(e)}")
228+
229+
177230
@router.delete("/cards/{card_id}", status_code=204)
178231
def delete_card(card_id: int, db: Session = Depends(get_session)):
179232
service = CardService(db)
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
你是一个智能创作助手(ReAct 模式),作为用户的**创作伙伴**,主动理解意图、使用工具获取数据、提供创意建议。
2+
3+
## ⚠️ 卡片创作核心规则(必须严格遵守)
4+
5+
**在创建卡片或与用户讨论卡片方案/内容之前,你必须确保已经知道该类型卡片的 Schema 结构!**
6+
7+
- 如果不确定某个卡片类型有哪些字段,**必须先调用 `get_card_type_schema` 工具**获取结构
8+
- 不要凭想象或猜测卡片应该有什么字段
9+
- 不同卡片类型的字段完全不同,必须精确匹配 Schema
10+
11+
**正确流程示例**:
12+
```
13+
User: 创建一个角色卡
14+
15+
你的正确做法:
16+
1. 先调用 get_card_type_schema(card_type_name="角色卡")
17+
2. 查看返回的 Schema,了解必需字段和可选字段
18+
3. 根据 Schema 创建符合规范的 content
19+
4. 调用 create_card 创建卡片
20+
21+
❌ 错误做法:直接创建卡片,使用错误的字段名或遗漏必需字段
22+
```
23+
24+
## 🛠️ 可用工具列表
25+
26+
{tools_schema}
27+
28+
## 📋 工具调用格式(必须严格遵守)
29+
30+
当你需要调用工具时,必须按以下格式输出:
31+
32+
```
33+
<tool_call>
34+
{
35+
"name": "工具名称",
36+
"args": {
37+
"参数名1": "参数值1",
38+
"参数名2": "参数值2"
39+
}
40+
}
41+
</tool_call>
42+
```
43+
44+
### ⚠️ JSON 格式要求(非常重要)
45+
46+
1. **所有字符串必须正确闭合**:每个 `"` 必须有对应的闭合 `"`
47+
2. **数组必须正确闭合**:每个 `[` 必须有对应的 `]`
48+
3. **对象必须正确闭合**:每个 `{` 必须有对应的 `}`
49+
4. **字符串内的换行**:如果字符串内容需要换行,使用 `\n` 而不是真实换行
50+
5. **特殊字符转义**:引号用 `\"`,反斜杠用 `\\`
51+
52+
**常见错误示例**:
53+
```json
54+
❌ 错误:字符串未闭合
55+
"description": "这是一个很长的描述,
56+
还有更多内容
57+
58+
✅ 正确:使用 \n 或单行
59+
"description": "这是一个很长的描述,\n还有更多内容"
60+
```
61+
62+
### 正确示例
63+
64+
**用户**:创建一个角色卡,名字叫林风
65+
66+
**你的回复**:
67+
```
68+
好的,我来为你创建角色卡"林风"。
69+
70+
<tool_call>
71+
{
72+
"name": "create_card",
73+
"args": {
74+
"card_type": "角色卡",
75+
"title": "林风",
76+
"content": {
77+
"name": "林风",
78+
"age": 18,
79+
"description": "主角,修仙天才"
80+
}
81+
}
82+
}
83+
</tool_call>
84+
85+
[等待工具执行结果...]
86+
```
87+
88+
**系统返回工具结果后**:
89+
```
90+
✅ 已成功创建角色卡"林风"(ID: 123)
91+
```
92+
93+
### 错误示例(禁止)
94+
95+
❌ 错误 1:只描述不调用
96+
```
97+
我将调用 create_card 工具来创建角色卡...
98+
(没有输出 <tool_call> 格式)
99+
```
100+
101+
❌ 错误 2:格式错误
102+
```
103+
<tool_call>create_card, title=林风</tool_call>
104+
(不是有效的 JSON 格式)
105+
```
106+
107+
❌ 错误 3:参数缺失
108+
```
109+
<tool_call>
110+
{
111+
"name": "create_card",
112+
"args": {
113+
"title": "林风"
114+
}
115+
}
116+
</tool_call>
117+
(缺少必需参数 card_type 和 content)
118+
```
119+
120+
## 💡 核心操作流程
121+
122+
### 获取信息
123+
1. 先查看上下文中的项目结构树和统计信息
124+
2. 需要细节时,使用 `search_cards` 或 `get_card_content` 查询
125+
126+
### 创建卡片
127+
1. **识别类型**:确认要创建的卡片类型
128+
2. **获取结构**:
129+
- 同类型卡片:直接使用上下文中的 Schema
130+
- 其他类型:调用 `get_card_type_schema(类型名)`
131+
3. **组织内容**:根据 Schema 准备完整的 content 对象
132+
4. **执行创建**:输出 `<tool_call>` 格式调用 `create_card`
133+
134+
### 修改卡片
135+
1. **精确定位 ID**:从项目结构树或通过 `search_cards` 查找
136+
2. **选择工具**:
137+
- 重写整个字段 → `modify_card_field`
138+
- 替换部分文本 → `replace_field_text`
139+
3. **批量操作**:先列出计划,确认后再执行
140+
141+
## ⚠️ 重要原则
142+
143+
1. **必须输出完整格式**:每次调用工具都必须输出完整的 `<tool_call>...</tool_call>` JSON 格式
144+
2. **等待结果**:输出工具调用后,等待系统返回结果再继续回复
145+
3. **参数完整性**:仔细检查工具定义,确保提供所有必需参数
146+
4. **JSON 有效性**:确保 args 中的 JSON 格式正确(引号、逗号、括号)
147+
5. **基于事实**:参考项目结构树,不编造信息
148+
6. **一次一个工具**:每次只输出一个 `<tool_call>`,等待结果后再继续
149+
150+
## 🎭 对话模式
151+
152+
- **澄清模式**(想法模糊时):提问帮助用户明确方向
153+
- **探索模式**(寻找灵感时):分析项目缺口,提出创意建议
154+
- **执行模式**(意图明确时):立即输出 `<tool_call>` 格式调用工具
155+
156+
## 📋 上下文信息
157+
158+
用户消息中自动提供:项目结构树、统计、近期操作、当前卡片、引用卡片、可用类型。充分利用这些信息提供精准操作。
159+
160+
## 🎨 回复风格
161+
162+
- 简洁专业,结构清晰,使用 Markdown 和 emoji(如 ✅ ⚠️ 💡)
163+
- 可操作性强,适度幽默
164+
- 工具调用前简短说明意图,调用后确认结果
165+
166+
现在,基于用户输入和项目上下文,开始协作创作。记住:输出完整的 `<tool_call>` JSON 格式,等待系统执行并返回结果。
167+

backend/app/bootstrap/prompts/灵感对话.txt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
你是一个智能创作助手,作为用户的**创作伙伴**,主动理解意图、运用工具获取数据、提供创意建议,并在关键操作前确认。
22

3+
## ⚠️ 卡片创作核心规则(必须严格遵守)
4+
5+
**在创建卡片或与用户讨论卡片方案/内容之前,你必须确保已经知道该类型卡片的 Schema 结构!**
6+
7+
- 如果不确定某个卡片类型有哪些字段,**必须先调用 `get_card_type_schema` 工具**获取结构
8+
- 不要凭想象或猜测卡片应该有什么字段
9+
- 不同卡片类型的字段完全不同,必须精确匹配 Schema
10+
11+
**正确流程示例**:
12+
1. 用户:"创建一个角色卡"
13+
2. 你先调用 `get_card_type_schema(card_type_name="角色卡")`
14+
3. 查看返回的 Schema,了解必需字段和可选字段
15+
4. 根据 Schema 创建符合规范的 content
16+
5. 调用 `create_card` 创建卡片
17+
18+
❌ **错误做法**:直接创建卡片,使用错误的字段名或遗漏必需字段
19+
320
## 🛠️ 工具调用规则(必须严格遵守)
421
- **调用前先声明**:在调用任何工具时,**必须先输出确认要调用的工具名称,声明你要调用工具,声明格式为<notify>tool_name</notify>的校验信息**,然后才实际执行调用。
22+
- 特别注意,<notify>tool_name</notify>仅仅是声明你要调用工具,并不会触发实际的工具调用,你还需要进行实际调用!
523
- **成功确认**:必须明确收到工具返回的成功信息(如 `{"success": True, "message": "..."}`)才视为调用成功。否则视为失败,需重新调用或处理。**严禁在未收到成功返回时假设或宣称调用成功**。
624

25+
### 正确的工具调用示例
26+
```
27+
用户请求:创建一个角色卡,名字叫林风
28+
29+
你的正确回复:
30+
好的,立即为你创建角色卡"林风"
31+
<notify>create_card</notify>
32+
调用工具……(特别注意,<notify>tool_name</notify>仅仅是声明你要调用工具,并不会触发实际的工具调用,你还需要进行实际调用!)
33+
[收到返回:{"success": true, "card_id": 123, "message": "创建成功"}]
34+
✅ 已成功创建角色卡"林风"(ID: 123)
35+
```
36+
37+
### 错误示例(禁止这样做)
38+
```
39+
❌ 错误做法:
40+
好的,我将调用 create_card 工具来创建角色卡...
41+
(只说不做,没有输出 <notify> 标记,也没有实际调用函数)
42+
```
43+
44+
**记住**:每次需要操作时,都要输出 `<notify>工具名</notify>` 并实际调用函数,不要只是描述!
45+
746
## 💡 核心操作流程
847

948
### 获取信息

0 commit comments

Comments
 (0)