fix: make lint && make format

This commit is contained in:
supreme0597 2026-03-20 21:18:13 +08:00
parent b76d0d663e
commit 8b6229d230
24 changed files with 257 additions and 628 deletions

View File

@ -434,9 +434,6 @@ class KubernetesProvisionerBackend:
if discovered is not None:
return discovered
pod_name = self._pod_name(sandbox_id)
service_name = self._service_name(sandbox_id)
try:
self._core_api.create_namespaced_pod(
namespace=self._namespace,

View File

@ -144,4 +144,4 @@ if __name__ == "__main__":
port=5050,
reload=True,
reload_dirs=["server", "src"],
)
)

View File

@ -1,4 +1,5 @@
"""ARQ worker entrypoint."""
import asyncio
import os
import sys
@ -11,4 +12,4 @@ if sys.platform == "win32":
from src.services.run_worker import WorkerSettings
__all__ = ["WorkerSettings"]
__all__ = ["WorkerSettings"]

View File

@ -2,8 +2,8 @@ from __future__ import annotations
from deepagents.backends import CompositeBackend
from src.sandbox import ProvisionerSandboxBackend
from src.agents.common.middlewares.skills_middleware import normalize_selected_skills
from src.sandbox import ProvisionerSandboxBackend
from .skills_backend import SelectedSkillsReadonlyBackend

View File

@ -1,59 +0,0 @@
"""
MinIO Backend for Deep Agents
基于 S3_backend 适配的 MinIO 后端实现
使用环境变量配置 MinIO 连接专用于 Agent 状态存储
"""
from __future__ import annotations
import os
from src.utils.S3_backend import S3Backend, S3Config
__all__ = ["MinIOBackend", "get_minio_backend"]
def get_minio_backend(runtime) -> MinIOBackend:
thread_id = getattr(runtime, "config", {}).get("configurable", {}).get("thread_id")
"""获取 MinIO 后端实例(单例)"""
return MinIOBackend(thread_id)
class MinIOBackend(S3Backend):
"""
基于 S3Backend MinIO 后端
使用环境变量配置
- MINIO_URI: MinIO 端点地址默认: http://milvus-minio:9000
- MINIO_ACCESS_KEY: 访问密钥默认: minioadmin
- MINIO_SECRET_KEY: 密钥默认: minioadmin
默认配置
- bucket: "state-bucket"
- prefix: "threads"
"""
def __init__(self, thread_id: str) -> None:
endpoint = os.getenv("MINIO_URI") or "http://milvus-minio:9000"
access_key = os.getenv("MINIO_ACCESS_KEY") or "minioadmin"
secret_key = os.getenv("MINIO_SECRET_KEY") or "minioadmin"
# 从 endpoint 提取 regionMinIO 通常用 us-east-1
region = os.getenv("MINIO_REGION") or "us-east-1"
config = S3Config(
bucket="agent-state-bucket",
prefix=f"threads/{thread_id}",
region=region,
endpoint_url=endpoint,
access_key_id=access_key,
secret_access_key=secret_key,
use_ssl=endpoint.startswith("https://"),
max_pool_connections=50,
connect_timeout=5.0,
read_timeout=30.0,
max_retries=3,
)
super().__init__(config)

View File

@ -118,6 +118,7 @@ def ask_user_question(
if isinstance(options, str):
try:
import json
options = json.loads(options)
logger.debug(f"Parsed string options to list: {options}")
except Exception as e:
@ -128,6 +129,7 @@ def ask_user_question(
if isinstance(questions, str):
try:
import json
questions = json.loads(questions)
logger.debug(f"Parsed string questions to list: {questions}")
except Exception as e:

View File

@ -78,9 +78,7 @@ class Config(BaseModel):
# Sandbox 配置
# ============================================================
sandbox_provider: str = Field(default="provisioner", description="沙箱提供者")
sandbox_provisioner_url: str = Field(
default="http://sandbox-provisioner:8002", description="沙箱服务地址"
)
sandbox_provisioner_url: str = Field(default="http://sandbox-provisioner:8002", description="沙箱服务地址")
sandbox_virtual_path_prefix: str = Field(default="/mnt/user-data", description="沙箱虚拟路径前缀")
sandbox_exec_timeout_seconds: int = Field(default=180, description="沙箱执行超时时间(秒)")
sandbox_max_output_bytes: int = Field(default=262144, description="沙箱最大输出字节数")
@ -247,18 +245,12 @@ class Config(BaseModel):
self.valuable_model_provider = [k for k, v in self.model_provider_status.items() if v]
# 处理 Sandbox 配置
self.sandbox_provider = (
os.getenv("SANDBOX_PROVIDER") or self.sandbox_provider or "provisioner"
).strip()
self.sandbox_provider = (os.getenv("SANDBOX_PROVIDER") or self.sandbox_provider or "provisioner").strip()
self.sandbox_provisioner_url = (
os.getenv("SANDBOX_PROVISIONER_URL")
or self.sandbox_provisioner_url
or "http://sandbox-provisioner:8002"
os.getenv("SANDBOX_PROVISIONER_URL") or self.sandbox_provisioner_url or "http://sandbox-provisioner:8002"
).strip()
self.sandbox_virtual_path_prefix = (
os.getenv("SANDBOX_VIRTUAL_PATH_PREFIX")
or self.sandbox_virtual_path_prefix
or "/mnt/user-data"
os.getenv("SANDBOX_VIRTUAL_PATH_PREFIX") or self.sandbox_virtual_path_prefix or "/mnt/user-data"
).strip()
self.sandbox_exec_timeout_seconds = int(
os.getenv("SANDBOX_EXEC_TIMEOUT_SECONDS") or self.sandbox_exec_timeout_seconds or 180
@ -267,9 +259,7 @@ class Config(BaseModel):
os.getenv("SANDBOX_MAX_OUTPUT_BYTES") or self.sandbox_max_output_bytes or 262144
)
self.sandbox_keepalive_interval_seconds = int(
os.getenv("SANDBOX_KEEPALIVE_INTERVAL_SECONDS")
or self.sandbox_keepalive_interval_seconds
or 30
os.getenv("SANDBOX_KEEPALIVE_INTERVAL_SECONDS") or self.sandbox_keepalive_interval_seconds or 30
)
# 验证 Sandbox 配置

View File

@ -4,8 +4,8 @@ import base64
from pathlib import PurePosixPath
from typing import Any
from deepagents.backends.sandbox import BaseSandbox
from deepagents.backends.protocol import ExecuteResponse, FileDownloadResponse, FileUploadResponse
from deepagents.backends.sandbox import BaseSandbox
from src import config as conf
from src.utils.logging_config import logger
@ -156,10 +156,7 @@ class ProvisionerSandboxBackend(BaseSandbox):
if not selected_lines:
return ""
return "\n".join(
f"{start + idx + 1:6d}\t{line}"
for idx, line in enumerate(selected_lines)
)
return "\n".join(f"{start + idx + 1:6d}\t{line}" for idx, line in enumerate(selected_lines))
def execute(self, command: str) -> ExecuteResponse:
try:

View File

@ -136,8 +136,7 @@ class ProvisionerSandboxProvider:
self._client.delete(connection.sandbox_id)
except Exception as exc: # noqa: BLE001
logger.warning(
f"Failed to release sandbox {connection.sandbox_id} "
f"for thread {connection.thread_id}: {exc}"
f"Failed to release sandbox {connection.sandbox_id} for thread {connection.thread_id}: {exc}"
)

View File

@ -36,10 +36,7 @@ class ProvisionerClient:
json={"sandbox_id": sandbox_id, "thread_id": thread_id},
)
if response.status_code >= 400:
raise RuntimeError(
f"failed to create sandbox {sandbox_id}: "
f"{response.status_code} {response.text}"
)
raise RuntimeError(f"failed to create sandbox {sandbox_id}: {response.status_code} {response.text}")
payload = response.json()
return SandboxRecord(
sandbox_id=payload["sandbox_id"],
@ -52,10 +49,7 @@ class ProvisionerClient:
if response.status_code == 404:
return None
if response.status_code >= 400:
raise RuntimeError(
f"failed to discover sandbox {sandbox_id}: "
f"{response.status_code} {response.text}"
)
raise RuntimeError(f"failed to discover sandbox {sandbox_id}: {response.status_code} {response.text}")
payload = response.json()
return SandboxRecord(
sandbox_id=payload["sandbox_id"],
@ -68,17 +62,11 @@ class ProvisionerClient:
if response.status_code == 404:
return False
if response.status_code >= 400:
raise RuntimeError(
f"failed to touch sandbox {sandbox_id}: "
f"{response.status_code} {response.text}"
)
raise RuntimeError(f"failed to touch sandbox {sandbox_id}: {response.status_code} {response.text}")
return True
def delete(self, sandbox_id: str) -> None:
response = self._request("DELETE", f"/api/sandboxes/{sandbox_id}")
if response.status_code in {200, 404}:
return
raise RuntimeError(
f"failed to delete sandbox {sandbox_id}: "
f"{response.status_code} {response.text}"
)
raise RuntimeError(f"failed to delete sandbox {sandbox_id}: {response.status_code} {response.text}")

View File

@ -9,7 +9,8 @@ from typing import Any
from langchain.messages import AIMessage, AIMessageChunk, HumanMessage
from langgraph.types import Command
from src import config as conf, knowledge_base
from src import config as conf
from src import knowledge_base
from src.agents import agent_manager
from src.plugins.guard import content_guard
from src.repositories.agent_config_repository import AgentConfigRepository

View File

@ -1,4 +1,4 @@
import shlex
import shlex
import uuid
from pathlib import Path

View File

@ -78,8 +78,8 @@ class PostgresManager(metaclass=SingletonMeta):
# 创建 LangGraph 专属连接池
self.langgraph_pool = AsyncConnectionPool(
conninfo=langgraph_db_url,
max_size=10, # 根据你的 Agent 并发情况设置,通常 5-10 足够了
kwargs={"autocommit": True} # LangGraph Checkpoint 强依赖 autocommit
max_size=10, # 根据你的 Agent 并发情况设置,通常 5-10 足够了
kwargs={"autocommit": True}, # LangGraph Checkpoint 强依赖 autocommit
)
self._initialized = True

View File

@ -1,486 +0,0 @@
"""
Deep Agents Remote Backends
S3 backend implementations for LangChain's Deep Agents.
Supports any S3-compatible storage (AWS S3, MinIO, etc.)
with connection pooling for optimal performance.
"""
from __future__ import annotations
import asyncio
import fnmatch
import json
import re
import threading
from contextlib import asynccontextmanager
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import PurePosixPath
from typing import TYPE_CHECKING, Any, AsyncIterator, Coroutine
import aioboto3
import wcmatch.glob as wcglob
from botocore.config import Config as BotoConfig
from botocore.exceptions import ClientError
from deepagents.backends.protocol import (
BackendProtocol,
EditResult,
FileDownloadResponse,
FileInfo,
FileUploadResponse,
GrepMatch,
WriteResult,
)
from deepagents.backends.utils import (
check_empty_content,
format_content_with_line_numbers,
perform_string_replacement,
)
if TYPE_CHECKING:
from types_aiobotocore_s3 import S3Client
__all__ = ["S3Backend", "S3Config"]
def run_async_safely[T](coroutine: Coroutine[Any, Any, T], timeout: float | None = None) -> T:
try:
asyncio.get_running_loop()
except RuntimeError:
return asyncio.run(coroutine)
result: dict[str, T] = {}
error: dict[str, Exception] = {}
def _run() -> None:
try:
result["value"] = asyncio.run(coroutine)
except Exception as exc: # noqa: BLE001
error["value"] = exc
thread = threading.Thread(target=_run, daemon=True)
thread.start()
thread.join(timeout)
if thread.is_alive():
raise TimeoutError("Timed out while waiting for coroutine result")
if "value" in error:
raise error["value"]
return result["value"]
# =============================================================================
# S3 Backend (S3-compatible: AWS S3, MinIO, etc.)
# =============================================================================
@dataclass
class S3Config:
"""Configuration for S3-compatible storage."""
bucket: str
prefix: str = ""
region: str = "us-east-1"
endpoint_url: str | None = None
access_key_id: str | None = None
secret_access_key: str | None = None
use_ssl: bool = True
max_pool_connections: int = 50
connect_timeout: float = 5.0
read_timeout: float = 30.0
max_retries: int = 3
class S3Backend(BackendProtocol):
"""
S3-compatible backend for Deep Agents file operations.
Supports AWS S3, MinIO, and any S3-compatible object storage.
All operations are async-native using aioboto3.
Files are stored as objects with paths mapping to S3 keys.
Content is stored as JSON with the structure:
{"content": [...lines], "created_at": "...", "modified_at": "..."}
"""
def __init__(self, config: S3Config) -> None:
self._config = config
self._prefix = config.prefix.strip("/")
if self._prefix:
self._prefix += "/"
self._boto_config = BotoConfig(
region_name=config.region,
signature_version="s3v4",
retries={"max_attempts": config.max_retries, "mode": "adaptive"},
max_pool_connections=config.max_pool_connections,
connect_timeout=config.connect_timeout,
read_timeout=config.read_timeout,
)
session_kwargs: dict[str, Any] = {}
if config.access_key_id:
session_kwargs["aws_access_key_id"] = config.access_key_id
if config.secret_access_key:
session_kwargs["aws_secret_access_key"] = config.secret_access_key
self._session = aioboto3.Session(**session_kwargs)
self._bucket = config.bucket
def _s3_key(self, path: str) -> str:
"""Convert virtual path to S3 key."""
clean = path.lstrip("/")
return f"{self._prefix}{clean}"
def _virtual_path(self, key: str) -> str:
"""Convert S3 key to virtual path."""
if self._prefix and key.startswith(self._prefix):
key = key[len(self._prefix) :]
return "/" + key.lstrip("/")
@asynccontextmanager
async def _client(self) -> AsyncIterator["S3Client"]:
"""Get S3 client context."""
async with self._session.client(
"s3",
config=self._boto_config,
endpoint_url=self._config.endpoint_url,
use_ssl=self._config.use_ssl,
) as client:
yield client
async def _get_file_data(self, path: str) -> dict[str, Any] | None:
"""Get file data dict from S3."""
key = self._s3_key(path)
try:
async with self._client() as client:
response = await client.get_object(Bucket=self._bucket, Key=key)
async with response["Body"] as stream:
content = await stream.read()
return json.loads(content.decode("utf-8"))
except ClientError as e:
if e.response["Error"]["Code"] == "NoSuchKey":
return None
raise
async def _put_file_data(
self, path: str, data: dict[str, Any], *, update_modified: bool = True
) -> None:
"""Put file data dict to S3."""
key = self._s3_key(path)
if update_modified:
data["modified_at"] = datetime.now(timezone.utc).isoformat()
content = json.dumps(data).encode("utf-8")
async with self._client() as client:
await client.put_object(
Bucket=self._bucket,
Key=key,
Body=content,
ContentType="application/json",
)
async def _exists(self, path: str) -> bool:
"""Check if file exists in S3."""
key = self._s3_key(path)
try:
async with self._client() as client:
await client.head_object(Bucket=self._bucket, Key=key)
return True
except ClientError as e:
if e.response["Error"]["Code"] == "404":
return False
raise
async def _list_keys(self, prefix: str = "") -> list[dict[str, Any]]:
"""List all keys with a prefix."""
full_prefix = self._s3_key(prefix)
results: list[dict[str, Any]] = []
async with self._client() as client:
paginator = client.get_paginator("list_objects_v2")
async for page in paginator.paginate(
Bucket=self._bucket, Prefix=full_prefix
):
for obj in page.get("Contents", []):
results.append(obj)
return results
# -------------------------------------------------------------------------
# BackendProtocol Implementation
# -------------------------------------------------------------------------
def ls_info(self, path: str) -> list[FileInfo]:
"""Sync wrapper for als_info."""
return run_async_safely(self.als_info(path))
async def als_info(self, path: str) -> list[FileInfo]:
"""List files in a directory."""
prefix = path.lstrip("/")
if prefix and not prefix.endswith("/"):
prefix += "/"
objects = await self._list_keys(prefix)
results: list[FileInfo] = []
seen_dirs: set[str] = set()
for obj in objects:
key = obj["Key"]
vpath = self._virtual_path(key)
# Check if this is a direct child or nested
rel = vpath[len("/" + prefix) :] if prefix else vpath[1:]
if "/" in rel:
# This is in a subdirectory, add the directory entry
dir_name = rel.split("/")[0]
dir_path = "/" + prefix + dir_name + "/"
if dir_path not in seen_dirs:
seen_dirs.add(dir_path)
results.append({"path": dir_path, "is_dir": True})
else:
# Direct file
results.append(
{
"path": vpath,
"is_dir": False,
"size": obj.get("Size", 0),
"modified_at": obj["LastModified"].isoformat()
if "LastModified" in obj
else None,
}
)
results.sort(key=lambda x: x.get("path", ""))
return results
def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> str:
"""Sync wrapper for aread."""
return run_async_safely(
self.aread(file_path, offset, limit)
)
async def aread(self, file_path: str, offset: int = 0, limit: int = 2000) -> str:
"""Read file content with line numbers."""
data = await self._get_file_data(file_path)
if data is None:
return f"Error: File '{file_path}' not found"
lines = data.get("content", [])
if not lines:
empty_msg = check_empty_content("")
if empty_msg:
return empty_msg
if offset >= len(lines):
return f"Error: Line offset {offset} exceeds file length ({len(lines)} lines)"
selected = lines[offset : offset + limit]
return format_content_with_line_numbers(selected, start_line=offset + 1)
def write(self, file_path: str, content: str) -> WriteResult:
"""Sync wrapper for awrite."""
return run_async_safely(
self.awrite(file_path, content)
)
async def awrite(self, file_path: str, content: str) -> WriteResult:
"""Create a new file."""
if await self._exists(file_path):
return WriteResult(
error=f"Cannot write to {file_path} because it already exists. "
"Read and then make an edit, or write to a new path."
)
now = datetime.now(timezone.utc).isoformat()
data = {
"content": content.splitlines(),
"created_at": now,
"modified_at": now,
}
try:
await self._put_file_data(file_path, data, update_modified=False)
return WriteResult(path=file_path, files_update=None)
except Exception as e:
return WriteResult(error=f"Error writing file '{file_path}': {e}")
def edit(
self,
file_path: str,
old_string: str,
new_string: str,
replace_all: bool = False,
) -> EditResult:
"""Sync wrapper for aedit."""
return run_async_safely(
self.aedit(file_path, old_string, new_string, replace_all)
)
async def aedit(
self,
file_path: str,
old_string: str,
new_string: str,
replace_all: bool = False,
) -> EditResult:
"""Edit file by replacing strings."""
data = await self._get_file_data(file_path)
if data is None:
return EditResult(error=f"Error: File '{file_path}' not found")
content = "\n".join(data.get("content", []))
result = perform_string_replacement(content, old_string, new_string, replace_all)
if isinstance(result, str):
return EditResult(error=result)
new_content, occurrences = result
data["content"] = new_content.splitlines()
try:
await self._put_file_data(file_path, data)
return EditResult(
path=file_path, files_update=None, occurrences=int(occurrences)
)
except Exception as e:
return EditResult(error=f"Error editing file '{file_path}': {e}")
def grep_raw(
self, pattern: str, path: str | None = None, glob: str | None = None
) -> list[GrepMatch] | str:
"""Sync wrapper for agrep_raw."""
return run_async_safely(
self.agrep_raw(pattern, path, glob)
)
async def agrep_raw(
self, pattern: str, path: str | None = None, glob: str | None = None
) -> list[GrepMatch] | str:
"""Search for pattern in files."""
try:
regex = re.compile(pattern)
except re.error as e:
return f"Invalid regex pattern: {e}"
search_prefix = (path or "/").lstrip("/")
objects = await self._list_keys(search_prefix)
matches: list[GrepMatch] = []
for obj in objects:
vpath = self._virtual_path(obj["Key"])
filename = PurePosixPath(vpath).name
if glob and not wcglob.globmatch(filename, glob, flags=wcglob.BRACE):
continue
data = await self._get_file_data(vpath)
if data is None:
continue
for line_num, line in enumerate(data.get("content", []), 1):
if regex.search(line):
matches.append({"path": vpath, "line": line_num, "text": line})
return matches
def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
"""Sync wrapper for aglob_info."""
return run_async_safely(
self.aglob_info(pattern, path)
)
async def aglob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
"""Find files matching a glob pattern."""
search_prefix = path.lstrip("/")
objects = await self._list_keys(search_prefix)
results: list[FileInfo] = []
for obj in objects:
vpath = self._virtual_path(obj["Key"])
rel_path = vpath[len(path) :].lstrip("/") if path != "/" else vpath[1:]
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(vpath, pattern):
results.append(
{
"path": vpath,
"is_dir": False,
"size": obj.get("Size", 0),
"modified_at": obj["LastModified"].isoformat()
if "LastModified" in obj
else None,
}
)
results.sort(key=lambda x: x.get("path", ""))
return results
def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
"""Sync wrapper for aupload_files."""
return run_async_safely(self.aupload_files(files))
async def aupload_files(
self, files: list[tuple[str, bytes]]
) -> list[FileUploadResponse]:
"""Upload multiple files."""
responses: list[FileUploadResponse] = []
async with self._client() as client:
for path, content in files:
try:
key = self._s3_key(path)
await client.put_object(
Bucket=self._bucket, Key=key, Body=content
)
responses.append(FileUploadResponse(path=path, error=None))
except ClientError as e:
code = e.response["Error"]["Code"]
if code == "AccessDenied":
responses.append(
FileUploadResponse(path=path, error="permission_denied")
)
else:
responses.append(
FileUploadResponse(path=path, error="invalid_path")
)
except Exception:
responses.append(
FileUploadResponse(path=path, error="invalid_path")
)
return responses
def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
"""Sync wrapper for adownload_files."""
return run_async_safely(self.adownload_files(paths))
async def adownload_files(self, paths: list[str]) -> list[FileDownloadResponse]:
"""Download multiple files."""
responses: list[FileDownloadResponse] = []
async with self._client() as client:
for path in paths:
try:
key = self._s3_key(path)
response = await client.get_object(Bucket=self._bucket, Key=key)
async with response["Body"] as stream:
content = await stream.read()
responses.append(
FileDownloadResponse(path=path, content=content, error=None)
)
except ClientError as e:
code = e.response["Error"]["Code"]
if code == "NoSuchKey":
responses.append(
FileDownloadResponse(
path=path, content=None, error="file_not_found"
)
)
elif code == "AccessDenied":
responses.append(
FileDownloadResponse(
path=path, content=None, error="permission_denied"
)
)
else:
responses.append(
FileDownloadResponse(
path=path, content=None, error="invalid_path"
)
)
return responses

182
uv.lock
View File

@ -29,6 +29,56 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" },
]
[[package]]
name = "agent-sandbox"
version = "0.0.26"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "httpx", extra = ["socks"] },
{ name = "pydantic" },
{ name = "volcengine-python-sdk" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/68/931c61b5e28ee344b5c2301bf56739f7fc90767da007c9e44a9b0a1ea937/agent_sandbox-0.0.26.tar.gz", hash = "sha256:67ec87e58794d017f6be321fc46913d76e84453d0cfe426a5f4b9bf8f5bd619b", size = 98635, upload-time = "2026-03-02T08:47:24.853Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/b5/2748e86137ee757b749d2fb7f5504e039f780c9346be798870ae01ee1559/agent_sandbox-0.0.26-py2.py3-none-any.whl", hash = "sha256:6421fc4eb6144f10ddfdd2668c6644e132056eaeb6ab89d76a0b2e4bcf327a0a", size = 216489, upload-time = "2026-03-02T08:47:23.351Z" },
]
[[package]]
name = "aioboto3"
version = "15.5.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "aiobotocore", extra = ["boto3"] },
{ name = "aiofiles" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/01/92e9ab00f36e2899315f49eefcd5b4685fbb19016c7f19a9edf06da80bb0/aioboto3-15.5.0.tar.gz", hash = "sha256:ea8d8787d315594842fbfcf2c4dce3bac2ad61be275bc8584b2ce9a3402a6979", size = 255069, upload-time = "2025-10-30T13:37:16.122Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/3e/e8f5b665bca646d43b916763c901e00a07e40f7746c9128bdc912a089424/aioboto3-15.5.0-py3-none-any.whl", hash = "sha256:cc880c4d6a8481dd7e05da89f41c384dbd841454fc1998ae25ca9c39201437a6", size = 35913, upload-time = "2025-10-30T13:37:14.549Z" },
]
[[package]]
name = "aiobotocore"
version = "2.25.1"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "aioitertools" },
{ name = "botocore" },
{ name = "jmespath" },
{ name = "multidict" },
{ name = "python-dateutil" },
{ name = "wrapt" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/94/2e4ec48cf1abb89971cb2612d86f979a6240520f0a659b53a43116d344dc/aiobotocore-2.25.1.tar.gz", hash = "sha256:ea9be739bfd7ece8864f072ec99bb9ed5c7e78ebb2b0b15f29781fbe02daedbc", size = 120560, upload-time = "2025-10-28T22:33:21.787Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/2a/d275ec4ce5cd0096665043995a7d76f5d0524853c76a3d04656de49f8808/aiobotocore-2.25.1-py3-none-any.whl", hash = "sha256:eb6daebe3cbef5b39a0bb2a97cffbe9c7cb46b2fcc399ad141f369f3c2134b1f", size = 86039, upload-time = "2025-10-28T22:33:19.949Z" },
]
[package.optional-dependencies]
boto3 = [
{ name = "boto3" },
]
[[package]]
name = "aiofiles"
version = "25.1.0"
@ -98,6 +148,15 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" },
]
[[package]]
name = "aioitertools"
version = "0.13.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/3c/53c4a17a05fb9ea2313ee1777ff53f5e001aefd5cc85aa2f4c2d982e1e38/aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c", size = 19322, upload-time = "2025-11-06T22:17:07.609Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be", size = 24182, upload-time = "2025-11-06T22:17:06.502Z" },
]
[[package]]
name = "aiosignal"
version = "1.4.0"
@ -334,6 +393,34 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/c1/84fc6811122f54b20de2e5afb312ee07a3a47a328755587d1e505475239b/blockbuster-1.5.26-py3-none-any.whl", hash = "sha256:f8e53fb2dd4b6c6ec2f04907ddbd063ca7cd1ef587d24448ef4e50e81e3a79bb", size = 13226, upload-time = "2025-12-05T10:43:48.778Z" },
]
[[package]]
name = "boto3"
version = "1.40.61"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/f9/6ef8feb52c3cce5ec3967a535a6114b57ac7949fd166b0f3090c2b06e4e5/boto3-1.40.61.tar.gz", hash = "sha256:d6c56277251adf6c2bdd25249feae625abe4966831676689ff23b4694dea5b12", size = 111535, upload-time = "2025-10-28T19:26:57.247Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/24/3bf865b07d15fea85b63504856e137029b6acbc73762496064219cdb265d/boto3-1.40.61-py3-none-any.whl", hash = "sha256:6b9c57b2a922b5d8c17766e29ed792586a818098efe84def27c8f582b33f898c", size = 139321, upload-time = "2025-10-28T19:26:55.007Z" },
]
[[package]]
name = "botocore"
version = "1.40.61"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/a3/81d3a47c2dbfd76f185d3b894f2ad01a75096c006a2dd91f237dca182188/botocore-1.40.61.tar.gz", hash = "sha256:a2487ad69b090f9cccd64cf07c7021cd80ee9c0655ad974f87045b02f3ef52cd", size = 14393956, upload-time = "2025-10-28T19:26:46.108Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/c5/f6ce561004db45f0b847c2cd9b19c67c6bf348a82018a48cb718be6b58b0/botocore-1.40.61-py3-none-any.whl", hash = "sha256:17ebae412692fd4824f99cde0f08d50126dc97954008e5ba2b522eb049238aa7", size = 14055973, upload-time = "2025-10-28T19:26:42.15Z" },
]
[[package]]
name = "bracex"
version = "2.6"
@ -1408,6 +1495,11 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[package.optional-dependencies]
socks = [
{ name = "socksio" },
]
[[package]]
name = "httpx-sse"
version = "0.4.3"
@ -1530,6 +1622,15 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" },
]
[[package]]
name = "jmespath"
version = "1.1.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" },
]
[[package]]
name = "joblib"
version = "1.5.3"
@ -3302,6 +3403,43 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" },
]
[package.optional-dependencies]
binary = [
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
]
pool = [
{ name = "psycopg-pool" },
]
[[package]]
name = "psycopg-binary"
version = "3.3.3"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170, upload-time = "2026-02-18T16:48:55.594Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009, upload-time = "2026-02-18T16:49:03.608Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735, upload-time = "2026-02-18T16:49:10.128Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919, upload-time = "2026-02-18T16:49:16.335Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785, upload-time = "2026-02-18T16:49:22.687Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008, upload-time = "2026-02-18T16:49:27.313Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255, upload-time = "2026-02-18T16:49:31.575Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166, upload-time = "2026-02-18T16:49:35.801Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544, upload-time = "2026-02-18T16:49:41.129Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297, upload-time = "2026-02-18T16:49:46.726Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659, upload-time = "2026-02-18T16:49:52.999Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" },
]
[[package]]
name = "psycopg-pool"
version = "3.3.0"
@ -4199,6 +4337,18 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" },
]
[[package]]
name = "s3transfer"
version = "0.14.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "botocore" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" },
]
[[package]]
name = "safetensors"
version = "0.7.0"
@ -4366,6 +4516,15 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "socksio"
version = "1.0.0"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" },
]
[[package]]
name = "soupsieve"
version = "2.8.3"
@ -5172,6 +5331,21 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" },
]
[[package]]
name = "volcengine-python-sdk"
version = "5.0.19"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "certifi" },
{ name = "python-dateutil" },
{ name = "six" },
{ name = "urllib3" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/a3/ebd474d14d7b63d9e3c14fc7afa8c8fb816d0d1e295e69f0f2c3a7799c08/volcengine_python_sdk-5.0.19.tar.gz", hash = "sha256:4154e5d1d13603d146c3b972d82433287c8b9e7f8a5f94210280e0ef2875e659", size = 8277541, upload-time = "2026-03-20T09:44:50.835Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/84/dd6713358185122ada9341ce44d57cb4c7fedf8a4374334cb6ff7d60b74a/volcengine_python_sdk-5.0.19-py2.py3-none-any.whl", hash = "sha256:95cd4e473d9f59423ee1cbcbe37e0f543448f133a3c1c540d2c6eaf83b526ed2", size = 32545589, upload-time = "2026-03-20T09:44:42.391Z" },
]
[[package]]
name = "wasabi"
version = "1.1.3"
@ -5490,6 +5664,8 @@ name = "yuxi-know"
version = "0.5.3"
source = { virtual = "." }
dependencies = [
{ name = "agent-sandbox" },
{ name = "aioboto3" },
{ name = "aiofiles" },
{ name = "aiohttp" },
{ name = "aiosqlite" },
@ -5529,6 +5705,7 @@ dependencies = [
{ name = "openai" },
{ name = "opencv-python-headless" },
{ name = "pillow" },
{ name = "psycopg", extra = ["binary", "pool"] },
{ name = "pyjwt" },
{ name = "pymilvus" },
{ name = "pymupdf" },
@ -5556,6 +5733,7 @@ dependencies = [
{ name = "typer" },
{ name = "unstructured" },
{ name = "uvicorn", extra = ["standard"] },
{ name = "wcmatch" },
]
[package.dev-dependencies]
@ -5571,6 +5749,8 @@ test = [
[package.metadata]
requires-dist = [
{ name = "agent-sandbox", specifier = ">=0.0.26" },
{ name = "aioboto3", specifier = ">=13.0.0" },
{ name = "aiofiles", specifier = ">=24.1.0" },
{ name = "aiohttp", specifier = ">=3.9.0" },
{ name = "aiosqlite", specifier = ">=0.20.0" },
@ -5610,6 +5790,7 @@ requires-dist = [
{ name = "openai", specifier = ">=1.109" },
{ name = "opencv-python-headless", specifier = ">=4.11.0.86" },
{ name = "pillow", specifier = ">=10.5.0" },
{ name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.3.3" },
{ name = "pyjwt", specifier = ">=2.8.0" },
{ name = "pymilvus", specifier = ">=2.5.8" },
{ name = "pymupdf", specifier = ">=1.25.5" },
@ -5635,6 +5816,7 @@ requires-dist = [
{ name = "typer", specifier = ">=0.16.0" },
{ name = "unstructured", specifier = ">=0.17.2" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.2" },
{ name = "wcmatch", specifier = ">=8.0.0" },
]
[package.metadata.requires-dev]

View File

@ -174,7 +174,7 @@ export const agentApi = {
/**
* 恢复被人工审批中断的对话流式响应
* @param {string} agentId - 智能体ID
* @param {Object} data - 恢复数据 { thread_id, answer: { question_id: answer }, approved }
* @param {Object} data - 恢复数据 { thread_id, answer: { question_id: answer }, approved }
* @param {Object} options - 可选参数signal, headers等
* @returns {Promise} - 恢复响应流
*/
@ -351,7 +351,9 @@ export const threadApi = {
* @returns {Promise}
*/
listThreadFiles: (threadId, path = '/mnt/user-data', recursive = false) =>
apiGet(`/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}&recursive=${recursive}`),
apiGet(
`/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}&recursive=${recursive}`
),
/**
* 读取线程文本文件内容分页

View File

@ -1201,8 +1201,8 @@ const startRunStream = async (threadId, runId, afterSeq = '0') => {
}
const approvalStatuses = ['ask_user_question_required', 'human_approval_required']
const isApprovalEvent = approvalStatuses.includes(event) ||
approvalStatuses.includes(payload?.chunk?.status)
const isApprovalEvent =
approvalStatuses.includes(event) || approvalStatuses.includes(payload?.chunk?.status)
if (isApprovalEvent) {
const approvalChunk = payload?.chunk || { status: event, thread_id: threadId }
@ -1216,9 +1216,11 @@ const startRunStream = async (threadId, runId, afterSeq = '0') => {
ts.activeRunId = null
ts.lastRetryableJobTry = null
clearActiveRunSnapshot(threadId)
fetchThreadMessages({ agentId: currentAgentId.value, threadId, delay: 200 }).finally(() => {
handleAgentStateRefresh(threadId)
})
fetchThreadMessages({ agentId: currentAgentId.value, threadId, delay: 200 }).finally(
() => {
handleAgentStateRefresh(threadId)
}
)
} else if (ts.activeRunId === runId) {
window.setTimeout(() => {
if (ts.activeRunId === runId && !ts.runStreamAbortController) {

View File

@ -1,12 +1,7 @@
<template>
<div class="attachment-panel">
<label class="attachment-upload" :class="{ disabled: disabled || isUploading }">
<input
type="file"
multiple
:disabled="disabled || isUploading"
@change="handleFileChange"
/>
<input type="file" multiple :disabled="disabled || isUploading" @change="handleFileChange" />
<Paperclip size="14" />
<span>{{ isUploading ? '上传中…' : '添加附件' }}</span>
</label>

View File

@ -2,12 +2,7 @@
<div class="file-table-container">
<div class="panel-header">
<div class="upload-btn-group">
<a-button
type="primary"
size="small"
class="upload-btn"
@click="showAddFilesModal()"
>
<a-button type="primary" size="small" class="upload-btn" @click="showAddFilesModal()">
<FileUp size="14" />
上传
</a-button>

View File

@ -20,9 +20,7 @@
<div v-if="activeQuestion" class="question-block">
<div class="approval-header">
<h4>
{{ activeQuestionIndex + 1 }}. {{ activeQuestion.question }}
</h4>
<h4>{{ activeQuestionIndex + 1 }}. {{ activeQuestion.question }}</h4>
</div>
<div v-if="activeQuestion.operation" class="approval-operation">
@ -77,7 +75,11 @@
<div class="approval-actions">
<button class="btn btn-reject" @click="handleCancel" :disabled="isProcessing">取消</button>
<button class="btn btn-approve" @click="handlePrimaryAction" :disabled="isPrimaryButtonDisabled">
<button
class="btn btn-approve"
@click="handlePrimaryAction"
:disabled="isPrimaryButtonDisabled"
>
{{ primaryButtonText }}
</button>
</div>
@ -92,7 +94,11 @@
<script setup>
import { computed, ref, watch } from 'vue'
import { isOtherOption, normalizeQuestions, DEFAULT_OTHER_OPTION_VALUE } from '@/utils/questionUtils'
import {
isOtherOption,
normalizeQuestions,
DEFAULT_OTHER_OPTION_VALUE
} from '@/utils/questionUtils'
const props = defineProps({
visible: { type: Boolean, default: false },

View File

@ -127,7 +127,9 @@ export function useAgentStreamHandler({
currentAgentId: unref(currentAgentId),
hasAgentState: !!chunk.agent_state,
todoCount: Array.isArray(chunk.agent_state?.todos) ? chunk.agent_state.todos.length : 0,
uploadCount: Array.isArray(chunk.agent_state?.uploads) ? chunk.agent_state.uploads.length : 0
uploadCount: Array.isArray(chunk.agent_state?.uploads)
? chunk.agent_state.uploads.length
: 0
})
if (chunk.agent_state) {
console.log(`${debugPrefix}[agent_state_apply]`, {

View File

@ -28,8 +28,10 @@ const parseApprovedDecision = (answer) => {
if (typeof value === 'boolean') return value
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase()
if (normalized === 'approve' || normalized === 'approved' || normalized === 'true') return true
if (normalized === 'reject' || normalized === 'rejected' || normalized === 'false') return false
if (normalized === 'approve' || normalized === 'approved' || normalized === 'true')
return true
if (normalized === 'reject' || normalized === 'rejected' || normalized === 'false')
return false
}
return null
}

View File

@ -9,8 +9,12 @@ const DEFAULT_OTHER_OPTION_VALUE = '__other__'
*/
export const isOtherOption = (option) => {
if (!option || typeof option !== 'object') return false
const label = String(option.label || '').trim().toLowerCase()
const value = String(option.value || '').trim().toLowerCase()
const label = String(option.label || '')
.trim()
.toLowerCase()
const value = String(option.value || '')
.trim()
.toLowerCase()
return (
value === DEFAULT_OTHER_OPTION_VALUE ||
@ -53,14 +57,16 @@ export const normalizeQuestions = (rawQuestions) => {
const question = String(item.question || '').trim()
if (!question) return null
const questionId = String(item.questionId || item.question_id || '').trim() || `q-${index + 1}`
const questionId =
String(item.questionId || item.question_id || '').trim() || `q-${index + 1}`
const operation = String(item.operation || '').trim()
const allowOther = Boolean(item.allowOther ?? item.allow_other ?? true)
const baseOptions = normalizeOptions(item.options || [])
const hasOtherOption = baseOptions.some((option) => isOtherOption(option))
const options = allowOther && !hasOtherOption
? [...baseOptions, { label: '其他', value: DEFAULT_OTHER_OPTION_VALUE }]
: baseOptions
const options =
allowOther && !hasOtherOption
? [...baseOptions, { label: '其他', value: DEFAULT_OTHER_OPTION_VALUE }]
: baseOptions
return {
questionId,

View File

@ -103,7 +103,11 @@
/>
<!-- 反馈模态框 -->
<FeedbackModalComponent v-if="userStore.isAdmin" ref="feedbackModal" :agent-id="selectedAgentId" />
<FeedbackModalComponent
v-if="userStore.isAdmin"
ref="feedbackModal"
:agent-id="selectedAgentId"
/>
<!-- 自定义更多菜单 -->
<Teleport to="body">
@ -186,7 +190,10 @@ const syncSelectedAgentFromRoute = async () => {
const routeAgentExists = (agents.value || []).some((agent) => agent.id === routeAgentId)
if (!routeAgentExists) {
if (selectedAgentId.value) {
await router.replace({ name: 'AgentCompWithId', params: { agent_id: selectedAgentId.value } })
await router.replace({
name: 'AgentCompWithId',
params: { agent_id: selectedAgentId.value }
})
}
return
}