Skip to content

Commit 02c213b

Browse files
committed
修复bug
1 parent e157efc commit 02c213b

8 files changed

Lines changed: 189 additions & 55 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<summary>v0.8.5</summary>
5656

5757
- 使用新的agent框架进行了全面替换;优化灵感助手功能、UI
58+
- 增加了灵感助手相关设置
5859
- 重新实现了React模式来为模型实现文本格式工具调用,适用工具调用能力不强的模型。可在设置-灵感助手处开启(默认关闭)
5960
- 兼容了推理模型,增加了thinking模式
6061
- 建议将DeepSeek、Qwen之类的模型选择/修改提供商为OpenAI兼容,而OpenAI则仅设置为GPT 5等官方模型。
@@ -75,7 +76,6 @@
7576
- 引用卡片区域重构:固定布局、始终可见的 `...(N)` 按钮,使用 Popover 替代 Modal
7677
- 优化工具调用结果展示:显示成功/失败状态、支持跳转卡片、可折叠查看完整 JSON
7778
- 修复引用卡片与模型选择重叠问题,调整输入框高度
78-
7979
- 代码优化与修复bug
8080

8181
</details>

backend/app/bootstrap/init_app.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def init_prompts(db: Session):
4444
"""初始化默认提示词。
4545
行为受环境变量 BOOTSTRAP_OVERWRITE 控制:
4646
"""
47-
overwrite = str(os.getenv('BOOTSTRAP_OVERWRITE', '')).lower() in ('1', 'true', 'yes', 'on')
47+
# 默认开启覆盖更新;仅当显式设置为 false/0 等时才关闭
48+
overwrite = str(os.getenv('BOOTSTRAP_OVERWRITE', 'true')).lower() in ('1', 'true', 'yes', 'on')
4849
existing_prompts = db.exec(select(Prompt)).all()
4950
existing_names = {p.name for p in existing_prompts}
5051

@@ -268,7 +269,8 @@ def init_knowledge(db: Session):
268269
created = 0
269270
updated = 0
270271
skipped = 0
271-
overwrite = str(os.getenv('BOOTSTRAP_OVERWRITE', '')).lower() in ('1', 'true', 'yes', 'on')
272+
# 默认开启覆盖更新;仅当显式设置为 false/0 等时才关闭
273+
overwrite = str(os.getenv('BOOTSTRAP_OVERWRITE', 'true')).lower() in ('1', 'true', 'yes', 'on')
272274

273275
for filename in os.listdir(knowledge_dir):
274276
if not filename.lower().endswith(('.txt', '.md')):
@@ -381,7 +383,8 @@ def init_workflows(db: Session):
381383
2.1) Card.UpsertChildByTitle(cardType=组织卡,title={item.name},useItemAsContent=true)
382384
2.2) Card.ModifyContent(setPath=world_view.social_system.major_power_camps,setValue=[])
383385
"""
384-
overwrite = str(os.getenv('BOOTSTRAP_OVERWRITE', '')).lower() in ('1', 'true', 'yes', 'on')
386+
# 默认开启覆盖更新;仅当显式设置为 false/0 等时才关闭
387+
overwrite = str(os.getenv('BOOTSTRAP_OVERWRITE', 'true')).lower() in ('1', 'true', 'yes', 'on')
385388
total_created = total_updated = total_skipped = 0
386389
name = "世界观"
387390
dsl = {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
你是一个智能创作助手,当前工作在文本工具协议模式下。你的核心目标是成为用户的创作伙伴,通过主动理解意图、合理使用工具、给出清晰可靠的建议,来为作者提供结构化的创作协助。
22

3+
修改测试……
4+
35
## ⚙️ 文本协议要求(务必遵守)
46
1. **正常对话**:大部分时候,你可以像平常一样自然地思考和回复,无需特殊格式。
57
2. **工具调用**:当需要调用工具时,使用 `<Action>{"tool": "工具名", "input": {…}}</Action>` 格式。其中:
68
- `tool` (或 `tool_name`) 必须是系统提供的工具名。
79
- `input` 必须是一个 JSON 对象,其字段需与工具定义的参数完全匹配。
810
- Action 标签内的内容必须是纯粹、有效的 JSON,不应包含任何额外说明文字。
11+
- **每次输出 `<Action>` 之前,尽量先用 1~2 句自然语言向用户说明你要做什么;工具执行完成后,再用 1~3 句自然语言总结你做了什么以及得到的关键信息。最好不要只输出一个 `<Action>` 而没有任何解释性文字。**
912
3. **工具结果**:系统会以 Observation (工具执行结果) 的形式反馈信息。收到后你可以继续对话或调用更多工具。
1013
4. **禁止事项**:
1114
- 不要猜测工具的执行结果;如果工具调用失败,应分析原因并决定是否重试或改变策略。

backend/app/services/langchain_assistant.py

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
"""LangChain-based assistant backend.
2-
3-
This module provides:
4-
- Factory to build LangChain ChatModel from LLMConfig.
5-
- A simple streaming generator that yields JSON-line events compatible with
6-
the existing assistant event protocol (currently only `token` events).
7-
8-
It is intentionally minimal for the first migration step and does not yet
9-
implement tools / ReAct. Those will be added incrementally on top.
10-
"""
111

122
from __future__ import annotations
133

@@ -57,7 +47,8 @@
5747
_ACTION_TAG_RE = re.compile(r"<Action>(.*?)</Action>", re.IGNORECASE | re.DOTALL)
5848
_CODE_FENCE_RE = re.compile(r"```(?:json)?\s*(.*?)```", re.IGNORECASE | re.DOTALL)
5949
_JSON_BLOCK_RE = re.compile(r"Action\s*:?\s*(\{.*\})", re.IGNORECASE | re.DOTALL)
60-
_PROTOCOL_TAGS = ("action", "thought", "finalanswer")
50+
# React 文本协议仅保留 Action,一律使用 <Action>{...}</Action> 格式声明工具调用
51+
_PROTOCOL_TAGS = ("action",)
6152

6253
MAX_REACT_STEPS = 8
6354

@@ -135,22 +126,6 @@ def _parse_action_payload(text: str) -> Optional[Tuple[str, Dict[str, Any]]]:
135126
return tool_name.strip(), args
136127

137128

138-
def _clean_react_output(text: str) -> str:
139-
"""清理 React 输出中的残留协议标记,但保留必要的空白字符。"""
140-
141-
if not text:
142-
return text
143-
144-
# 移除 Action 标签(已被系统处理)
145-
cleaned = _ACTION_TAG_RE.sub("", text)
146-
147-
# 移除其他可能的协议标签,但保留换行符
148-
cleaned = re.sub(r"</?(?:Thought|FinalAnswer|Action).*?>", "", cleaned, flags=re.IGNORECASE | re.DOTALL)
149-
150-
# 只移除首尾空白,保留中间的换行符和缩进
151-
return cleaned.strip()
152-
153-
154129
def _process_react_stream_text(state: dict[str, str], new_text: str) -> str:
155130
"""在流式阶段移除协议标签,但保留换行符和空白字符以维护 Markdown 格式。"""
156131

@@ -215,12 +190,9 @@ def _process_react_stream_text(state: dict[str, str], new_text: str) -> str:
215190
state["buffer"] = buffer
216191
return "".join(output_parts)
217192

218-
content = block[inner_start + 1 : close_idx]
219-
220-
if potential_tag == "finalanswer":
221-
# 保留 FinalAnswer 的内容,包括换行符
222-
output_parts.append(content)
223-
# Action 和 Thought 直接丢弃,但不影响前后的空白字符
193+
# 提取标签内部内容(目前仅用于完整跳过 <Action> ... </Action>)
194+
# 注意:这里不直接拼接任何协议标签内部的文本,保证前端只看到清洗后的可见正文。
195+
_ = block[inner_start + 1 : close_idx]
224196

225197
# 推进 buffer
226198
buffer = buffer[block_end:]
@@ -525,7 +497,7 @@ async def stream_chat_with_react(
525497
llm_config_id=request.llm_config_id,
526498
temperature=request.temperature or 0.6,
527499
max_tokens=request.max_tokens or 8192,
528-
timeout=request.timeout or 60,
500+
timeout=request.timeout or 90,
529501
thinking_enabled=getattr(request, "thinking_enabled", None),
530502
)
531503

@@ -607,9 +579,12 @@ async def stream_chat_with_react(
607579
usage_in_total += in_tokens
608580
usage_out_total += out_tokens
609581

610-
# 仅在本轮已经产生过面向用户的正文文本时,才解析 Action 协议。
611-
# 这样可以避免模型在纯思考/Reasoning 阶段输出的 <Action> 触发前端提前进入工具调用状态。
612-
action_payload = _parse_action_payload(step_text) if has_visible_text else None
582+
# 直接从本轮累计的文本中解析 Action 协议。
583+
# 早期实现曾经要求 has_visible_text 才允许解析,为的是避免模型在纯思考阶段输出 <Action>。
584+
# 但在当前提示词下,我们只约定了 <Action>{...}</Action>,没有显式的 Thought/FinalAnswer 标签,
585+
# 严格依赖 has_visible_text 会导致"只输出 Action、不输出正文"的情况完全被忽略,前端看到的是空回复。
586+
# 因此这里放宽限制:总是尝试从 step_text 中解析 Action,由上游提示词约束模型行为。
587+
action_payload = _parse_action_payload(step_text)
613588

614589
if action_payload:
615590
tool_name, args = action_payload
@@ -657,6 +632,7 @@ async def stream_chat_with_react(
657632
raise RuntimeError("React 模式未能产生最终回复")
658633

659634
except asyncio.CancelledError:
635+
logger.warning(f"[React-Agent] 请求被客户端取消 (CancelledError)")
660636
if usage_in_total and usage_out_total:
661637
in_tokens = usage_in_total
662638
out_tokens = usage_out_total
@@ -671,7 +647,8 @@ async def stream_chat_with_react(
671647
calls=1,
672648
aborted=True,
673649
)
674-
return
650+
# 必须重新抛出 CancelledError 以便上层协程正确感知取消
651+
raise
675652
except Exception as e:
676653
logger.error(f"[React-Agent] 执行失败: {e}")
677654
raise
@@ -728,7 +705,7 @@ async def stream_chat_with_tools(
728705
llm_config_id=request.llm_config_id,
729706
temperature=request.temperature or 0.6,
730707
max_tokens=request.max_tokens or 8192,
731-
timeout=request.timeout or 60,
708+
timeout=request.timeout or 90,
732709
thinking_enabled=getattr(request, "thinking_enabled", None),
733710
)
734711

frontend/src/renderer/src/components/assistants/AssistantPanel.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,9 @@ function buildAssistantChatRequest() {
760760
context_summarization_enabled: assistantPrefs.contextSummaryEnabled.value || undefined,
761761
context_summarization_threshold: assistantPrefs.contextSummaryThreshold.value || undefined,
762762
react_mode_enabled: assistantPrefs.reactModeEnabled.value || undefined,
763+
temperature: assistantPrefs.assistantTemperature.value || undefined,
764+
max_tokens: assistantPrefs.assistantMaxTokens.value || undefined,
765+
timeout: assistantPrefs.assistantTimeout.value || undefined,
763766
}
764767
765768
return {
@@ -787,9 +790,6 @@ function startStreaming(_prev: string, _tail: string, targetIdx: number) {
787790
prompt_name: promptName,
788791
project_id: projectStore.currentProject?.id as number,
789792
stream: true,
790-
temperature: props.temperature ?? 0.7,
791-
max_tokens: props.max_tokens ?? 8192,
792-
timeout: props.timeout ?? undefined,
793793
thinking_enabled: useThinkingMode.value
794794
} as any, (chunk) => {
795795
// 优先尝试解析为结构化事件(JSON-line)

frontend/src/renderer/src/components/setting/AssistantSettings.vue

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { computed } from 'vue'
3+
import { QuestionFilled } from '@element-plus/icons-vue'
34
import { useAssistantPreferences } from '@renderer/composables/useAssistantPreferences'
45
56
// 通过组合式统一管理灵感助手偏好,方便在设置页与助手面板之间复用
@@ -19,6 +20,21 @@ const reactModeEnabled = computed({
1920
get: () => prefs.reactModeEnabled.value,
2021
set: (val: boolean) => prefs.setReactModeEnabled(val)
2122
})
23+
24+
const assistantTemperature = computed({
25+
get: () => prefs.assistantTemperature.value,
26+
set: (val: number | null) => prefs.setAssistantTemperature(val)
27+
})
28+
29+
const assistantMaxTokens = computed({
30+
get: () => prefs.assistantMaxTokens.value,
31+
set: (val: number | null) => prefs.setAssistantMaxTokens(val)
32+
})
33+
34+
const assistantTimeout = computed({
35+
get: () => prefs.assistantTimeout.value,
36+
set: (val: number | null) => prefs.setAssistantTimeout(val)
37+
})
2238
</script>
2339

2440
<template>
@@ -28,14 +44,99 @@ const reactModeEnabled = computed({
2844
配置灵感助手的高级能力,目前仅开放 React 工具解析协议。上下文摘要功能尚未启用。
2945
</p>
3046

31-
<el-form label-width="140px" class="assistant-form" size="small">
32-
<el-form-item label="React 模式">
47+
<el-form label-width="160px" class="assistant-form" size="small">
48+
<!-- 参数配置组 -->
49+
<div class="group-title">参数设置</div>
50+
51+
<el-form-item>
52+
<template #label>
53+
<span>
54+
采样温度 (temperature)
55+
<el-tooltip placement="top" effect="dark">
56+
<template #content>
57+
控制输出的随机性,数值越大越有创意、越发散,越小越保守、越稳定。<br/>
58+
建议范围 0.4 ~ 0.9。默认值为 0.6。
59+
</template>
60+
<el-icon class="field-help-icon"><QuestionFilled /></el-icon>
61+
</el-tooltip>
62+
</span>
63+
</template>
64+
<el-input-number
65+
v-model="assistantTemperature"
66+
:min="0.1"
67+
:max="2"
68+
:step="0.1"
69+
:precision="2"
70+
controls-position="right"
71+
placeholder="0.6"
72+
/>
73+
</el-form-item>
74+
75+
<el-form-item>
76+
<template #label>
77+
<span>
78+
最大输出 Token 数
79+
<el-tooltip placement="top" effect="dark">
80+
<template #content>
81+
控制单次回复的最大长度。值越大,回复可以越长,但也会增加响应时间和费用。<br/>
82+
默认值为 8192。
83+
</template>
84+
<el-icon class="field-help-icon"><QuestionFilled /></el-icon>
85+
</el-tooltip>
86+
</span>
87+
</template>
88+
<el-input-number
89+
v-model="assistantMaxTokens"
90+
:min="256"
91+
:max="65536"
92+
:step="512"
93+
controls-position="right"
94+
placeholder="8192"
95+
/>
96+
</el-form-item>
97+
98+
<el-form-item>
99+
<template #label>
100+
<span>
101+
超时 (秒)
102+
<el-tooltip placement="top" effect="dark">
103+
<template #content>
104+
限制单次调用的最长等待时间,避免请求长时间挂起。<br/>
105+
默认值为 90 秒。
106+
</template>
107+
<el-icon class="field-help-icon"><QuestionFilled /></el-icon>
108+
</el-tooltip>
109+
</span>
110+
</template>
111+
<el-input-number
112+
v-model="assistantTimeout"
113+
:min="10"
114+
:max="600"
115+
:step="10"
116+
controls-position="right"
117+
placeholder="90"
118+
/>
119+
</el-form-item>
120+
121+
<el-divider />
122+
123+
<!-- React 配置组 -->
124+
<div class="group-title">模式设置</div>
125+
<el-form-item>
126+
<template #label>
127+
<span>
128+
React 模式
129+
<el-tooltip placement="top" effect="dark">
130+
<template #content>
131+
让模型通过文本协议输出工具调用指令(<Action>{...}</Action>),
132+
系统解析后真正调用工具,适合不支持函数调用的模型。
133+
</template>
134+
<el-icon class="field-help-icon"><QuestionFilled /></el-icon>
135+
</el-tooltip>
136+
</span>
137+
</template>
33138
<el-switch v-model="reactModeEnabled" />
34139
</el-form-item>
35-
<div class="field-hint">
36-
React 模式要求模型按照通过文本协议输出工具调用,
37-
系统会解析 Action 并真正调用工具,适合不支持函数调用的模型。
38-
</div>
39140
</el-form>
40141
</div>
41142
</template>
@@ -70,4 +171,16 @@ const reactModeEnabled = computed({
70171
.hint-alert {
71172
margin-top: 12px;
72173
}
174+
175+
.group-title {
176+
margin: 8px 0 4px 0;
177+
font-size: 13px;
178+
font-weight: 600;
179+
color: var(--el-text-color-regular);
180+
}
181+
182+
.field-help-icon {
183+
margin-left: 4px;
184+
cursor: help;
185+
}
73186
</style>

0 commit comments

Comments
 (0)