Skip to content

Commit 7ec8317

Browse files
authored
Merge branch 'v2' into tool-workflow
2 parents 71ba00c + 995c73c commit 7ec8317

187 files changed

Lines changed: 3086 additions & 1130 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.

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*.py]
4+
max_line_length = 120

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

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from common.exception.app_exception import AppApiException
2929
from common.utils.logger import maxkb_logger
3030
from common.utils.rsa_util import rsa_long_decrypt
31+
from common.utils.shared_resource_auth import filter_authorized_ids
3132
from common.utils.tool_code import ToolExecutor
3233
from models_provider.tools import get_model_instance_by_model_workspace_id
3334
from tools.models import Tool
@@ -192,6 +193,7 @@ def execute(self, message_list: List[BaseMessage],
192193
mcp_tool_ids, mcp_servers, mcp_source, tool_ids,
193194
application_ids,
194195
skill_tool_ids,
196+
workspace_id,
195197
mcp_output_enable)
196198
else:
197199
return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model,
@@ -201,6 +203,7 @@ def execute(self, message_list: List[BaseMessage],
201203
mcp_tool_ids, mcp_servers, mcp_source, tool_ids,
202204
application_ids,
203205
skill_tool_ids,
206+
workspace_id,
204207
mcp_output_enable)
205208

206209
def get_details(self, manage, **kwargs):
@@ -211,9 +214,9 @@ def get_details(self, manage, **kwargs):
211214
'run_time': self.context.get('run_time') or 0,
212215
'model_id': str(manage.context['model_id']),
213216
'message_list': self.reset_message_list(self.context['step_args'].get('message_list'),
214-
self.context['answer_text']),
215-
'message_tokens': self.context['message_tokens'],
216-
'answer_tokens': self.context['answer_tokens'],
217+
self.context.get('answer_text')),
218+
'message_tokens': self.context.get('message_tokens'),
219+
'answer_tokens': self.context.get('answer_tokens'),
217220
'cost': 0,
218221
}
219222

@@ -228,7 +231,8 @@ def reset_message_list(message_list: List[BaseMessage], answer_text):
228231
return result
229232

230233
def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
231-
application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, agent_id):
234+
application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, agent_id,
235+
chat_id):
232236

233237
mcp_servers_config = {}
234238

@@ -298,11 +302,11 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
298302
tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first()
299303
if tool is None or tool.is_active is False:
300304
continue
305+
init_params_default_value = {i["field"]: i.get('default_value') for i in tool.init_field_list}
301306
if tool.init_params is not None:
302-
params = json.loads(rsa_long_decrypt(tool.init_params))
303-
tool_init_params = json.loads(rsa_long_decrypt(tool.init_params))
307+
params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params))
304308
else:
305-
params = {}
309+
params = init_params_default_value
306310

307311
skill_file_items.append({
308312
'tool_id': str(tool.id),
@@ -314,8 +318,10 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
314318
if len(mcp_servers_config) > 0:
315319
source_id = agent_id
316320
source_type = 'APPLICATION'
317-
return mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable,
318-
tool_init_params, source_id, source_type)
321+
return mcp_response_generator(
322+
chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable,
323+
tool_init_params, source_id, source_type, chat_id
324+
)
319325

320326
return None
321327

@@ -330,8 +336,10 @@ def get_stream_result(self, message_list: List[BaseMessage],
330336
tool_ids=None,
331337
application_ids=None,
332338
skill_tool_ids=None,
339+
workspace_id=None,
333340
mcp_output_enable=True,
334-
agent_id=None
341+
agent_id=None,
342+
chat_id=None
335343
):
336344
if paragraph_list is None:
337345
paragraph_list = []
@@ -348,11 +356,22 @@ def get_stream_result(self, message_list: List[BaseMessage],
348356
return iter([AIMessageChunk(
349357
_('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.'))]), False
350358
else:
359+
# 过滤tool_id
360+
all_tool_ids = list(set(
361+
(mcp_tool_ids or []) +
362+
(tool_ids or []) +
363+
(skill_tool_ids or [])
364+
))
365+
authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id))
366+
367+
mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set]
368+
tool_ids = [i for i in (tool_ids or []) if i in authorized_set]
369+
skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set]
351370
# 处理 MCP 请求
352371
mcp_result = self._handle_mcp_request(
353372
mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
354373
application_ids, skill_tool_ids, mcp_output_enable, chat_model,
355-
message_list, agent_id
374+
message_list, agent_id, chat_id
356375
)
357376
if mcp_result:
358377
return mcp_result, True
@@ -375,12 +394,14 @@ def execute_stream(self, message_list: List[BaseMessage],
375394
tool_ids=None,
376395
application_ids=None,
377396
skill_tool_ids=None,
397+
workspace_id=None,
378398
mcp_output_enable=True):
379399
chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list,
380400
no_references_setting, problem_text, mcp_tool_ids,
381401
mcp_servers, mcp_source, tool_ids,
382-
application_ids, skill_tool_ids,
383-
mcp_output_enable, manage.context.get('application_id'))
402+
application_ids, skill_tool_ids, workspace_id,
403+
mcp_output_enable, manage.context.get('application_id'),
404+
chat_id)
384405
chat_record_id = self.context.get('step_args', {}).get('chat_record_id') if self.context.get('step_args',
385406
{}).get(
386407
'chat_record_id') else uuid.uuid7()
@@ -405,8 +426,10 @@ def get_block_result(self, message_list: List[BaseMessage],
405426
tool_ids=None,
406427
application_ids=None,
407428
skill_tool_ids=None,
429+
workspace_id=None,
408430
mcp_output_enable=True,
409-
application_id=None
431+
application_id=None,
432+
chat_id=None
410433
):
411434
if paragraph_list is None:
412435
paragraph_list = []
@@ -422,11 +445,22 @@ def get_block_result(self, message_list: List[BaseMessage],
422445
return AIMessage(
423446
_('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.')), False
424447
else:
448+
# 过滤tool_id
449+
all_tool_ids = list(set(
450+
(mcp_tool_ids or []) +
451+
(tool_ids or []) +
452+
(skill_tool_ids or [])
453+
))
454+
authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id))
455+
456+
mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set]
457+
tool_ids = [i for i in (tool_ids or []) if i in authorized_set]
458+
skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set]
425459
# 处理 MCP 请求
426460
mcp_result = self._handle_mcp_request(
427461
mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
428462
application_ids, skill_tool_ids, mcp_output_enable,
429-
chat_model, message_list, application_id
463+
chat_model, message_list, application_id, chat_id
430464
)
431465
if mcp_result:
432466
return mcp_result, True
@@ -448,6 +482,7 @@ def execute_block(self, message_list: List[BaseMessage],
448482
tool_ids=None,
449483
application_ids=None,
450484
skill_tool_ids=None,
485+
workspace_id=None,
451486
mcp_output_enable=True):
452487
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
453488
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
@@ -460,8 +495,9 @@ def execute_block(self, message_list: List[BaseMessage],
460495
chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,
461496
no_references_setting, problem_text,
462497
mcp_tool_ids, mcp_servers, mcp_source,
463-
tool_ids, application_ids, skill_tool_ids,
464-
mcp_output_enable, manage.context.get('application_id'))
498+
tool_ids, application_ids, skill_tool_ids,workspace_id,
499+
mcp_output_enable, manage.context.get('application_id'),
500+
chat_id)
465501
if is_ai_chat:
466502
request_token = chat_model.get_num_tokens_from_messages(message_list)
467503
response_token = chat_model.get_num_tokens(chat_result.content)

apps/application/flow/backend/sandbox_shell.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,64 @@
11
import getpass
2+
import os
3+
import re
24

35
from deepagents.backends import LocalShellBackend
46
from deepagents.backends.protocol import ExecuteResponse
57

68
from maxkb.const import CONFIG
79

8-
_enable_sandbox = bool(CONFIG.get('SANDBOX', 0))
10+
_enable_sandbox = bool(int(CONFIG.get('SANDBOX', 0)))
911
_run_user = 'sandbox' if _enable_sandbox else getpass.getuser()
12+
_sandbox_python_sys_path = CONFIG.get_sandbox_python_package_paths().replace(',', ':')
1013

1114

1215
class SandboxShellBackend(LocalShellBackend):
1316
def __init__(self, root_dir: str, **kwargs):
17+
if 'env' not in kwargs and not kwargs.get('inherit_env', False):
18+
env = os.environ.copy()
19+
path = env.get('PATH', '/usr/bin:/bin')
20+
21+
# 将 sandbox 路径分解为列表,检查每个路径是否已存在
22+
existing_paths = set(path.split(os.pathsep))
23+
sandbox_paths = _sandbox_python_sys_path.split(os.pathsep) if _sandbox_python_sys_path else []
24+
new_paths = [p for p in sandbox_paths if p and p not in existing_paths]
25+
26+
if new_paths:
27+
env['PATH'] = f"{os.pathsep.join(new_paths)}{os.pathsep}{path}"
28+
29+
kwargs['env'] = env
1430
super().__init__(root_dir=root_dir, **kwargs)
1531

32+
def _translate_virtual_paths(self, command: str) -> str:
33+
"""Translate virtual absolute paths in the command to real filesystem paths.
34+
35+
In virtual_mode=True, file tools (ls, glob, read_file) return virtual absolute
36+
paths like /skills/foo.py which map to {root_dir}/skills/foo.py. But execute()
37+
runs a real shell where /skills/foo.py does not exist. This method replaces
38+
any path token that exists under root_dir with its real path, while leaving
39+
genuine system paths (e.g. /usr/bin/python3) untouched.
40+
"""
41+
root = str(self.cwd)
42+
43+
def translate(m: re.Match) -> str:
44+
virtual_path = m.group(0)
45+
real_path = root + virtual_path
46+
return real_path if os.path.lexists(real_path) else virtual_path
47+
48+
# Match absolute-path-like tokens: / followed by a non-whitespace sequence
49+
# that isn't clearly a flag (e.g. avoid matching -/something).
50+
# Only translate when virtual_mode is active.
51+
return re.sub(r'(?<![.\w\-])/[A-Za-z_][^\s\'"\\;|&><:,]*', translate, command)
52+
1653
def execute(
1754
self,
1855
command: str,
1956
*,
2057
timeout: int | None = None,
2158
) -> ExecuteResponse:
59+
if self.virtual_mode:
60+
command = self._translate_virtual_paths(command)
61+
2262
if _enable_sandbox:
2363
# 用 runuser 在子进程里切换用户,父进程凭据保持不变,
2464
# 避免父进程 ruid/euid 不一致导致 execve 报 Permission denied

apps/application/flow/compare/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
from .len_lt_compare import *
2525
from .lt_compare import *
2626
from .not_contain_compare import *
27+
from .not_equal_compare import *
2728
from .start_with import StartWithCompare
2829

2930
compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(),
3031
LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(),
3132
IsNullCompare(),
32-
IsNotNullCompare(), NotContainCompare(), IsTrueCompare(), IsNotTrueCompare(), StartWithCompare(),
33+
IsNotNullCompare(), NotContainCompare(), NotEqualCompare(), IsTrueCompare(), IsNotTrueCompare(), StartWithCompare(),
3334
EndWithCompare()]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# coding=utf-8
2+
"""
3+
@project: maxkb
4+
@Author:wangliang181230
5+
@file: not_equal_compare.py
6+
@date:2026/3/17 9:41
7+
@desc:
8+
"""
9+
from typing import List
10+
11+
from application.flow.compare import Compare
12+
13+
14+
class NotEqualCompare(Compare):
15+
16+
def support(self, node_id, fields: List[str], source_value, compare, target_value):
17+
if compare == 'not_eq':
18+
return True
19+
20+
def compare(self, source_value, compare, target_value):
21+
return str(source_value) != str(target_value)

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@
1212
from functools import reduce
1313
from typing import List, Dict
1414

15-
from django.db.models import QuerySet
16-
from django.utils.translation import gettext as _
17-
from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage
18-
1915
from application.flow.i_step_node import NodeResult, INode
2016
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
2117
from application.flow.tools import Reasoning, mcp_response_generator
2218
from application.models import Application, ApplicationApiKey, ApplicationAccessToken
2319
from common.exception.app_exception import AppApiException
2420
from common.utils.rsa_util import rsa_long_decrypt
21+
from common.utils.shared_resource_auth import filter_authorized_ids
2522
from common.utils.tool_code import ToolExecutor
23+
from django.db.models import QuerySet
24+
from django.utils.translation import gettext as _
25+
from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage
2626
from models_provider.models import Model
2727
from models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id
2828
from tools.models import Tool
@@ -199,11 +199,24 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record
199199
message_list = self.generate_message_list(system, prompt, history_message)
200200
self.context['message_list'] = message_list
201201

202+
# 过滤tool_id
203+
all_tool_ids = list(set(
204+
(mcp_tool_ids or []) +
205+
(tool_ids or []) +
206+
(skill_tool_ids or []) +
207+
([mcp_tool_id] if mcp_tool_id else [])
208+
))
209+
authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id))
210+
211+
mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set]
212+
tool_ids = [i for i in (tool_ids or []) if i in authorized_set]
213+
skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set]
214+
mcp_tool_id = mcp_tool_id if (mcp_tool_id and mcp_tool_id in authorized_set) else None
202215
# 处理 MCP 请求
203216
mcp_result = self._handle_mcp_request(
204217
mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids,
205218
application_ids, skill_tool_ids, mcp_output_enable,
206-
chat_model, message_list, history_message, question
219+
chat_model, message_list, history_message, question, chat_id
207220
)
208221
if mcp_result:
209222
return mcp_result
@@ -223,7 +236,7 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record
223236

224237
def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids,
225238
application_ids, skill_tool_ids,
226-
mcp_output_enable, chat_model, message_list, history_message, question):
239+
mcp_output_enable, chat_model, message_list, history_message, question, chat_id):
227240

228241
mcp_servers_config = {}
229242

@@ -296,11 +309,11 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids
296309
tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first()
297310
if tool is None or tool.is_active is False:
298311
continue
312+
init_params_default_value = {i["field"]: i.get('default_value') for i in tool.init_field_list}
299313
if tool.init_params is not None:
300-
params = json.loads(rsa_long_decrypt(tool.init_params))
301-
tool_init_params = json.loads(rsa_long_decrypt(tool.init_params))
314+
params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params))
302315
else:
303-
params = {}
316+
params = init_params_default_value
304317

305318
skill_file_items.append({
306319
'tool_id': str(tool.id),
@@ -320,7 +333,7 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids
320333
source_id = application_id or knowledge_id
321334
source_type = 'APPLICATION' if application_id else 'KNOWLEDGE'
322335
r = mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable,
323-
tool_init_params, source_id, source_type)
336+
tool_init_params, source_id, source_type, chat_id)
324337
return NodeResult(
325338
{'result': r, 'chat_model': chat_model, 'message_list': message_list,
326339
'history_message': [{'content': message.content, 'role': message.type} for message in

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,15 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
3939
response = node_variable.get('result')
4040
answer = ''
4141
for chunk in response:
42-
answer += chunk.content
43-
yield chunk.content
42+
if isinstance(chunk.content, list):
43+
for chunk_item in chunk.content:
44+
text = chunk_item.get("text", "")
45+
answer += text
46+
yield text
47+
else:
48+
text = chunk.content or ""
49+
answer += text
50+
yield text
4451
_write_context(node_variable, workflow_variable, node, workflow, answer)
4552

4653

0 commit comments

Comments
 (0)