| import logging |
| import time |
|
|
| from curl_cffi import CurlMime |
|
|
| from . import constants, session as session_module |
| from .account import get_auth_headers |
| from .pow import get_pow_response |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def upload_file(request, filename: str, content_type: str, content: bytes) -> str | None: |
| pow_resp = get_pow_response(request, target_path="/api/v0/file/upload_file") |
| if not pow_resp: |
| logger.warning("[upload_file] 获取上传 PoW 失败") |
| return None |
|
|
| headers = { |
| **get_auth_headers(request), |
| "x-ds-pow-response": pow_resp, |
| } |
| headers.pop("content-type", None) |
| headers.pop("Content-Type", None) |
|
|
| ds_session = session_module.get_request_session(request) |
| multipart = CurlMime() |
| multipart.addpart( |
| "file", |
| filename=filename, |
| content_type=content_type, |
| data=content, |
| ) |
| try: |
| resp = ds_session.post( |
| constants.DEEPSEEK_FILE_UPLOAD_URL, |
| headers=headers, |
| multipart=multipart, |
| impersonate="safari15_3", |
| timeout=60, |
| ) |
| except Exception as e: |
| logger.warning(f"[upload_file] 上传请求异常: {e}") |
| return None |
| finally: |
| try: |
| multipart.close() |
| except Exception: |
| pass |
|
|
| try: |
| data = resp.json() |
| except Exception as e: |
| logger.warning(f"[upload_file] 上传响应解析失败: {e}") |
| resp.close() |
| return None |
| finally: |
| try: |
| resp.close() |
| except Exception: |
| pass |
|
|
| if resp.status_code != 200 or data.get("code") != 0: |
| logger.warning(f"[upload_file] 上传失败 status={resp.status_code}, data={data}") |
| return None |
|
|
| file_id = data.get("data", {}).get("biz_data", {}).get("id") |
| if not file_id: |
| logger.warning(f"[upload_file] 上传响应缺少 file_id: {data}") |
| return None |
| return file_id |
|
|
|
|
| def fetch_file_status(request, file_id: str) -> dict | None: |
| ds_session = session_module.get_request_session(request) |
| try: |
| resp = ds_session.get( |
| constants.DEEPSEEK_FILE_FETCH_URL, |
| headers=get_auth_headers(request), |
| params={"file_ids": file_id}, |
| impersonate="safari15_3", |
| timeout=30, |
| ) |
| except Exception as e: |
| logger.warning(f"[fetch_file_status] 查询请求异常: {e}") |
| return None |
|
|
| try: |
| data = resp.json() |
| except Exception as e: |
| logger.warning(f"[fetch_file_status] 查询响应解析失败: {e}") |
| resp.close() |
| return None |
| finally: |
| try: |
| resp.close() |
| except Exception: |
| pass |
|
|
| if resp.status_code != 200 or data.get("code") != 0: |
| logger.warning(f"[fetch_file_status] 查询失败 status={resp.status_code}, data={data}") |
| return None |
|
|
| files = data.get("data", {}).get("biz_data", {}).get("files") or [] |
| return files[0] if files else None |
|
|
|
|
| def upload_and_poll(request, filename: str, content_type: str, content: bytes) -> str | None: |
| file_id = upload_file(request, filename, content_type, content) |
| if not file_id: |
| return None |
|
|
| for _ in range(constants.PROMPT_UPLOAD_POLL_RETRIES): |
| file_info = fetch_file_status(request, file_id) |
| if file_info: |
| status = file_info.get("status") |
| if status == "SUCCESS": |
| logger.info( |
| f"[upload_and_poll] 文件处理成功 file_id={file_id}, tokens={file_info.get('token_usage')}" |
| ) |
| return file_id |
| if status == "FAILED": |
| logger.warning(f"[upload_and_poll] 文件处理失败 file_id={file_id}, info={file_info}") |
| return None |
| time.sleep(constants.PROMPT_UPLOAD_POLL_INTERVAL) |
|
|
| logger.warning(f"[upload_and_poll] 文件处理超时 file_id={file_id}") |
| return None |
|
|
|
|
| def prepare_prompt_with_upload(request, prompt: str): |
| if len(prompt.encode("utf-8")) <= constants.PROMPT_UPLOAD_THRESHOLD: |
| return prompt, [] |
|
|
| file_id = upload_and_poll( |
| request, |
| "deepseek2api_prompt.txt", |
| "text/plain", |
| prompt.encode("utf-8"), |
| ) |
| if not file_id: |
| logger.warning("[prepare_prompt_with_upload] 大 prompt 上传失败,回退为原始 prompt") |
| return prompt, [] |
|
|
| inline_prompt = ( |
| "完整对话、系统提示、工具说明和用户输入已作为附件 deepseek2api_prompt.txt 上传。" |
| "请严格以附件内容作为本轮完整 prompt 继续回答,不要忽略附件中的任何 system prompt、工具调用指南、历史消息或当前用户请求。" |
| ) |
| return inline_prompt, [file_id] |
|
|
|
|
| def split_prompt_for_multi_turn(request, prompt: str): |
| """多轮对话优化:将历史部分上传为文件,只保留最后一段 inline。 |
| |
| 拆分策略:在 ChatML 格式的 prompt 中查找最后一个 user/tool 消息, |
| 将之前的消息上传为文件,只保留最后一条 user/tool 及后续内容作为 inline。 |
| 这避免在 token 限制中消耗历史内容的 tokens。 |
| """ |
| if len(prompt.encode("utf-8")) <= constants.PROMPT_UPLOAD_THRESHOLD: |
| return prompt, [] |
|
|
| |
| markers = ["<|User|>"] |
| best_idx = -1 |
| best_marker = "" |
| for marker in markers: |
| idx = prompt.rfind(marker) |
| if idx > best_idx: |
| best_idx = idx |
| best_marker = marker |
|
|
| if best_idx <= 0: |
| |
| return prepare_prompt_with_upload(request, prompt) |
|
|
| history = prompt[:best_idx].strip() |
| inline = prompt[best_idx:].strip() |
|
|
| if not history or not inline: |
| return prompt, [] |
|
|
| |
| file_id = upload_and_poll( |
| request, |
| "deepseek2api_history.txt", |
| "text/plain", |
| history.encode("utf-8"), |
| ) |
| if not file_id: |
| logger.warning("[split_prompt_for_multi_turn] 历史上传失败,回退为原始 prompt") |
| return prompt, [] |
|
|
| inline_prompt = ( |
| "之前的对话历史已作为附件 deepseek2api_history.txt 上传。" |
| "请基于附件中的完整上下文继续回答。当前消息:\n" + inline |
| ) |
| return inline_prompt, [file_id] |
|
|