Skip to content

Commit 267bdae

Browse files
committed
feat: enhance video processing by adding video model parameter and implement file upload to OSS
1 parent 8bfbac6 commit 267bdae

File tree

3 files changed

+134
-17
lines changed

3 files changed

+134
-17
lines changed

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,24 +168,22 @@ def generate_history_human_message(self, chat_record):
168168
def generate_prompt_question(self, prompt):
169169
return HumanMessage(self.workflow_manage.generate_prompt(prompt))
170170

171-
def _process_videos(self, image):
171+
def _process_videos(self, image, video_model):
172172
videos = []
173173
if isinstance(image, str) and image.startswith('http'):
174174
videos.append({'type': 'video_url', 'video_url': {'url': image}})
175175
elif image is not None and len(image) > 0:
176176
for img in image:
177177
file_id = img['file_id']
178178
file = QuerySet(File).filter(id=file_id).first()
179-
video_bytes = file.get_bytes()
180-
base64_video = base64.b64encode(video_bytes).decode("utf-8")
181-
video_format = mimetypes.guess_type(file.file_name)[0] # 获取MIME类型
179+
url = video_model.upload_file_and_get_url(file.get_bytes(), file.file_name)
182180
videos.append(
183-
{'type': 'video_url', 'video_url': {'url': f'data:{video_format};base64,{base64_video}'}})
181+
{'type': 'video_url', 'video_url': {'url': url}})
184182
return videos
185183

186184
def generate_message_list(self, video_model, system: str, prompt: str, history_message, video):
187185
prompt_text = self.workflow_manage.generate_prompt(prompt)
188-
videos = self._process_videos(video)
186+
videos = self._process_videos(video, video_model)
189187

190188
if videos:
191189
messages = [HumanMessage(content=[{'type': 'text', 'text': prompt_text}, *videos])]

apps/chat/serializers/chat.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,10 @@ def chat(self, instance: dict, base_to_response: BaseToResponse = SystemToRespon
143143
"application_id": chat_info.application.id, "debug": True
144144
}).chat(instance, base_to_response)
145145

146+
146147
SYSTEM_ROLE = get_file_content(os.path.join(PROJECT_DIR, "apps", "chat", 'template', 'generate_prompt_system'))
147148

149+
148150
class PromptGenerateSerializer(serializers.Serializer):
149151
workspace_id = serializers.CharField(required=False, label=_('Workspace ID'))
150152
model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model"))
@@ -156,13 +158,13 @@ def is_valid(self, *, raise_exception=False):
156158
query_set = QuerySet(Application).filter(id=self.data.get('application_id'))
157159
if workspace_id:
158160
query_set = query_set.filter(workspace_id=workspace_id)
159-
application=query_set.first()
161+
application = query_set.first()
160162
if application is None:
161163
raise AppApiException(500, _('Application id does not exist'))
162164
return application
163165

164166
def generate_prompt(self, instance: dict):
165-
application=self.is_valid(raise_exception=True)
167+
application = self.is_valid(raise_exception=True)
166168
GeneratePromptSerializers(data=instance).is_valid(raise_exception=True)
167169
workspace_id = self.data.get('workspace_id')
168170
model_id = self.data.get('model_id')
@@ -171,29 +173,32 @@ def generate_prompt(self, instance: dict):
171173

172174
message = messages[-1]['content']
173175
q = prompt.replace("{userInput}", message)
174-
q = q.replace("{application_name}",application.name)
175-
q = q.replace("{detail}",application.desc)
176+
q = q.replace("{application_name}", application.name)
177+
q = q.replace("{detail}", application.desc)
176178

177179
messages[-1]['content'] = q
178-
180+
SUPPORTED_MODEL_TYPES = ["LLM", "IMAGE"]
179181
model_exist = QuerySet(Model).filter(
180182
id=model_id,
181-
model_type="LLM"
183+
model_type__in=SUPPORTED_MODEL_TYPES
182184
).exists()
183185
if not model_exist:
184186
raise Exception(_("Model does not exists or is not an LLM model"))
185187

186188
system_content = SYSTEM_ROLE.format(application_name=application.name, detail=application.desc)
187189

188190
def process():
189-
model = get_model_instance_by_model_workspace_id(model_id=model_id, workspace_id=workspace_id,**application.model_params_setting)
191+
model = get_model_instance_by_model_workspace_id(model_id=model_id, workspace_id=workspace_id,
192+
**application.model_params_setting)
190193
try:
191194
for r in model.stream([SystemMessage(content=system_content),
192-
*[HumanMessage(content=m.get('content')) if m.get('role') == 'user' else AIMessage(
195+
*[HumanMessage(content=m.get('content')) if m.get(
196+
'role') == 'user' else AIMessage(
193197
content=m.get('content')) for m in messages]]):
194198
yield 'data: ' + json.dumps({'content': r.content}) + '\n\n'
195199
except Exception as e:
196200
yield 'data: ' + json.dumps({'error': str(e)}) + '\n\n'
201+
197202
return to_stream_response_simple(process())
198203

199204

apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
# coding=utf-8
22

3-
from typing import Dict
3+
from typing import Dict, Optional, Any, Iterator
44

5+
import requests
56
from langchain_community.chat_models import ChatTongyi
6-
from langchain_core.messages import HumanMessage
7+
from langchain_core.language_models import LanguageModelInput
8+
from langchain_core.messages import HumanMessage, BaseMessageChunk, AIMessage
79
from django.utils.translation import gettext
10+
from langchain_core.runnables import RunnableConfig
11+
812
from models_provider.base_model_provider import MaxKBBaseModel
913
from models_provider.impl.base_chat_open_ai import BaseChatOpenAI
10-
14+
import json
1115

1216
class QwenVLChatModel(MaxKBBaseModel, BaseChatOpenAI):
1317

@@ -32,3 +36,113 @@ def new_instance(model_type, model_name, model_credential: Dict[str, object], **
3236
def check_auth(self, api_key):
3337
chat = ChatTongyi(api_key=api_key, model_name='qwen-max')
3438
chat.invoke([HumanMessage([{"type": "text", "text": gettext('Hello')}])])
39+
40+
def get_upload_policy(self, api_key, model_name):
41+
"""获取文件上传凭证"""
42+
url = "https://dashscope.aliyuncs.com/api/v1/uploads"
43+
headers = {
44+
"Authorization": f"Bearer {api_key}",
45+
"Content-Type": "application/json"
46+
}
47+
params = {
48+
"action": "getPolicy",
49+
"model": model_name
50+
}
51+
52+
response = requests.get(url, headers=headers, params=params)
53+
if response.status_code != 200:
54+
raise Exception(f"Failed to get upload policy: {response.text}")
55+
56+
return response.json()['data']
57+
58+
def upload_file_to_oss(self, policy_data, file_stream, file_name):
59+
"""将文件流上传到临时存储OSS"""
60+
# 构建OSS上传的目标路径
61+
key = f"{policy_data['upload_dir']}/{file_name}"
62+
63+
# 构建上传数据
64+
files = {
65+
'OSSAccessKeyId': (None, policy_data['oss_access_key_id']),
66+
'Signature': (None, policy_data['signature']),
67+
'policy': (None, policy_data['policy']),
68+
'x-oss-object-acl': (None, policy_data['x_oss_object_acl']),
69+
'x-oss-forbid-overwrite': (None, policy_data['x_oss_forbid_overwrite']),
70+
'key': (None, key),
71+
'success_action_status': (None, '200'),
72+
'file': (file_name, file_stream)
73+
}
74+
75+
# 执行上传请求
76+
response = requests.post(policy_data['upload_host'], files=files)
77+
if response.status_code != 200:
78+
raise Exception(f"Failed to upload file: {response.text}")
79+
80+
return f"oss://{key}"
81+
82+
def upload_file_and_get_url(self, file_stream, file_name):
83+
"""上传文件并获取URL"""
84+
# 1. 获取上传凭证,上传凭证接口有限流,超出限流将导致请求失败
85+
policy_data = self.get_upload_policy(self.openai_api_key.get_secret_value(), self.model_name)
86+
# 2. 上传文件到OSS
87+
oss_url = self.upload_file_to_oss(policy_data, file_stream, file_name)
88+
print(oss_url)
89+
90+
return oss_url
91+
92+
def stream(
93+
self,
94+
input: LanguageModelInput,
95+
config: Optional[RunnableConfig] = None,
96+
*,
97+
stop: Optional[list[str]] = None,
98+
**kwargs: Any,
99+
) -> Iterator[BaseMessageChunk]:
100+
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
101+
102+
headers = {
103+
"Authorization": f"Bearer {self.openai_api_key.get_secret_value()}",
104+
"Content-Type": "application/json",
105+
"X-DashScope-OssResourceResolve": "enable"
106+
}
107+
108+
data = {
109+
"model": self.model_name,
110+
"messages": [
111+
{
112+
"role": "user",
113+
"content": input[0].content
114+
}
115+
],
116+
**self.extra_body,
117+
"stream": True,
118+
}
119+
response = requests.post(url, headers=headers, json=data)
120+
if response.status_code != 200:
121+
raise Exception(f"Failed to get response: {response.text}")
122+
for line in response.iter_lines():
123+
if line:
124+
try:
125+
decoded_line = line.decode('utf-8')
126+
# 检查是否是有效的SSE数据行
127+
if decoded_line.startswith('data: '):
128+
# 提取JSON部分
129+
json_str = decoded_line[6:] # 移除 'data: ' 前缀
130+
# 检查是否是结束标记
131+
if json_str.strip() == '[DONE]':
132+
continue
133+
134+
# 尝试解析JSON
135+
chunk_data = json.loads(json_str)
136+
137+
if 'choices' in chunk_data and chunk_data['choices']:
138+
delta = chunk_data['choices'][0].get('delta', {})
139+
content = delta.get('content', '')
140+
if content:
141+
print(content)
142+
yield AIMessage(content=content)
143+
except json.JSONDecodeError:
144+
# 忽略无法解析的行
145+
continue
146+
except Exception as e:
147+
# 处理其他可能的异常
148+
continue

0 commit comments

Comments
 (0)