Skip to content

Commit 44c0a81

Browse files
Merge remote-tracking branch 'upstream/v2' into PR/feat-1-tool
2 parents 6a0c416 + dc1c04d commit 44c0a81

58 files changed

Lines changed: 927 additions & 514 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
inputs:
88
dockerImageTag:
99
description: 'Image Tag'
10-
default: 'v2.9.0-dev'
10+
default: 'v2.10.0-dev'
1111
required: true
1212
dockerImageTagWithLatest:
1313
description: '是否发布latest tag(正式发版时选择,测试版本切勿选择)'

apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,10 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
279279
continue
280280
executor = ToolExecutor()
281281
if tool.init_params is not None:
282-
params = json.loads(rsa_long_decrypt(tool.init_params))
283282
tool_init_params = json.loads(rsa_long_decrypt(tool.init_params))
284283
else:
285-
params = {}
286-
tool_config = executor.get_tool_mcp_config(tool, params)
284+
tool_init_params = {i["field"]: i.get('default_value') for i in tool.init_field_list}
285+
tool_config = executor.get_tool_mcp_config(tool, tool_init_params)
287286

288287
mcp_servers_config[str(tool.id)] = tool_config
289288

apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class ChatNodeSerializer(serializers.Serializer):
4949
label=_("Skill IDs"), )
5050
mcp_output_enable = serializers.BooleanField(required=False, default=True, label=_("Whether to enable MCP output"))
5151

52+
video_list = serializers.ListField(required=False, label=_("video"))
53+
54+
image_list = serializers.ListField(required=False, label=_("picture"))
55+
5256

5357
class IChatNode(INode):
5458
type = 'ai-chat-node'

apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
@date:2024/6/4 14:30
77
@desc:
88
"""
9+
import base64
910
import json
1011
import re
1112
import time
1213
from functools import reduce
14+
from imghdr import what
1315
from typing import List, Dict
1416

1517
from django.db.models import QuerySet
@@ -25,6 +27,7 @@
2527
from common.utils.rsa_util import rsa_long_decrypt
2628
from common.utils.shared_resource_auth import filter_authorized_ids
2729
from common.utils.tool_code import ToolExecutor
30+
from knowledge.models import File
2831
from models_provider.models import Model
2932
from models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id
3033
from tools.models import Tool, ToolType
@@ -196,11 +199,11 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record
196199
self.runtime_node_id)
197200
self.context['history_message'] = [{'content': message.content, 'role': message.type} for message in
198201
(history_message if history_message is not None else [])]
199-
question = self.generate_prompt_question(prompt)
202+
question = self.generate_prompt_question(prompt, chat_model)
200203
self.context['question'] = question.content
201204
system = self.workflow_manage.generate_prompt(system)
202205
self.context['system'] = system
203-
message_list = self.generate_message_list(prompt, history_message)
206+
message_list = self.generate_message_list(question, history_message)
204207
self.context['message_list'] = message_list
205208

206209
# 过滤tool_id
@@ -274,11 +277,10 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids
274277
continue
275278
executor = ToolExecutor()
276279
if tool.init_params is not None:
277-
params = json.loads(rsa_long_decrypt(tool.init_params))
278280
tool_init_params = json.loads(rsa_long_decrypt(tool.init_params))
279281
else:
280-
params = {}
281-
tool_config = executor.get_tool_mcp_config(tool, params)
282+
tool_init_params = {i["field"]: i.get('default_value') for i in tool.init_field_list}
283+
tool_config = executor.get_tool_mcp_config(tool, tool_init_params)
282284

283285
mcp_servers_config[str(tool.id)] = tool_config
284286

@@ -383,14 +385,76 @@ def get_history_message(history_chat_record, dialogue_number, dialogue_type, run
383385
range(start_index if start_index > 0 else 0, len(history_chat_record))], [])
384386
for message in history_message:
385387
if isinstance(message.content, str):
386-
message.content = re.sub('<form_rander>[\d\D]*?<\/form_rander>', '', message.content)
388+
message.content = re.sub(r'<form_rander>.*?<\/form_rander>', '', message.content, flags=re.DOTALL)
387389
return history_message
388390

389-
def generate_prompt_question(self, prompt):
390-
return HumanMessage(self.workflow_manage.generate_prompt(prompt))
391+
def generate_prompt_question(self, prompt, model):
392+
image = self.get_image()
393+
video = self.get_video()
394+
videos = []
395+
images = []
396+
if image:
397+
images = self._process_images(image)
398+
if video:
399+
videos = self._process_videos(video, model)
400+
return HumanMessage(
401+
content=[*videos, *images, {'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)}])
402+
403+
def get_image(self):
404+
if 'image_list' in self.node_params_serializer.data:
405+
image = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('image_list')[0],
406+
self.node_params_serializer.data.get('image_list')[1:])
407+
return image
408+
return None
409+
410+
def get_video(self):
411+
if 'video_list' in self.node_params_serializer.data:
412+
video = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('video_list')[0],
413+
self.node_params_serializer.data.get('video_list')[1:])
414+
return video
415+
return None
391416

392-
def generate_message_list(self, prompt: str, history_message):
393-
return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))]
417+
def _process_videos(self, image, video_model):
418+
videos = []
419+
if isinstance(image, str) and image.startswith('http'):
420+
videos.append({'type': 'video_url', 'video_url': {'url': image}})
421+
elif image is not None and len(image) > 0:
422+
for img in image:
423+
if 'file_id' in img:
424+
file_id = img['file_id']
425+
file = QuerySet(File).filter(id=file_id).first()
426+
url = video_model.upload_file_and_get_url(file.get_bytes(), file.file_name)
427+
videos.append(
428+
{'type': 'video_url', 'video_url': {'url': url}})
429+
elif 'url' in img and img['url'].startswith('http'):
430+
videos.append(
431+
{'type': 'video_url', 'video_url': {'url': img['url']}})
432+
return videos
433+
434+
def _process_images(self, image):
435+
"""
436+
处理图像数据,转换为模型可识别的格式
437+
"""
438+
images = []
439+
if isinstance(image, str) and image.startswith('http'):
440+
images.append({'type': 'image_url', 'image_url': {'url': image}})
441+
elif image is not None and len(image) > 0:
442+
for img in image:
443+
if 'file_id' in img:
444+
file_id = img['file_id']
445+
file = QuerySet(File).filter(id=file_id).first()
446+
image_bytes = file.get_bytes()
447+
base64_image = base64.b64encode(image_bytes).decode("utf-8")
448+
image_format = what(None, image_bytes)
449+
images.append(
450+
{'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})
451+
elif 'url' in img and img['url'].startswith('http'):
452+
images.append(
453+
{'type': 'image_url', 'image_url': {'url': img["url"]}})
454+
return images
455+
456+
def generate_message_list(self, question, history_message):
457+
return [*history_message, question]
394458

395459
@staticmethod
396460
def reset_message_list(message_list: List[BaseMessage], answer_text):

apps/application/flow/step_node/application_node/impl/base_application_node.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,14 @@ def reset_application_node_dict(application_node_dict, runtime_node_id, node_dat
132132
application_node = application_node_dict[key]
133133
if application_node.get('runtime_node_id') == runtime_node_id:
134134
content: str = application_node.get('content')
135-
match = re.search('<form_rander>.*?</form_rander>', content)
135+
match = re.search(r'<form_rander>.*?<\/form_rander>', content, flags=re.DOTALL)
136136
if match:
137137
form_setting_str = match.group().replace('<form_rander>', '').replace('</form_rander>', '')
138138
form_setting = json.loads(form_setting_str)
139139
form_setting['is_submit'] = True
140140
form_setting['form_data'] = node_data
141141
value = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
142-
res = re.sub('<form_rander>.*?</form_rander>',
143-
'${value}', content)
142+
res = re.sub(r'<form_rander>.*?<\/form_rander>', '${value}', content, flags=re.DOTALL)
144143
application_node['content'] = res.replace('${value}', value)
145144
except Exception as e:
146145
maxkb_logger.warning(f'reset_application_node_dict error: {e}', exc_info=True)

apps/application/flow/step_node/intent_node/impl/base_intent_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def get_history_message(history_chat_record, dialogue_number):
133133

134134
for message in history_message:
135135
if isinstance(message.content, str):
136-
message.content = re.sub('<form_rander>[\d\D]*?<\/form_rander>', '', message.content)
136+
message.content = re.sub(r'<form_rander>.*?<\/form_rander>', '', message.content, flags=re.DOTALL)
137137
return history_message
138138

139139
def build_system_prompt(self) -> str:

apps/application/flow/step_node/loop_node/impl/base_loop_node.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ def loop(workflow_manage_new_instance, node: INode, generate_loop):
216216
def get_tokens(loop_node_data):
217217
message_tokens = 0
218218
answer_tokens = 0
219-
for details in loop_node_data:
220-
message_tokens += sum([row.get('message_tokens') for row in details.values() if
219+
for details in (loop_node_data or {}):
220+
message_tokens += sum([row.get('message_tokens') or 0 for row in details.values() if
221221
'message_tokens' in row and row.get('message_tokens') is not None])
222-
answer_tokens += sum([row.get('answer_tokens') for row in details.values() if
222+
answer_tokens += sum([row.get('answer_tokens') or 0 for row in details.values() if
223223
'answer_tokens' in row and row.get('answer_tokens') is not None])
224224
return {'message_tokens': message_tokens, 'answer_tokens': answer_tokens}
225225

apps/application/flow/step_node/parameter_extraction_node/impl/base_parameter_extraction_node.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,24 @@ def generate_content(input_variable, variable_list):
6262
return value
6363

6464

65-
def json_loads(response, expected_fields):
65+
def json_loads(response, variable_list):
6666
if not response or not isinstance(response, str):
67-
return {field: None for field in expected_fields}
67+
return generate_example(variable_list)
6868

6969
cleaned = response.strip()
7070

7171
extraction_strategies = [
7272
lambda: json.loads(cleaned),
7373
lambda: json.loads(re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned, re.DOTALL).group(1)),
74-
lambda: json.loads(re.search(r'(\{[\s\S]*\})', cleaned).group(1)),
74+
lambda: json.loads(re.search(r'(\{.*\})', cleaned, flags=re.DOTALL).group(1)),
7575
]
7676
for strategy in extraction_strategies:
7777
try:
7878
result = strategy()
7979
return result
8080
except:
8181
continue
82-
return generate_example(expected_fields)
82+
return generate_example(variable_list)
8383

8484

8585
class BaseParameterExtractionNode(IParameterExtractionNode):

apps/application/flow/step_node/question_node/impl/base_question_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def get_history_message(history_chat_record, dialogue_number):
130130
range(start_index if start_index > 0 else 0, len(history_chat_record))], [])
131131
for message in history_message:
132132
if isinstance(message.content, str):
133-
message.content = re.sub('<form_rander>[\d\D]*?<\/form_rander>', '', message.content)
133+
message.content = re.sub(r'<form_rander>.*?<\/form_rander>', '', message.content, flags=re.DOTALL)
134134
return history_message
135135

136136
def generate_prompt_question(self, prompt):

apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,24 @@ def get_field_value(debug_field_list, name, is_required):
5858

5959

6060
def valid_reference_value(_type, value, name):
61-
try:
62-
if _type == 'int':
63-
instance_type = int | float
64-
elif _type == 'boolean':
65-
instance_type = bool
66-
elif _type == 'float':
67-
instance_type = float | int
68-
elif _type == 'dict':
69-
value = json.loads(value) if isinstance(value, str) else value
70-
instance_type = dict
71-
elif _type == 'array':
72-
value = json.loads(value) if isinstance(value, str) else value
73-
instance_type = list
74-
elif _type == 'string':
75-
instance_type = str
76-
else:
77-
raise Exception(_(
78-
'Field: {name} Type: {_type} Value: {value} Unsupported types'
79-
).format(name=name, _type=_type, value=value))
80-
except:
61+
if _type == 'int':
62+
instance_type = int | float
63+
elif _type == 'boolean':
64+
instance_type = bool
65+
elif _type == 'float':
66+
instance_type = float | int
67+
elif _type == 'dict':
68+
value = json.loads(value) if isinstance(value, str) else value
69+
instance_type = dict
70+
elif _type == 'array':
71+
value = json.loads(value) if isinstance(value, str) else value
72+
instance_type = list
73+
elif _type == 'string':
74+
instance_type = str
75+
else:
76+
maxkb_logger.error(_(
77+
'Field: {name} Type: {_type} Value: {value} Unsupported this type'
78+
).format(name=name, _type=_type, value=value))
8179
return value
8280
if not isinstance(value, instance_type):
8381
raise Exception(_(

0 commit comments

Comments
 (0)