Skip to content

Commit 1a72ba2

Browse files
Merge remote-tracking branch 'upstream/v2' into perf
2 parents fbbdb70 + 34fb95b commit 1a72ba2

File tree

20 files changed

+435
-192
lines changed

20 files changed

+435
-192
lines changed

.github/workflows/build-and-push-python-pg.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
run: |
2626
DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-base
2727
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
28-
TAG_NAME=python3.11-pg17.7-20260323
28+
TAG_NAME=python3.11-pg17.9-20260326
2929
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
3030
echo ::set-output name=docker_image::${DOCKER_IMAGE}
3131
echo ::set-output name=version::${TAG_NAME}

apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ImageUnderstandNodeSerializer(serializers.Serializer):
3030

3131
model_params_setting = serializers.JSONField(required=False, default=dict,
3232
label=_("Model parameter settings"))
33+
model_setting = serializers.DictField(required=False,
34+
label='Model settings')
3335

3436

3537
class IImageUnderstandNode(INode):
@@ -56,5 +58,6 @@ def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, hist
5658
chat_record_id,
5759
image,
5860
model_id_type=None, model_id_reference=None,
61+
model_setting=None,
5962
**kwargs) -> NodeResult:
6063
pass

apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010

1111
from application.flow.i_step_node import NodeResult, INode
1212
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
13+
from application.flow.tools import Reasoning
1314
from knowledge.models import File
1415
from models_provider.tools import get_model_instance_by_model_workspace_id
1516

1617

17-
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
18+
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,
19+
reasoning_content: str):
1820
chat_model = node_variable.get('chat_model')
1921
message_tokens = node_variable['usage_metadata']['output_tokens'] if 'usage_metadata' in node_variable else 0
2022
answer_tokens = chat_model.get_num_tokens(answer)
@@ -24,6 +26,7 @@ def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wo
2426
node.context['history_message'] = node_variable['history_message']
2527
node.context['question'] = node_variable['question']
2628
node.context['run_time'] = time.time() - node.context['start_time']
29+
node.context['reasoning_content'] = reasoning_content
2730
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
2831
node.answer_text = answer
2932

@@ -38,17 +41,55 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
3841
"""
3942
response = node_variable.get('result')
4043
answer = ''
44+
reasoning_content = ''
45+
model_setting = node.context.get('model_setting',
46+
{'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
47+
'reasoning_content_start': '<think>'})
48+
reasoning = Reasoning(model_setting.get('reasoning_content_start', '<think>'),
49+
model_setting.get('reasoning_content_end', '</think>'))
50+
response_reasoning_content = False
51+
4152
for chunk in response:
53+
if workflow.is_the_task_interrupted():
54+
break
55+
56+
# 处理 reasoning content
57+
reasoning_chunk = reasoning.get_reasoning_content(chunk)
58+
content_chunk = reasoning_chunk.get('content')
59+
if 'reasoning_content' in chunk.additional_kwargs:
60+
response_reasoning_content = True
61+
reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '')
62+
else:
63+
reasoning_content_chunk = reasoning_chunk.get('reasoning_content')
64+
65+
answer += content_chunk
66+
if reasoning_content_chunk is None:
67+
reasoning_content_chunk = ''
68+
reasoning_content += reasoning_content_chunk
69+
70+
# 处理 chunk.content 为 list 的情况
4271
if isinstance(chunk.content, list):
4372
for chunk_item in chunk.content:
4473
text = chunk_item.get("text", "")
45-
answer += text
46-
yield text
74+
yield {'content': text,
75+
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
76+
False) else ''}
4777
else:
4878
text = chunk.content or ""
49-
answer += text
50-
yield text
51-
_write_context(node_variable, workflow_variable, node, workflow, answer)
79+
yield {'content': text,
80+
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
81+
False) else ''}
82+
83+
reasoning_chunk = reasoning.get_end_reasoning_content()
84+
answer += reasoning_chunk.get('content')
85+
reasoning_content_chunk = ""
86+
if not response_reasoning_content:
87+
reasoning_content_chunk = reasoning_chunk.get(
88+
'reasoning_content')
89+
yield {'content': reasoning_chunk.get('content'),
90+
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
91+
False) else ''}
92+
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)
5293

5394

5495
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
@@ -60,8 +101,20 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
60101
@param workflow: 工作流管理器
61102
"""
62103
response = node_variable.get('result')
63-
answer = response.content
64-
_write_context(node_variable, workflow_variable, node, workflow, answer)
104+
model_setting = node.context.get('model_setting',
105+
{'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
106+
'reasoning_content_start': '<think>'})
107+
reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end'))
108+
reasoning_result = reasoning.get_reasoning_content(response)
109+
reasoning_result_end = reasoning.get_end_reasoning_content()
110+
content = reasoning_result.get('content') + reasoning_result_end.get('content')
111+
meta = {**response.response_metadata, **response.additional_kwargs}
112+
if 'reasoning_content' in meta:
113+
reasoning_content = (meta.get('reasoning_content', '') or '')
114+
else:
115+
reasoning_content = (reasoning_result.get('reasoning_content') or '') + (
116+
reasoning_result_end.get('reasoning_content') or '')
117+
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
65118

66119

67120
def file_id_to_base64(file_id: str):
@@ -84,7 +137,12 @@ def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, hist
84137
chat_record_id,
85138
image,
86139
model_id_type=None, model_id_reference=None,
140+
model_setting=None,
87141
**kwargs) -> NodeResult:
142+
if model_setting is None:
143+
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
144+
'reasoning_content_start': '<think>'}
145+
self.context['model_setting'] = model_setting
88146
# 处理引用类型
89147
if model_id_type == 'reference' and model_id_reference:
90148
reference_data = self.workflow_manage.get_reference_field(
@@ -266,6 +324,7 @@ def get_details(self, index: int, **kwargs):
266324
'history_message') is not None else [])],
267325
'question': self.context.get('question'),
268326
'answer': self.context.get('answer'),
327+
'reasoning_content': self.context.get('reasoning_content'),
269328
'type': self.node.type,
270329
'message_tokens': self.context.get('message_tokens'),
271330
'answer_tokens': self.context.get('answer_tokens'),

apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class VideoUnderstandNodeSerializer(serializers.Serializer):
2929

3030
model_params_setting = serializers.JSONField(required=False, default=dict,
3131
label=_("Model parameter settings"))
32+
model_setting = serializers.DictField(required=False,
33+
label='Model settings')
3234

3335

3436
class IVideoUnderstandNode(INode):
@@ -45,7 +47,7 @@ def _run(self):
4547

4648
if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,
4749
WorkflowMode.TOOL_LOOP].__contains__(
48-
self.workflow_manage.flow.workflow_mode):
50+
self.workflow_manage.flow.workflow_mode):
4951
return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data,
5052
**{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None})
5153
else:
@@ -56,5 +58,6 @@ def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, hist
5658
chat_record_id,
5759
video,
5860
model_id_type=None, model_id_reference=None,
61+
model_setting=None,
5962
**kwargs) -> NodeResult:
6063
pass

apps/application/flow/step_node/video_understand_step_node/impl/base_video_understand_node.py

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
from application.flow.i_step_node import NodeResult, INode
1111
from application.flow.step_node.video_understand_step_node.i_video_understand_node import IVideoUnderstandNode
12+
from application.flow.tools import Reasoning
1213
from knowledge.models import File
1314
from models_provider.tools import get_model_instance_by_model_workspace_id
1415

1516

16-
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
17+
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,
18+
reasoning_content: str):
1719
chat_model = node_variable.get('chat_model')
1820
message_tokens = node_variable['usage_metadata']['output_tokens'] if 'usage_metadata' in node_variable else 0
1921
answer_tokens = chat_model.get_num_tokens(answer)
@@ -23,6 +25,7 @@ def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wo
2325
node.context['history_message'] = node_variable['history_message']
2426
node.context['question'] = node_variable['question']
2527
node.context['run_time'] = time.time() - node.context['start_time']
28+
node.context['reasoning_content'] = reasoning_content
2629
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
2730
node.answer_text = answer
2831

@@ -37,10 +40,55 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
3740
"""
3841
response = node_variable.get('result')
3942
answer = ''
43+
reasoning_content = ''
44+
model_setting = node.context.get('model_setting',
45+
{'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
46+
'reasoning_content_start': '<think>'})
47+
reasoning = Reasoning(model_setting.get('reasoning_content_start', '<think>'),
48+
model_setting.get('reasoning_content_end', '</think>'))
49+
response_reasoning_content = False
50+
4051
for chunk in response:
41-
answer += chunk.content
42-
yield chunk.content
43-
_write_context(node_variable, workflow_variable, node, workflow, answer)
52+
if workflow.is_the_task_interrupted():
53+
break
54+
55+
# 处理 reasoning content
56+
reasoning_chunk = reasoning.get_reasoning_content(chunk)
57+
content_chunk = reasoning_chunk.get('content')
58+
if 'reasoning_content' in chunk.additional_kwargs:
59+
response_reasoning_content = True
60+
reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '')
61+
else:
62+
reasoning_content_chunk = reasoning_chunk.get('reasoning_content')
63+
64+
answer += content_chunk
65+
if reasoning_content_chunk is None:
66+
reasoning_content_chunk = ''
67+
reasoning_content += reasoning_content_chunk
68+
69+
# 处理 chunk.content 为 list 的情况
70+
if isinstance(chunk.content, list):
71+
for chunk_item in chunk.content:
72+
text = chunk_item.get("text", "")
73+
yield {'content': text,
74+
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
75+
False) else ''}
76+
else:
77+
text = chunk.content or ""
78+
yield {'content': text,
79+
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
80+
False) else ''}
81+
82+
reasoning_chunk = reasoning.get_end_reasoning_content()
83+
answer += reasoning_chunk.get('content')
84+
reasoning_content_chunk = ""
85+
if not response_reasoning_content:
86+
reasoning_content_chunk = reasoning_chunk.get(
87+
'reasoning_content')
88+
yield {'content': reasoning_chunk.get('content'),
89+
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
90+
False) else ''}
91+
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)
4492

4593

4694
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
@@ -52,8 +100,20 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
52100
@param workflow: 工作流管理器
53101
"""
54102
response = node_variable.get('result')
55-
answer = response.content
56-
_write_context(node_variable, workflow_variable, node, workflow, answer)
103+
model_setting = node.context.get('model_setting',
104+
{'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
105+
'reasoning_content_start': '<think>'})
106+
reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end'))
107+
reasoning_result = reasoning.get_reasoning_content(response)
108+
reasoning_result_end = reasoning.get_end_reasoning_content()
109+
content = reasoning_result.get('content') + reasoning_result_end.get('content')
110+
meta = {**response.response_metadata, **response.additional_kwargs}
111+
if 'reasoning_content' in meta:
112+
reasoning_content = (meta.get('reasoning_content', '') or '')
113+
else:
114+
reasoning_content = (reasoning_result.get('reasoning_content') or '') + (
115+
reasoning_result_end.get('reasoning_content') or '')
116+
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
57117

58118

59119
def file_id_to_base64(file_id: str, video_model):
@@ -76,6 +136,7 @@ def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, hist
76136
chat_record_id,
77137
video,
78138
model_id_type=None, model_id_reference=None,
139+
model_setting=None,
79140
**kwargs) -> NodeResult:
80141
# 处理引用类型
81142
if model_id_type == 'reference' and model_id_reference:
@@ -88,6 +149,10 @@ def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, hist
88149
model_params_setting = reference_data.get('model_params_setting')
89150

90151
workspace_id = self.workflow_manage.get_body().get('workspace_id')
152+
if model_setting is None:
153+
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
154+
'reasoning_content_start': '<think>'}
155+
self.context['model_setting'] = model_setting
91156
video_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
92157
**(model_params_setting or {}))
93158
# 执行详情中的历史消息不需要图片内容
@@ -251,6 +316,7 @@ def get_details(self, index: int, **kwargs):
251316
'history_message') is not None else [])],
252317
'question': self.context.get('question'),
253318
'answer': self.context.get('answer'),
319+
'reasoning_content': self.context.get('reasoning_content'),
254320
'type': self.node.type,
255321
'message_tokens': self.context.get('message_tokens'),
256322
'answer_tokens': self.context.get('answer_tokens'),

apps/application/sql/list_application_chat_ee.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ select application_chat.*,
22
(CASE
33
WHEN "chat_user".id is NULL THEN application_chat.asker
44
ELSE jsonb_build_object('id', chat_user.id, 'username',
5-
chat_user.username) END) ::json AS asker, application_chat.source::json AS source
5+
chat_user.nick_name) END) ::json AS asker, application_chat.source::json AS source
66
from application_chat application_chat
77
left join chat_user chat_user on chat_user.id::varchar = application_chat.chat_user_id ${default_queryset}

0 commit comments

Comments
 (0)