Skip to content

Commit 5e0ce50

Browse files
authored
Merge branch '1Panel-dev:main' into main
2 parents 7b41a09 + 56a09d7 commit 5e0ce50

File tree

167 files changed

+6232
-2241
lines changed

Some content is hidden

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

167 files changed

+6232
-2241
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
3939

4040
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
4141
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
42-
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html)
42+
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html)
43+
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202411.pdf)
4344

4445
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
4546

apps/application/flow/i_step_node.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@desc:
88
"""
99
import time
10+
import uuid
1011
from abc import abstractmethod
1112
from typing import Type, Dict, List
1213

@@ -31,7 +32,7 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
3132
if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'answer' in step_variable:
3233
answer = step_variable['answer']
3334
yield answer
34-
workflow.answer += answer
35+
node.answer_text = answer
3536
if global_variable is not None:
3637
for key in global_variable:
3738
workflow.context[key] = global_variable[key]
@@ -54,15 +55,27 @@ def handler(self, chat_id,
5455
'message_tokens' in row and row.get('message_tokens') is not None])
5556
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
5657
'answer_tokens' in row and row.get('answer_tokens') is not None])
57-
chat_record = ChatRecord(id=chat_record_id,
58-
chat_id=chat_id,
59-
problem_text=question,
60-
answer_text=answer,
61-
details=details,
62-
message_tokens=message_tokens,
63-
answer_tokens=answer_tokens,
64-
run_time=time.time() - workflow.context['start_time'],
65-
index=0)
58+
answer_text_list = workflow.get_answer_text_list()
59+
answer_text = '\n\n'.join(answer_text_list)
60+
if workflow.chat_record is not None:
61+
chat_record = workflow.chat_record
62+
chat_record.answer_text = answer_text
63+
chat_record.details = details
64+
chat_record.message_tokens = message_tokens
65+
chat_record.answer_tokens = answer_tokens
66+
chat_record.answer_text_list = answer_text_list
67+
chat_record.run_time = time.time() - workflow.context['start_time']
68+
else:
69+
chat_record = ChatRecord(id=chat_record_id,
70+
chat_id=chat_id,
71+
problem_text=question,
72+
answer_text=answer_text,
73+
details=details,
74+
message_tokens=message_tokens,
75+
answer_tokens=answer_tokens,
76+
answer_text_list=answer_text_list,
77+
run_time=time.time() - workflow.context['start_time'],
78+
index=0)
6679
self.chat_info.append_chat_record(chat_record, self.client_id)
6780
# 重新设置缓存
6881
chat_cache.set(chat_id,
@@ -118,7 +131,15 @@ class FlowParamsSerializer(serializers.Serializer):
118131

119132

120133
class INode:
121-
def __init__(self, node, workflow_params, workflow_manage):
134+
135+
@abstractmethod
136+
def save_context(self, details, workflow_manage):
137+
pass
138+
139+
def get_answer_text(self):
140+
return self.answer_text
141+
142+
def __init__(self, node, workflow_params, workflow_manage, runtime_node_id=None):
122143
# 当前步骤上下文,用于存储当前步骤信息
123144
self.status = 200
124145
self.err_message = ''
@@ -129,7 +150,12 @@ def __init__(self, node, workflow_params, workflow_manage):
129150
self.node_params_serializer = None
130151
self.flow_params_serializer = None
131152
self.context = {}
153+
self.answer_text = None
132154
self.id = node.id
155+
if runtime_node_id is None:
156+
self.runtime_node_id = str(uuid.uuid1())
157+
else:
158+
self.runtime_node_id = runtime_node_id
133159

134160
def valid_args(self, node_params, flow_params):
135161
flow_params_serializer_class = self.get_flow_params_serializer_class()

apps/application/flow/step_node/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,25 @@
77
@desc:
88
"""
99
from .ai_chat_step_node import *
10+
from .application_node import BaseApplicationNode
1011
from .condition_node import *
11-
from .question_node import *
12-
from .search_dataset_node import *
13-
from .start_node import *
1412
from .direct_reply_node import *
13+
from .form_node import *
1514
from .function_lib_node import *
1615
from .function_node import *
16+
from .question_node import *
1717
from .reranker_node import *
1818

19+
from .document_extract_node import *
20+
from .image_understand_step_node import *
21+
22+
from .search_dataset_node import *
23+
from .start_node import *
24+
1925
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode,
20-
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode]
26+
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode,
27+
BaseDocumentExtractNode,
28+
BaseImageUnderstandNode, BaseFormNode]
2129

2230

2331
def get_node(node_type):

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wo
3232
node.context['question'] = node_variable['question']
3333
node.context['run_time'] = time.time() - node.context['start_time']
3434
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
35-
workflow.answer += answer
35+
node.answer_text = answer
3636

3737

3838
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
@@ -73,6 +73,11 @@ def get_default_model_params_setting(model_id):
7373

7474

7575
class BaseChatNode(IChatNode):
76+
def save_context(self, details, workflow_manage):
77+
self.context['answer'] = details.get('answer')
78+
self.context['question'] = details.get('question')
79+
self.answer_text = details.get('answer')
80+
7681
def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,
7782
model_params_setting=None,
7883
**kwargs) -> NodeResult:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# coding=utf-8
2+
from .impl import *
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# coding=utf-8
2+
from typing import Type
3+
4+
from rest_framework import serializers
5+
6+
from application.flow.i_step_node import INode, NodeResult
7+
from common.util.field_message import ErrMessage
8+
9+
10+
class ApplicationNodeSerializer(serializers.Serializer):
11+
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char("应用id"))
12+
question_reference_address = serializers.ListField(required=True, error_messages=ErrMessage.list("用户问题"))
13+
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list("api输入字段"))
14+
user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid("用户输入字段"))
15+
16+
17+
class IApplicationNode(INode):
18+
type = 'application-node'
19+
20+
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
21+
return ApplicationNodeSerializer
22+
23+
def _run(self):
24+
question = self.workflow_manage.get_reference_field(
25+
self.node_params_serializer.data.get('question_reference_address')[0],
26+
self.node_params_serializer.data.get('question_reference_address')[1:])
27+
kwargs = {}
28+
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
29+
kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(api_input_field['value'][0],
30+
api_input_field['value'][1:])
31+
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
32+
kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(user_input_field['value'][0],
33+
user_input_field['value'][1:])
34+
35+
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,
36+
message=str(question), **kwargs)
37+
38+
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
39+
**kwargs) -> NodeResult:
40+
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# coding=utf-8
2+
from .base_application_node import BaseApplicationNode
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# coding=utf-8
2+
import json
3+
import time
4+
import uuid
5+
from typing import List, Dict
6+
from application.flow.i_step_node import NodeResult, INode
7+
from application.flow.step_node.application_node.i_application_node import IApplicationNode
8+
from application.models import Chat
9+
from common.handle.impl.response.openai_to_response import OpenaiToResponse
10+
11+
12+
def string_to_uuid(input_str):
13+
return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str))
14+
15+
16+
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
17+
result = node_variable.get('result')
18+
node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)
19+
node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
20+
node.context['answer'] = answer
21+
node.context['question'] = node_variable['question']
22+
node.context['run_time'] = time.time() - node.context['start_time']
23+
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
24+
node.answer_text = answer
25+
26+
27+
def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
28+
"""
29+
写入上下文数据 (流式)
30+
@param node_variable: 节点数据
31+
@param workflow_variable: 全局数据
32+
@param node: 节点
33+
@param workflow: 工作流管理器
34+
"""
35+
response = node_variable.get('result')
36+
answer = ''
37+
usage = {}
38+
for chunk in response:
39+
# 先把流转成字符串
40+
response_content = chunk.decode('utf-8')[6:]
41+
response_content = json.loads(response_content)
42+
choices = response_content.get('choices')
43+
if choices and isinstance(choices, list) and len(choices) > 0:
44+
content = choices[0].get('delta', {}).get('content', '')
45+
answer += content
46+
yield content
47+
usage = response_content.get('usage', {})
48+
node_variable['result'] = {'usage': usage}
49+
_write_context(node_variable, workflow_variable, node, workflow, answer)
50+
51+
52+
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
53+
"""
54+
写入上下文数据
55+
@param node_variable: 节点数据
56+
@param workflow_variable: 全局数据
57+
@param node: 节点实例对象
58+
@param workflow: 工作流管理器
59+
"""
60+
response = node_variable.get('result')['choices'][0]['message']
61+
answer = response.get('content', '') or "抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
62+
_write_context(node_variable, workflow_variable, node, workflow, answer)
63+
64+
65+
class BaseApplicationNode(IApplicationNode):
66+
67+
def save_context(self, details, workflow_manage):
68+
self.context['answer'] = details.get('answer')
69+
self.context['question'] = details.get('question')
70+
self.context['type'] = details.get('type')
71+
self.answer_text = details.get('answer')
72+
73+
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
74+
**kwargs) -> NodeResult:
75+
from application.serializers.chat_message_serializers import ChatMessageSerializer
76+
# 生成嵌入应用的chat_id
77+
current_chat_id = string_to_uuid(chat_id + application_id)
78+
Chat.objects.get_or_create(id=current_chat_id, defaults={
79+
'application_id': application_id,
80+
'abstract': message
81+
})
82+
response = ChatMessageSerializer(
83+
data={'chat_id': current_chat_id, 'message': message,
84+
're_chat': re_chat,
85+
'stream': stream,
86+
'application_id': application_id,
87+
'client_id': client_id,
88+
'client_type': client_type, 'form_data': kwargs}).chat(base_to_response=OpenaiToResponse())
89+
if response.status_code == 200:
90+
if stream:
91+
content_generator = response.streaming_content
92+
return NodeResult({'result': content_generator, 'question': message}, {},
93+
_write_context=write_context_stream)
94+
else:
95+
data = json.loads(response.content)
96+
return NodeResult({'result': data, 'question': message}, {},
97+
_write_context=write_context)
98+
99+
def get_details(self, index: int, **kwargs):
100+
global_fields = []
101+
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
102+
global_fields.append({
103+
'label': api_input_field['variable'],
104+
'key': api_input_field['variable'],
105+
'value': self.workflow_manage.get_reference_field(
106+
api_input_field['value'][0],
107+
api_input_field['value'][1:])
108+
})
109+
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
110+
global_fields.append({
111+
'label': user_input_field['label'],
112+
'key': user_input_field['field'],
113+
'value': self.workflow_manage.get_reference_field(
114+
user_input_field['value'][0],
115+
user_input_field['value'][1:])
116+
})
117+
return {
118+
'name': self.node.properties.get('stepName'),
119+
"index": index,
120+
"info": self.node.properties.get('node_data'),
121+
'run_time': self.context.get('run_time'),
122+
'question': self.context.get('question'),
123+
'answer': self.context.get('answer'),
124+
'type': self.node.type,
125+
'message_tokens': self.context.get('message_tokens'),
126+
'answer_tokens': self.context.get('answer_tokens'),
127+
'status': self.status,
128+
'err_message': self.err_message,
129+
'global_fields': global_fields
130+
}

apps/application/flow/step_node/condition_node/impl/base_condition_node.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515

1616
class BaseConditionNode(IConditionNode):
17+
def save_context(self, details, workflow_manage):
18+
self.context['branch_id'] = details.get('branch_id')
19+
self.context['branch_name'] = details.get('branch_name')
20+
1721
def execute(self, **kwargs) -> NodeResult:
1822
branch_list = self.node_params_serializer.data['branch']
1923
branch = self._execute(branch_list)

apps/application/flow/step_node/direct_reply_node/impl/base_reply_node.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414

1515
class BaseReplyNode(IReplyNode):
16+
def save_context(self, details, workflow_manage):
17+
self.context['answer'] = details.get('answer')
18+
self.answer_text = details.get('answer')
1619
def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:
1720
if reply_type == 'referencing':
1821
result = self.get_reference_content(fields)

0 commit comments

Comments
 (0)