chore(prep): shared groundwork for the parallel task batch

多 agent 并行批次的协调提交(消除全部共享文件冲突面):
- 迁移 0004:id_session + ob_event 两表(T3/T4 用,任务不再写迁移)
- ORM:UserSession / ObEvent;错误码:九个新码(40xxx/60006,含 meta)
- 配置:四档 tiers / refresh TTL / 任务奖励注册表 / 埋点上限 / COS 直传全套
- 骨架:packages/analytics + packages/storage + events/uploads 空路由(已注册)
- deps:AnalyticsServiceDep / StorageServiceDep;DailyBudgetGuard 接收 tiers
- uv add cos-python-sdk-v5;import-linter/CI/Makefile 纳入两个新包
This commit is contained in:
xsl
2026-08-14 16:02:54 +08:00
parent 2009aa938c
commit 9e7a886863
20 changed files with 380 additions and 6 deletions
+31
View File
@@ -81,3 +81,34 @@ OTEL_ENABLED=false
OTEL_SERVICE_NAME=meida-api
OTEL_EXPORTER_OTLP_ENDPOINT=
PROMETHEUS_ENABLED=true
# ── 四档分级降级(T-P1-25b,架构 §13.3)──────────────────────────────────
# JSON 为列表;blocked 支持 "*";allow 仅 block_all 档有意义(纯文本对话放行)。
# 留空则使用内置默认四档(与下面示例一致)。
LLM_BUDGET_TIERS_JSON=[{"threshold":0.6,"name":"warn","blocked":[]},{"threshold":0.8,"name":"degrade_secondary","blocked":["hd_export","report_regen"]},{"threshold":0.9,"name":"degrade_generation","blocked":["hairstyle","tryon"]},{"threshold":1.0,"name":"block_all","blocked":"*","allow":["agent_chat"]}]
# ── 会话(FR-ACC-05)───────────────────────────────────────────────────────
REFRESH_TOKEN_TTL_SECONDS=2592000
# ── 任务奖励(FR-BILL-03/04)───────────────────────────────────────────────
# amount 均为占位值 1:正式数值待产品在产品方案 §3.2 定稿后改此处,不改代码。
# claim_period 决定幂等粒度(同一任务同周期只能领一次)。
TASK_BONUS_RULES_JSON={"sign_in":{"quota_key":"agent_chat","amount":1,"claim_period":"day"},"profile_complete":{"quota_key":"agent_chat","amount":1,"claim_period":"lifetime"},"report_feedback":{"quota_key":"report_regen","amount":1,"claim_period":"day"},"share_report":{"quota_key":"tryon","amount":1,"claim_period":"day"},"invite_friend":{"quota_key":"tryon","amount":1,"claim_period":"day"}}
# ── 客户端埋点(FR-SYS-06)─────────────────────────────────────────────────
EVENTS_MAX_BATCH=50
EVENTS_PROPS_MAX_BYTES=8192
# ── 腾讯云 COS 直传(FR-SYS-03 / T-P1-27)──────────────────────────────────
# 密钥只在此文件对应的真实 .env 里出现;四桶按《前置开通清单》创建后填名称。
COS_REGION=ap-shanghai
COS_SECRET_ID=
COS_SECRET_KEY=
COS_BUCKET_TEMP=meida-temp
COS_BUCKET_USER=meida-user
COS_BUCKET_KB=meida-kb
COS_BUCKET_REPORT=meida-report
COS_PRESIGN_EXPIRE_SECONDS=600
UPLOAD_MAX_BYTES=20971520
UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/webp
UPLOAD_SCENES_JSON={"report_photo":{"bucket":"temp"},"avatar":{"bucket":"temp"}}
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
- name: Mypy (strict scope)
run: uv run mypy packages/domain packages/llm
- name: Mypy (remaining scope)
run: uv run mypy apps packages/shared packages/identity packages/billing packages/task packages/agent packages/compliance
run: uv run mypy apps packages/shared packages/identity packages/billing packages/task packages/agent packages/compliance packages/analytics packages/storage
# --- 防线 3: 分层依赖检查 (架构文档 §4.2) ---
- name: Import contracts
Executable → Regular
+1 -1
View File
@@ -56,7 +56,7 @@ fmt: ## 自动修复与格式化
typecheck: ## mypy(domain 与 llm 为 strict)
uv run mypy packages/domain packages/llm
uv run mypy apps packages/shared packages/identity packages/billing packages/task \
packages/agent packages/compliance
packages/agent packages/compliance packages/analytics packages/storage
imports: ## 分层依赖契约(架构文档 §4.2)
uv run lint-imports
Executable → Regular
+13
View File
@@ -16,6 +16,7 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
from packages.agent.service import AgentService
from packages.analytics.service import AnalyticsService
from packages.billing.service import BillingService
from packages.identity.service import IdentityService
from packages.llm.budget import DailyBudgetGuard, RedisCostCounter
@@ -39,6 +40,7 @@ from packages.shared.db import get_session_factory
from packages.shared.errors import AppError, ErrorCode
from packages.shared.redis_client import get_redis
from packages.shared.security import decode_access_token
from packages.storage.service import StorageService
from packages.task.service import TaskService
bearer_scheme = HTTPBearer(auto_error=False)
@@ -99,6 +101,7 @@ def build_llm_gateway(settings: Settings) -> LLMGateway:
daily_budget_cents=Decimal(settings.llm_daily_budget_cents),
enabled=settings.llm_daily_budget_enabled,
tz_offset_hours=settings.llm_budget_timezone_offset_hours,
tiers=list(settings.llm_budget_tiers),
)
return LLMGateway(
@@ -180,6 +183,14 @@ def task_service_dep(session: SessionDep) -> TaskService:
return TaskService(session)
def analytics_service_dep(session: SessionDep) -> AnalyticsService:
return AnalyticsService(session)
def storage_service_dep(settings: SettingsDep) -> StorageService:
return StorageService(settings)
def agent_service_dep(
session: SessionDep,
gateway: GatewayDep,
@@ -191,4 +202,6 @@ def agent_service_dep(
IdentityServiceDep = Annotated[IdentityService, Depends(identity_service_dep)]
BillingServiceDep = Annotated[BillingService, Depends(billing_service_dep)]
TaskServiceDep = Annotated[TaskService, Depends(task_service_dep)]
AnalyticsServiceDep = Annotated[AnalyticsService, Depends(analytics_service_dep)]
StorageServiceDep = Annotated[StorageService, Depends(storage_service_dep)]
AgentServiceDep = Annotated[AgentService, Depends(agent_service_dep)]
Executable → Regular
+3 -2
View File
@@ -12,7 +12,7 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI
from apps.api.deps import build_llm_gateway
from apps.api.routers import agent, auth, health, quota, tasks
from apps.api.routers import agent, auth, events, health, quota, tasks, uploads
from packages.shared.config import Settings, get_settings
from packages.shared.db import dispose_engine
from packages.shared.handlers import install_exception_handlers
@@ -69,7 +69,8 @@ def create_app(settings: Settings | None = None) -> FastAPI:
setup_tracing(app, cfg)
app.include_router(health.router)
for router in (auth.router, agent.router, quota.router, tasks.router):
routers = (auth.router, agent.router, quota.router, tasks.router, events.router, uploads.router)
for router in routers:
app.include_router(router, prefix=cfg.api_prefix)
return app
+10
View File
@@ -0,0 +1,10 @@
"""``POST /events/batch`` — 客户端埋点批量上报(骨架,T3 填充实现)。
FR-SYS-06 服务端入口;实现契约见 docs/开发任务包/T3-埋点上报.md。
"""
from __future__ import annotations
from fastapi import APIRouter
router = APIRouter(prefix="/events", tags=["events"])
+10
View File
@@ -0,0 +1,10 @@
"""``POST /uploads/presign`` — COS 直传预签名(骨架,T5 填充实现)。
FR-SYS-03:图片不经应用服务器;实现契约见 docs/开发任务包/T5-上传预签名.md。
"""
from __future__ import annotations
from fastapi import APIRouter
router = APIRouter(prefix="/uploads", tags=["uploads"])
@@ -0,0 +1,63 @@
"""Sessions and client events for the Sprint-0 parallel batch.
Two tables that the parallel task batch (T3/T4) needs, landed together in one
revision so no task ever writes a migration:
1. ``id_session`` — server-revocable refresh sessions (架构文档 §6.2, FR-ACC-05).
2. ``ob_event`` — client telemetry (架构文档 §6.2, FR-SYS-06).
Partitioning (架构文档 says "按月分区") is deliberately deferred for both —
same call as ``ob_ai_call``: plain tables until volume justifies it.
Reversible: 是。降级直接 drop 两表。
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
revision: str = "0004"
down_revision: str | None = "0003"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"id_session",
sa.Column("session_id", sa.BigInteger(), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("refresh_token_hash", sa.String(length=64), nullable=False),
sa.Column("expire_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("revoked", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.PrimaryKeyConstraint("session_id"),
sa.UniqueConstraint("refresh_token_hash", name="uq_id_session_token_hash"),
)
op.create_index("ix_id_session_user", "id_session", ["user_id"])
op.create_table(
"ob_event",
sa.Column("event_id", sa.BigInteger(), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("event_name", sa.String(length=64), nullable=False),
sa.Column("props", postgresql.JSONB(), nullable=False),
sa.Column("client_ts", sa.DateTime(timezone=True), nullable=True),
sa.Column("server_ts", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.PrimaryKeyConstraint("event_id"),
)
op.create_index("ix_ob_event_name_ts", "ob_event", ["event_name", "server_ts"])
op.create_index("ix_ob_event_user_ts", "ob_event", ["user_id", "server_ts"])
def downgrade() -> None:
op.drop_index("ix_ob_event_user_ts", table_name="ob_event")
op.drop_index("ix_ob_event_name_ts", table_name="ob_event")
op.drop_table("ob_event")
op.drop_index("ix_id_session_user", table_name="id_session")
op.drop_table("id_session")
+7
View File
@@ -0,0 +1,7 @@
"""analytics 域 — 客户端埋点上报(FR-SYS-06)。
分层位置:共享能力层(与 identity/billing/task 同层),
只准 import packages.shared / packages.domain。
实现任务:docs/开发任务包/T3-埋点上报.md(worktree task/t3-events)。
"""
+1
View File
@@ -0,0 +1 @@
"""analytics 域(埋点上报)。见 README.md。"""
+10
View File
@@ -0,0 +1,10 @@
"""analytics 域 service — 骨架(T3 任务填充实现)。"""
from __future__ import annotations
from sqlalchemy.ext.asyncio import AsyncSession
class AnalyticsService:
def __init__(self, session: AsyncSession) -> None:
self._session = session
Executable → Regular
+4
View File
@@ -76,6 +76,7 @@ class DailyBudgetGuard:
key_prefix: str = "llm:cost:daily",
tz_offset_hours: int = 8,
clock: object | None = None,
tiers: list[dict[str, object]] | None = None,
) -> None:
self._counter = counter
self._budget = daily_budget_cents
@@ -83,6 +84,9 @@ class DailyBudgetGuard:
self._key_prefix = key_prefix
self._tz = timezone(timedelta(hours=tz_offset_hours))
self._clock = clock
# 四档分级降级的配置(架构 §13.3 / T-P1-25b)。T1 任务实现判定逻辑;
# 这里先接收配置,使 deps 装配层不必随后再改。None = 使用内置默认四档。
self._tiers_config = tiers
@property
def enabled(self) -> bool:
Executable → Regular
+64
View File
@@ -106,6 +106,67 @@ class Settings(BaseSettings):
llm_daily_budget_enabled: bool = True
llm_daily_budget_cents: Decimal = Decimal("100000")
llm_budget_timezone_offset_hours: int = 8
# 四档分级降级(架构 §13.3,T-P1-25b)。JSON 为列表:
# [{"threshold":0.8,"name":"degrade_secondary","blocked":["hd_export"],...}]
# blocked 支持 "*"; allow 仅 block_all 档有意义(纯文本对话放行)。
llm_budget_tiers: list[dict[str, Any]] = Field(
default_factory=lambda: [
{"threshold": 0.6, "name": "warn", "blocked": []},
{
"threshold": 0.8,
"name": "degrade_secondary",
"blocked": ["hd_export", "report_regen"],
},
{"threshold": 0.9, "name": "degrade_generation", "blocked": ["hairstyle", "tryon"]},
{"threshold": 1.0, "name": "block_all", "blocked": "*", "allow": ["agent_chat"]},
],
alias="LLM_BUDGET_TIERS_JSON",
)
# --- Auth: refresh sessions (FR-ACC-05) --------------------------------
refresh_token_ttl_seconds: int = 2_592_000 # 30 天
# --- 任务奖励(FR-BILL-03/04,配置化)-----------------------------------
# amount 全部是占位值 1:正式数值待产品在产品方案 §3.2 定稿后改 env,不改代码。
# claim_period 决定幂等粒度(同一任务同周期只能领一次)。
task_bonus_rules: dict[str, dict[str, Any]] = Field(
default_factory=lambda: {
"sign_in": {"quota_key": "agent_chat", "amount": 1, "claim_period": "day"},
"profile_complete": {
"quota_key": "agent_chat",
"amount": 1,
"claim_period": "lifetime",
},
"report_feedback": {"quota_key": "report_regen", "amount": 1, "claim_period": "day"},
"share_report": {"quota_key": "tryon", "amount": 1, "claim_period": "day"},
"invite_friend": {"quota_key": "tryon", "amount": 1, "claim_period": "day"},
},
alias="TASK_BONUS_RULES_JSON",
)
# --- 客户端埋点(FR-SYS-06)---------------------------------------------
events_max_batch: int = 50
events_props_max_bytes: int = 8192
# --- 腾讯云 COS 直传(FR-SYS-03 / T-P1-27)------------------------------
cos_region: str = "ap-shanghai"
cos_secret_id: str = ""
cos_secret_key: str = ""
cos_bucket_temp: str = "meida-temp" # 临时桶,2 小时生命周期(CR-02)
cos_bucket_user: str = "meida-user"
cos_bucket_kb: str = "meida-kb"
cos_bucket_report: str = "meida-report"
cos_presign_expire_seconds: int = 600
upload_max_bytes: int = 20_971_520 # 20MB
upload_allowed_types: str = "image/jpeg,image/png,image/webp"
# scene → {"bucket": "temp"|"user"|"kb"|"report"}。新增上传场景只改这里。
upload_scenes: dict[str, dict[str, Any]] = Field(
default_factory=lambda: {
"report_photo": {"bucket": "temp"},
"avatar": {"bucket": "temp"},
},
alias="UPLOAD_SCENES_JSON",
)
# --- Observability -----------------------------------------------------
otel_enabled: bool = False
@@ -118,6 +179,9 @@ class Settings(BaseSettings):
"llm_cost_rates",
"llm_cost_rates_peak",
"llm_default_cost_rate",
"llm_budget_tiers",
"task_bonus_rules",
"upload_scenes",
mode="before",
)
@classmethod
Executable → Regular
+27 -1
View File
@@ -24,15 +24,23 @@ class ErrorCode(IntEnum):
BAD_REQUEST = 40000
VALIDATION_FAILED = 40001
IDEMPOTENCY_CONFLICT = 40002
UPLOAD_REJECTED = 40010
EVENTS_BATCH_REJECTED = 40020
UNAUTHENTICATED = 40100
TOKEN_EXPIRED = 40101
TOKEN_INVALID = 40102
REFRESH_TOKEN_INVALID = 40103
REFRESH_TOKEN_EXPIRED = 40104
SESSION_REVOKED = 40105
FORBIDDEN = 40300
QUOTA_EXHAUSTED = 40301
CONSENT_REQUIRED = 40302
MINOR_RESTRICTED = 40303
TASK_BONUS_CAP_REACHED = 40310
NOT_FOUND = 40400
TASK_BONUS_UNKNOWN = 40410
STATE_CONFLICT = 40900
TASK_BONUS_ALREADY_CLAIMED = 40910
RATE_LIMITED = 42900
# --- 5xxxx server -----------------------------------------------------
@@ -49,6 +57,7 @@ class ErrorCode(IntEnum):
LLM_DAILY_BUDGET_EXCEEDED = 60003
LLM_PROMPT_CACHE_VIOLATION = 60004
MODERATION_UNAVAILABLE = 60005
LLM_SCENE_DEGRADED = 60006
@dataclass(frozen=True, slots=True)
@@ -63,17 +72,31 @@ ERROR_META: dict[ErrorCode, ErrorMeta] = {
ErrorCode.BAD_REQUEST: ErrorMeta(400, "请求参数有误", "请检查后重试"),
ErrorCode.VALIDATION_FAILED: ErrorMeta(422, "请求参数校验未通过", "请按提示修改后重试"),
ErrorCode.IDEMPOTENCY_CONFLICT: ErrorMeta(
409, "相同请求正在处理中", "请稍候查看任务中心,不要重复提交"
409, "相同请求正在处理中", "请稍后查看任务中心,不要重复提交"
),
ErrorCode.UPLOAD_REJECTED: ErrorMeta(
400, "不支持的图片类型或超出大小限制", "请上传 20MB 以内的 JPG/PNG/WebP 图片"
),
ErrorCode.EVENTS_BATCH_REJECTED: ErrorMeta(400, "埋点数据格式有误"),
ErrorCode.UNAUTHENTICATED: ErrorMeta(401, "请先登录", "登录后即可继续"),
ErrorCode.TOKEN_EXPIRED: ErrorMeta(401, "登录状态已过期", "请重新登录"),
ErrorCode.TOKEN_INVALID: ErrorMeta(401, "登录状态无效", "请重新登录"),
ErrorCode.REFRESH_TOKEN_INVALID: ErrorMeta(401, "登录状态无效", "请重新登录"),
ErrorCode.REFRESH_TOKEN_EXPIRED: ErrorMeta(401, "登录状态已过期", "请重新登录"),
ErrorCode.SESSION_REVOKED: ErrorMeta(401, "该会话已登出", "请重新登录"),
ErrorCode.FORBIDDEN: ErrorMeta(403, "没有访问权限"),
ErrorCode.QUOTA_EXHAUSTED: ErrorMeta(403, "今日额度已用完", "明天恢复,或完成任务多换几次"),
ErrorCode.CONSENT_REQUIRED: ErrorMeta(403, "需要先完成授权", "请阅读并同意后继续"),
ErrorCode.MINOR_RESTRICTED: ErrorMeta(403, "该功能未向未成年人开放"),
ErrorCode.TASK_BONUS_CAP_REACHED: ErrorMeta(
403, "任务奖励已达上限", "明天再来,或等额度自然恢复"
),
ErrorCode.NOT_FOUND: ErrorMeta(404, "内容不存在或已删除"),
ErrorCode.TASK_BONUS_UNKNOWN: ErrorMeta(404, "任务不存在或暂未开放"),
ErrorCode.STATE_CONFLICT: ErrorMeta(409, "当前状态无法执行该操作"),
ErrorCode.TASK_BONUS_ALREADY_CLAIMED: ErrorMeta(
409, "这个周期的任务奖励已领取", "下个周期再来"
),
ErrorCode.RATE_LIMITED: ErrorMeta(429, "操作太频繁了", "请稍后再试"),
ErrorCode.INTERNAL_ERROR: ErrorMeta(500, "服务开小差了", "请稍后重试"),
ErrorCode.DB_ERROR: ErrorMeta(500, "服务开小差了", "请稍后重试"),
@@ -94,6 +117,9 @@ ERROR_META: dict[ErrorCode, ErrorMeta] = {
ErrorCode.MODERATION_UNAVAILABLE: ErrorMeta(
503, "内容安全服务不可用", "为保证安全本次请求已中止,请稍后重试"
),
ErrorCode.LLM_SCENE_DEGRADED: ErrorMeta(
503, "该功能今日暂时关闭", "AI 服务今日已达服务上限,明天恢复"
),
}
Executable → Regular
+50
View File
@@ -254,3 +254,53 @@ class AiCall(Base):
Index("ix_ob_ai_call_scene_created", "scene", "created_at"),
Index("ix_ob_ai_call_user_created", "user_id", "created_at"),
)
class UserSession(Base):
"""refresh token 的服务端会话(架构文档 §6.2 id_session,FR-ACC-05).
刻意叫 UserSession 而不是 Session:避免与 SQLAlchemy 的 AsyncSession 混淆。
refresh token 本体是 opaque 随机串(不出现在任何存储里),库里只存 sha256
哈希 —— 泄漏数据库不等于泄漏令牌。轮换 = 旧行 revoked + 新行,同一事务。
"""
__tablename__ = "id_session"
session_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
refresh_token_hash: Mapped[str] = mapped_column(String(64), nullable=False)
expire_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
revoked: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
__table_args__ = (
UniqueConstraint("refresh_token_hash", name="uq_id_session_token_hash"),
Index("ix_id_session_user", "user_id"),
)
class ObEvent(Base):
"""客户端埋点事件(架构文档 §6.2 ob_event,FR-SYS-06).
架构原文「按月分区」—— V1 先普通表(与 ob_ai_call 同样处理),
数据量上来后再分区,先不预支复杂度。
"""
__tablename__ = "ob_event"
event_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
event_name: Mapped[str] = mapped_column(String(64), nullable=False)
props: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
client_ts: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
server_ts: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
__table_args__ = (
Index("ix_ob_event_name_ts", "event_name", "server_ts"),
Index("ix_ob_event_user_ts", "user_id", "server_ts"),
)
+8
View File
@@ -0,0 +1,8 @@
"""storage 域 — COS 直传预签名(FR-SYS-03 / T-P1-27)。
分层位置:共享能力层(与 identity/billing/task 同层),
只准 import packages.shared / packages.domain 与 qcloud_cos(COS SDK)。
SDK 必须隔离在 CosSigner Protocol 后面,业务与测试都不直连。
实现任务:docs/开发任务包/T5-上传预签名.md(worktree task/t5-upload-presign)。
"""
+1
View File
@@ -0,0 +1 @@
"""storage 域(COS 直传)。见 README.md。"""
+11
View File
@@ -0,0 +1,11 @@
"""storage 域 service — 骨架(T5 任务填充实现)。"""
from __future__ import annotations
from typing import Any
class StorageService:
def __init__(self, settings: Any, signer: Any | None = None) -> None:
self._settings = settings
self._signer = signer
+2 -1
View File
@@ -20,6 +20,7 @@ dependencies = [
"structlog>=24.4",
"orjson>=3.10",
"prometheus-client>=0.21",
"cos-python-sdk-v5>=1.9.44",
]
[project.optional-dependencies]
@@ -110,7 +111,7 @@ type = "layers"
layers = [
"apps",
"packages.agent | packages.report | packages.tryon | packages.wardrobe",
"packages.identity | packages.billing | packages.task | packages.compliance | packages.notify",
"packages.identity | packages.billing | packages.task | packages.compliance | packages.notify | packages.analytics | packages.storage",
"packages.kb | packages.retrieval | packages.rules | packages.memory | packages.profile",
"packages.llm",
"packages.shared",
Generated
+63
View File
@@ -488,6 +488,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "cos-python-sdk-v5"
version = "1.9.44"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "crcmod" },
{ name = "pycryptodome" },
{ name = "requests" },
{ name = "six" },
{ name = "xmltodict" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5e/c8/5cc0d0bd4b4bd9006976408bf390927fe51138658235c512a76799932de0/cos_python_sdk_v5-1.9.44.tar.gz", hash = "sha256:2ab403f3b64efdbb9a1984ad3e4381bd7f0755d1064148aeb25269bd757b49ab", size = 103461, upload-time = "2026-05-29T03:50:19.02Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/ed/4eabca16f3f9049ac0ee5bd17ef42709a6ede659e81e97f74e77a1aae30b/cos_python_sdk_v5-1.9.44-py3-none-any.whl", hash = "sha256:584a97ecd5e9b15b04a1e511e6dd0ea4eca00ee87fe50aa9a3c91ce716149bcb", size = 99127, upload-time = "2026-05-29T03:50:17.35Z" },
]
[[package]]
name = "coverage"
version = "7.15.4"
@@ -587,6 +603,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" },
]
[[package]]
name = "crcmod"
version = "1.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e", size = 89670, upload-time = "2010-06-27T14:35:29.538Z" }
[[package]]
name = "cryptography"
version = "50.0.0"
@@ -1150,6 +1172,7 @@ dependencies = [
{ name = "alembic" },
{ name = "asyncpg" },
{ name = "celery" },
{ name = "cos-python-sdk-v5" },
{ name = "fastapi" },
{ name = "httpx" },
{ name = "orjson" },
@@ -1183,6 +1206,7 @@ requires-dist = [
{ name = "alembic", specifier = ">=1.14" },
{ name = "asyncpg", specifier = ">=0.30" },
{ name = "celery", specifier = ">=5.4" },
{ name = "cos-python-sdk-v5", specifier = ">=1.9.44" },
{ name = "detect-secrets", marker = "extra == 'dev'", specifier = ">=1.5" },
{ name = "fastapi", specifier = ">=0.115" },
{ name = "httpx", specifier = ">=0.28" },
@@ -1415,6 +1439,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
[[package]]
name = "pycryptodome"
version = "3.23.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
]
[[package]]
name = "pydantic"
version = "2.13.4"
@@ -2131,3 +2185,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/24/a585e7573e128070605d003b5544729bcd58d9756c7e99d550818ca4b916/websockets-17.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b", size = 213009, upload-time = "2026-07-31T11:31:16.776Z" },
{ url = "https://files.pythonhosted.org/packages/09/ce/3929538b2b9918f5eee623fbf3346893973191f6df93f19bbda097bd7bb7/websockets-17.0.1-py3-none-any.whl", hash = "sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345", size = 206718, upload-time = "2026-07-31T11:31:26.037Z" },
]
[[package]]
name = "xmltodict"
version = "1.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" },
]