feat: Add resource mapping table (#4546)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled

This commit is contained in:
shaohuzhang1 2025-12-22 14:52:22 +08:00 committed by GitHub
parent a0d48da406
commit 78be028a0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 241 additions and 11 deletions

View File

@ -315,7 +315,8 @@ async def _yield_mcp_response(chat_model, message_list, mcp_servers, mcp_output_
tools = await client.get_tools() tools = await client.get_tools()
agent = create_react_agent(chat_model, tools) agent = create_react_agent(chat_model, tools)
recursion_limit = int(CONFIG.get("LANGCHAIN_GRAPH_RECURSION_LIMIT", '25')) recursion_limit = int(CONFIG.get("LANGCHAIN_GRAPH_RECURSION_LIMIT", '25'))
response = agent.astream({"messages": message_list}, config={"recursion_limit": recursion_limit}, stream_mode='messages') response = agent.astream({"messages": message_list}, config={"recursion_limit": recursion_limit},
stream_mode='messages')
# 用于存储工具调用信息(按 tool_id以及按 index 聚合分片 # 用于存储工具调用信息(按 tool_id以及按 index 聚合分片
tool_calls_info = {} tool_calls_info = {}
@ -396,7 +397,6 @@ async def _yield_mcp_response(chat_model, message_list, mcp_servers, mcp_output_
raise RuntimeError(error_msg) from None raise RuntimeError(error_msg) from None
def mcp_response_generator(chat_model, message_list, mcp_servers, mcp_output_enable=True): def mcp_response_generator(chat_model, message_list, mcp_servers, mcp_output_enable=True):
"""使用全局事件循环,不创建新实例""" """使用全局事件循环,不创建新实例"""
result_queue = queue.Queue() result_queue = queue.Queue()
@ -427,3 +427,82 @@ def mcp_response_generator(chat_model, message_list, mcp_servers, mcp_output_ena
async def anext_async(agen): async def anext_async(agen):
return await agen.__anext__() return await agen.__anext__()
target_source_node_mapping = {
'TOOL': {'tool-lib-node': lambda n: [n.get('properties').get('node_data').get('tool_lib_id')]},
'MODEL': {'ai-chat-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'question-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'speech-to-text-node': lambda n: [n.get('properties').get('node_data').get('stt_model_id')],
'text-to-speech-node': lambda n: [n.get('properties').get('node_data').get('tts_model_id')],
'image-to-video-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'image-generate-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'intent-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'image-understand-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'parameter-extraction-node': lambda n: [n.get('properties').get('node_data').get('model_id')],
'video-understand-node': lambda n: [n.get('properties').get('node_data').get('model_id')]
},
'KNOWLEDGE': {'search-knowledge-node': lambda n: n.get('properties').get('node_data').get('knowledge_id_list')},
'APPLICATION': {
'application-node': lambda n: [n.get('properties').get('node_data').get('application_id')]
}
}
def get_node_handle_callback(source_type, source_id):
def node_handle_callback(node):
from system_manage.models.resource_mapping import ResourceMapping
response = []
for key, value in target_source_node_mapping.items():
if node.get('type') in value:
call = value.get(node.get('type'))
target_source_id_list = call(node)
for target_source_id in target_source_id_list:
if target_source_id:
response.append(ResourceMapping(source_type=source_type, target_type=key, source_id=source_id,
target_id=target_source_id))
return response
return node_handle_callback
def get_workflow_resource(workflow, node_handle):
response = []
if 'nodes' in workflow:
for node in workflow.get('nodes'):
rs = node_handle(node)
if rs:
for r in rs:
response.append(r)
if node.get('type') == 'loop-node':
r = get_workflow_resource(node.get('properties', {}).get('node_data', {}).get('loop_body'), node_handle)
for rn in r:
response.append(rn)
return list({(str(item.target_type) + str(item.target_id)): item for item in response}.values())
return []
def get_instance_resource(instance, source_type, source_id, target_type, field_call_list):
response = []
from system_manage.models.resource_mapping import ResourceMapping
for field_call in field_call_list:
target_id = field_call(instance)
if target_id:
response.append(ResourceMapping(source_type=source_type, target_type=target_type, source_id=source_id,
target_id=target_id))
return response
def save_workflow_mapping(workflow, source_type, source_id, other_resource_mapping=None):
if not other_resource_mapping:
other_resource_mapping = []
from system_manage.models.resource_mapping import ResourceMapping
from django.db.models import QuerySet
QuerySet(ResourceMapping).filter(source_type=source_type, source_id=source_id).delete()
resource_mapping_list = get_workflow_resource(workflow,
get_node_handle_callback(source_type,
source_id))
if resource_mapping_list:
resource_mapping_list += other_resource_mapping
QuerySet(ResourceMapping).bulk_create(
{(str(item.target_type) + str(item.target_id)): item for item in resource_mapping_list}.values())

View File

@ -30,6 +30,7 @@ from application.flow.common import Workflow
from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \ from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \
ApplicationFolder, ApplicationVersion ApplicationFolder, ApplicationVersion
from application.models.application_access_token import ApplicationAccessToken from application.models.application_access_token import ApplicationAccessToken
from application.serializers.common import update_resource_mapping_by_application
from common import result from common import result
from common.cache_data.application_access_token_cache import del_application_access_token from common.cache_data.application_access_token_cache import del_application_access_token
from common.database_model_manage.database_model_manage import DatabaseModelManage from common.database_model_manage.database_model_manage import DatabaseModelManage
@ -780,6 +781,7 @@ class ApplicationOperateSerializer(serializers.Serializer):
application_access_token.save() application_access_token.save()
else: else:
access_token = application_access_token.access_token access_token = application_access_token.access_token
update_resource_mapping_by_application(self.data.get("application_id"))
del_application_access_token(access_token) del_application_access_token(access_token)
return self.one(with_valid=False) return self.one(with_valid=False)

View File

@ -6,21 +6,18 @@
@date2025/6/9 13:42 @date2025/6/9 13:42
@desc: @desc:
""" """
import json
from typing import List from typing import List
from django.core.cache import cache from django.core.cache import cache
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from application.chat_pipeline.step.chat_step.i_chat_step import PostResponseHandler
from application.models import Application, ChatRecord, Chat, ApplicationVersion, ChatUserType, ApplicationTypeChoices, \ from application.models import Application, ChatRecord, Chat, ApplicationVersion, ChatUserType, ApplicationTypeChoices, \
ApplicationKnowledgeMapping ApplicationKnowledgeMapping
from application.serializers.application_chat import ChatCountSerializer from application.serializers.application_chat import ChatCountSerializer
from common.constants.cache_version import Cache_Version from common.constants.cache_version import Cache_Version
from common.database_model_manage.database_model_manage import DatabaseModelManage from common.database_model_manage.database_model_manage import DatabaseModelManage
from common.encoder.encoder import SystemEncoder
from common.exception.app_exception import ChatException from common.exception.app_exception import ChatException
from knowledge.models import Document from knowledge.models import Document
from models_provider.models import Model from models_provider.models import Model
@ -167,7 +164,7 @@ class ChatInfo:
'mcp_output_enable': self.application.mcp_output_enable, 'mcp_output_enable': self.application.mcp_output_enable,
} }
def to_pipeline_manage_params(self, problem_text: str, post_response_handler: PostResponseHandler, def to_pipeline_manage_params(self, problem_text: str, post_response_handler,
exclude_paragraph_id_list, chat_user_id: str, chat_user_type, stream=True, exclude_paragraph_id_list, chat_user_id: str, chat_user_type, stream=True,
form_data=None): form_data=None):
if form_data is None: if form_data is None:
@ -321,3 +318,19 @@ class ChatInfo:
if chat_info_dict: if chat_info_dict:
return ChatInfo.map_to_chat_info(chat_info_dict) return ChatInfo.map_to_chat_info(chat_info_dict)
return None return None
def update_resource_mapping_by_application(application_id: str):
from application.flow.tools import get_instance_resource, save_workflow_mapping
from system_manage.models.resource_mapping import ResourceType
application = QuerySet(Application).filter(id=application_id).first()
instance_mapping = get_instance_resource(application, ResourceType.APPLICATION, str(application.id),
ResourceType.MODEL,
[lambda i: i.tts_model_id, lambda i: i.stt_model_id, ])
if application.type == 'WORK_FLOW':
save_workflow_mapping(application.work_flow, ResourceType.APPLICATION, str(application_id),
instance_mapping)
return
else:
save_workflow_mapping({}, ResourceType.APPLICATION, str(application_id),
instance_mapping)

View File

@ -16,6 +16,7 @@ from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.tools import save_workflow_mapping
from common.config.embedding_config import ModelManage from common.config.embedding_config import ModelManage
from common.db.search import native_search from common.db.search import native_search
from common.db.sql_execute import sql_execute, update_execute from common.db.sql_execute import sql_execute, update_execute
@ -23,10 +24,11 @@ from common.exception.app_exception import AppApiException
from common.utils.common import get_file_content from common.utils.common import get_file_content
from common.utils.fork import Fork from common.utils.fork import Fork
from common.utils.logger import maxkb_logger from common.utils.logger import maxkb_logger
from knowledge.models import Document from knowledge.models import Document, KnowledgeWorkflow, KnowledgeWorkflowVersion, KnowledgeType
from knowledge.models import Paragraph, Problem, ProblemParagraphMapping, Knowledge, File from knowledge.models import Paragraph, Problem, ProblemParagraphMapping, Knowledge, File
from maxkb.conf import PROJECT_DIR from maxkb.conf import PROJECT_DIR
from models_provider.tools import get_model, get_model_default_params from models_provider.tools import get_model, get_model_default_params
from system_manage.models.resource_mapping import ResourceMapping, ResourceType
class MetaSerializer(serializers.Serializer): class MetaSerializer(serializers.Serializer):
@ -275,3 +277,24 @@ def drop_knowledge_index(knowledge_id=None, document_id=None):
sql = f'DROP INDEX "embedding_hnsw_idx_{k_id}"' sql = f'DROP INDEX "embedding_hnsw_idx_{k_id}"'
update_execute(sql, []) update_execute(sql, [])
maxkb_logger.info(f'Dropped index for knowledge ID: {k_id}') maxkb_logger.info(f'Dropped index for knowledge ID: {k_id}')
def update_resource_mapping_by_knowledge(knowledge_id: str):
knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()
if knowledge.type == KnowledgeType.WORKFLOW:
knowledge_workflow_version = QuerySet(KnowledgeWorkflowVersion).filter(
knowledge_id=knowledge_id).order_by(
'-create_time')[0:1].first()
if knowledge_workflow_version:
other = ResourceMapping(source_type=ResourceType.KNOWLEDGE, target_type=ResourceType.MODEL,
source_id=knowledge.id,
target_id=knowledge.embedding_model_id)
save_workflow_mapping(knowledge_workflow_version.work_flow, ResourceType.KNOWLEDGE,
str(knowledge_id), [other])
return
QuerySet(ResourceMapping).filter(source_type=ResourceType.KNOWLEDGE,
source_id=knowledge.id).delete()
ResourceMapping(source_type=ResourceType.KNOWLEDGE, target_type=ResourceType.MODEL,
source_id=knowledge.id,
target_id=knowledge.embedding_model_id).save()

View File

@ -19,6 +19,7 @@ from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.tools import get_workflow_resource, get_node_handle_callback, save_workflow_mapping
from application.models import ApplicationKnowledgeMapping from application.models import ApplicationKnowledgeMapping
from common.config.embedding_config import VectorStore from common.config.embedding_config import VectorStore
from common.database_model_manage.database_model_manage import DatabaseModelManage from common.database_model_manage.database_model_manage import DatabaseModelManage
@ -34,7 +35,8 @@ from knowledge.models import Knowledge, KnowledgeScope, KnowledgeType, Document,
ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder, File, Tag, KnowledgeWorkflow ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder, File, Tag, KnowledgeWorkflow
from knowledge.serializers.common import ProblemParagraphManage, drop_knowledge_index, \ from knowledge.serializers.common import ProblemParagraphManage, drop_knowledge_index, \
get_embedding_model_id_by_knowledge_id, MetaSerializer, \ get_embedding_model_id_by_knowledge_id, MetaSerializer, \
GenerateRelatedSerializer, get_embedding_model_by_knowledge_id, list_paragraph, write_image, zip_dir GenerateRelatedSerializer, get_embedding_model_by_knowledge_id, list_paragraph, write_image, zip_dir, \
update_resource_mapping_by_knowledge
from knowledge.serializers.document import DocumentSerializers from knowledge.serializers.document import DocumentSerializers
from knowledge.task.embedding import embedding_by_knowledge, delete_embedding_by_knowledge from knowledge.task.embedding import embedding_by_knowledge, delete_embedding_by_knowledge
from knowledge.task.generate import generate_related_by_knowledge_id from knowledge.task.generate import generate_related_by_knowledge_id
@ -42,6 +44,7 @@ from knowledge.task.sync import sync_web_knowledge, sync_replace_web_knowledge
from maxkb.conf import PROJECT_DIR from maxkb.conf import PROJECT_DIR
from models_provider.models import Model from models_provider.models import Model
from system_manage.models import WorkspaceUserResourcePermission, AuthTargetType from system_manage.models import WorkspaceUserResourcePermission, AuthTargetType
from system_manage.models.resource_mapping import ResourceType, ResourceMapping
from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer
from users.serializers.user import is_workspace_manage from users.serializers.user import is_workspace_manage
@ -373,6 +376,7 @@ class KnowledgeSerializer(serializers.Serializer):
KnowledgeEditRequest(data=instance).is_valid(knowledge=knowledge) KnowledgeEditRequest(data=instance).is_valid(knowledge=knowledge)
if 'embedding_model_id' in instance: if 'embedding_model_id' in instance:
knowledge.embedding_model_id = instance.get('embedding_model_id') knowledge.embedding_model_id = instance.get('embedding_model_id')
update_resource_mapping_by_knowledge(self.data.get("knowledge_id"))
if "name" in instance: if "name" in instance:
knowledge.name = instance.get("name") knowledge.name = instance.get("name")
if 'desc' in instance: if 'desc' in instance:

View File

@ -6,6 +6,7 @@ from functools import reduce
from typing import Dict, List from typing import Dict, List
import uuid_utils.compat as uuid import uuid_utils.compat as uuid
from django.core.cache import cache
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpResponse from django.http import HttpResponse
@ -17,6 +18,7 @@ from application.flow.common import Workflow, WorkflowMode
from application.flow.i_step_node import KnowledgeWorkflowPostHandler from application.flow.i_step_node import KnowledgeWorkflowPostHandler
from application.flow.knowledge_workflow_manage import KnowledgeWorkflowManage from application.flow.knowledge_workflow_manage import KnowledgeWorkflowManage
from application.flow.step_node import get_node from application.flow.step_node import get_node
from application.flow.tools import save_workflow_mapping
from application.serializers.application import get_mcp_tools from application.serializers.application import get_mcp_tools
from common.constants.cache_version import Cache_Version from common.constants.cache_version import Cache_Version
from common.db.search import page_search from common.db.search import page_search
@ -28,9 +30,10 @@ from common.utils.rsa_util import rsa_long_decrypt
from common.utils.tool_code import ToolExecutor from common.utils.tool_code import ToolExecutor
from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion
from knowledge.models.knowledge_action import KnowledgeAction, State from knowledge.models.knowledge_action import KnowledgeAction, State
from knowledge.serializers.common import update_resource_mapping_by_knowledge
from knowledge.serializers.knowledge import KnowledgeModelSerializer from knowledge.serializers.knowledge import KnowledgeModelSerializer
from django.core.cache import cache
from system_manage.models import AuthTargetType from system_manage.models import AuthTargetType
from system_manage.models.resource_mapping import ResourceType
from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer
from tools.models import Tool, ToolScope from tools.models import Tool, ToolScope
from tools.serializers.tool import ToolExportModelSerializer from tools.serializers.tool import ToolExportModelSerializer
@ -244,7 +247,7 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
) )
knowledge_workflow.save() knowledge_workflow.save()
save_workflow_mapping(instance.get('work_flow', {}), ResourceType.KNOWLEDGE, str(knowledge_id))
return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []} return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []}
class Import(serializers.Serializer): class Import(serializers.Serializer):
@ -395,6 +398,7 @@ class KnowledgeWorkflowSerializer(serializers.Serializer):
QuerySet(KnowledgeWorkflow).filter( QuerySet(KnowledgeWorkflow).filter(
knowledge_id=self.data.get("knowledge_id") knowledge_id=self.data.get("knowledge_id")
).update(is_publish=True, publish_time=timezone.now()) ).update(is_publish=True, publish_time=timezone.now())
update_resource_mapping_by_knowledge(self.data.get("knowledge_id"))
return True return True
def edit(self, instance: Dict): def edit(self, instance: Dict):

View File

@ -0,0 +1,74 @@
# Generated by Django 5.2.8 on 2025-12-19 09:37
import uuid_utils.compat
from django.db import migrations, models
from knowledge.models import Knowledge
def resource_mapping(apps, schema_editor):
from system_manage.models.resource_mapping import ResourceMapping
from django.db.models import QuerySet
from application.flow.tools import get_workflow_resource, get_node_handle_callback, \
get_instance_resource
from system_manage.models.resource_mapping import ResourceType
from application.models import Application
from knowledge.models import KnowledgeWorkflow
resource_mapping_list = []
for application in QuerySet(Application):
workflow_mapping = get_workflow_resource(application.work_flow,
get_node_handle_callback(ResourceType.APPLICATION,
application.id))
instance_mapping = get_instance_resource(application, ResourceType.APPLICATION, str(application.id),
ResourceType.MODEL,
[lambda i: i.tts_model_id, lambda i: i.stt_model_id, ])
resource_mapping_list += workflow_mapping
resource_mapping_list += instance_mapping
knowledge_workflow_dict = {str(kw.knowledge_id): kw for kw in QuerySet(KnowledgeWorkflow)}
for knowledge in QuerySet(Knowledge):
knowledge_workflow = knowledge_workflow_dict.get(str(knowledge.id))
if knowledge_workflow:
workflow_mapping = get_workflow_resource(knowledge_workflow.work_flow,
get_node_handle_callback(ResourceType.KNOWLEDGE,
str(knowledge_workflow.knowledge_id)))
resource_mapping_list += workflow_mapping
instance_mapping = get_instance_resource(knowledge, ResourceType.KNOWLEDGE, str(knowledge.id),
ResourceType.MODEL,
[lambda i: i.embedding_model_id])
resource_mapping_list += instance_mapping
QuerySet(ResourceMapping).bulk_create(resource_mapping_list)
class Migration(migrations.Migration):
dependencies = [
('system_manage', '0004_alter_systemsetting_type_and_more'),
('knowledge', '0007_remove_knowledgeworkflowversion_workflow_and_more'),
('application', '0003_application_stt_model_params_setting_and_more'),
]
operations = [
migrations.CreateModel(
name='ResourceMapping',
fields=[
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
('id',
models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False,
verbose_name='主键id')),
('source_type', models.CharField(
choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')],
db_index=True, verbose_name='关联资源类型')),
('target_type', models.CharField(
choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')],
db_index=True, verbose_name='被关联资源类型')),
('source_id', models.CharField(db_index=True, max_length=128, verbose_name='关联资源id')),
('target_id', models.CharField(db_index=True, max_length=128, verbose_name='被关联资源id')),
],
options={
'db_table': 'resource_mapping',
},
),
migrations.RunPython(resource_mapping)
]

View File

@ -0,0 +1,31 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file resource_mapping.py
@date2025/12/19 15:41
@desc:
"""
from django.db import models
import uuid_utils.compat as uuid
from common.constants.permission_constants import Group
from common.mixins.app_model_mixin import AppModelMixin
class ResourceType(models.TextChoices):
KNOWLEDGE = Group.KNOWLEDGE.value, '知识库'
APPLICATION = Group.APPLICATION.value, '应用'
TOOL = Group.TOOL.value, '工具'
MODEL = Group.MODEL.value, '模型'
class ResourceMapping(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id")
source_type = models.CharField(verbose_name="关联资源类型", choices=ResourceType.choices, db_index=True)
target_type = models.CharField(verbose_name="被关联资源类型", choices=ResourceType.choices, db_index=True)
source_id = models.CharField(max_length=128, verbose_name="关联资源id", db_index=True)
target_id = models.CharField(max_length=128, verbose_name="被关联资源id", db_index=True)
class Meta:
db_table = "resource_mapping"