feat 1.增加 建议词 机制 2.对话生图实现
This commit is contained in:
@@ -27,6 +27,8 @@ agents:
|
|||||||
步骤:
|
步骤:
|
||||||
1. 根据上下文,编写一个详细的 Stable Diffusion 风格的英文 Prompt。
|
1. 根据上下文,编写一个详细的 Stable Diffusion 风格的英文 Prompt。
|
||||||
2. 必须调用 generate_furniture_sketch 工具来生成图片。
|
2. 必须调用 generate_furniture_sketch 工具来生成图片。
|
||||||
|
|
||||||
|
注意:如果对话中出现 [SYSTEM_DIRECTIVE] 要求直接绘图,请立即根据已知信息编写 Prompt 并调用 generate_furniture_sketch 工具,不要进行多余的询问。
|
||||||
|
|
||||||
researcher:
|
researcher:
|
||||||
prompt_template: |
|
prompt_template: |
|
||||||
|
|||||||
@@ -6,14 +6,18 @@ requires-python = ">=3.12"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi[standard]>=0.128.0",
|
"fastapi[standard]>=0.128.0",
|
||||||
"gunicorn>=25.0.1",
|
"gunicorn>=25.0.1",
|
||||||
|
"image>=1.5.33",
|
||||||
"langchain-core>=1.2.8",
|
"langchain-core>=1.2.8",
|
||||||
"langchain-google-genai>=4.2.0",
|
"langchain-google-genai>=4.2.0",
|
||||||
"langgraph>=1.0.7",
|
"langgraph>=1.0.7",
|
||||||
"langgraph-checkpoint-mongodb>=0.3.1",
|
"langgraph-checkpoint-mongodb>=0.3.1",
|
||||||
|
"minio>=7.2.20",
|
||||||
|
"modality>=0.1.0",
|
||||||
"motor>=3.7.1",
|
"motor>=3.7.1",
|
||||||
"pydantic>=2.12.5",
|
"pydantic>=2.12.5",
|
||||||
"pydantic-settings>=2.12.0",
|
"pydantic-settings>=2.12.0",
|
||||||
"pymongo[srv]>=4.15.5",
|
"pymongo[srv]>=4.15.5",
|
||||||
"python-dotenv>=1.2.1",
|
"python-dotenv>=1.2.1",
|
||||||
|
"uuid>=1.30",
|
||||||
"uvicorn>=0.40.0",
|
"uvicorn>=0.40.0",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,13 +7,21 @@ class Settings(BaseSettings):
|
|||||||
应用配置类。Pydantic Settings 会自动从环境变量和 .env 文件中加载这些值。
|
应用配置类。Pydantic Settings 会自动从环境变量和 .env 文件中加载这些值。
|
||||||
"""
|
"""
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_file='.env',
|
env_file='.env_local',
|
||||||
env_file_encoding='utf-8',
|
env_file_encoding='utf-8',
|
||||||
extra='ignore' # 忽略环境变量中多余的键
|
extra='ignore' # 忽略环境变量中多余的键
|
||||||
)
|
)
|
||||||
# --- google api 配置信息 ---
|
# --- google api 配置信息 ---
|
||||||
GOOGLE_GENAI_USE_VERTEXAI: str = Field(default="", description="")
|
GOOGLE_GENAI_USE_VERTEXAI: str = Field(default="", description="")
|
||||||
GOOGLE_API_KEY: str = Field(default="", description="")
|
GOOGLE_API_KEY: str = Field(default="", description="")
|
||||||
|
GOOGLE_CLOUD_PROJECT: str = Field(default="", description="")
|
||||||
|
GOOGLE_CLOUD_LOCATION: str = Field(default="", description="")
|
||||||
|
|
||||||
|
# --- minio 配置信息 ---
|
||||||
|
MINIO_URL: str = Field(default='', description="")
|
||||||
|
MINIO_ACCESS: str = Field(default='', description="")
|
||||||
|
MINIO_SECRET: str = Field(default='', description="")
|
||||||
|
MINIO_SECURE: bool = Field(default=True, description="")
|
||||||
|
|
||||||
# --- mongodb配置信息 ---
|
# --- mongodb配置信息 ---
|
||||||
MONGODB_USERNAME: str = Field(default="", description="")
|
MONGODB_USERNAME: str = Field(default="", description="")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter
|
|||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from src.schemas.chat import ChatRequest, HistoryResponse, HistoryItem
|
from src.schemas.chat import ChatRequest, HistoryResponse, HistoryItem
|
||||||
from src.server.agent.graph import app # 导入已经 compile 好的 graph
|
from src.server.agent.graph import app # 导入已经 compile 好的 graph
|
||||||
from langchain_core.messages import HumanMessage
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
|
|
||||||
router = APIRouter(prefix="/chat", tags=["Furniture Design Chat"])
|
router = APIRouter(prefix="/chat", tags=["Furniture Design Chat"])
|
||||||
|
|
||||||
@@ -57,38 +57,114 @@ async def chat_stream(request: ChatRequest):
|
|||||||
# 如果是回溯操作,我们生成一个新的 ID,或者由前端传入一个新的 target_thread_id
|
# 如果是回溯操作,我们生成一个新的 ID,或者由前端传入一个新的 target_thread_id
|
||||||
is_branching = source_thread_id and checkpoint_id
|
is_branching = source_thread_id and checkpoint_id
|
||||||
target_thread_id = str(uuid.uuid4())[:8] if is_branching else (source_thread_id or str(uuid.uuid4())[:8])
|
target_thread_id = str(uuid.uuid4())[:8] if is_branching else (source_thread_id or str(uuid.uuid4())[:8])
|
||||||
|
# 2. 获取配置参数
|
||||||
|
temp = request.config_params.temperature if request.config_params else 0.7
|
||||||
|
|
||||||
# 2. 如果是分叉请求,我们需要先“搬家”状态
|
# 构建基础 Config
|
||||||
|
current_config = {
|
||||||
|
"configurable": {
|
||||||
|
"thread_id": target_thread_id,
|
||||||
|
"llm_temperature": temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# 3. 处理状态初始化与分支
|
||||||
|
initial_messages = []
|
||||||
|
|
||||||
|
# 如果是全新的对话(没有 source_thread_id),或者明确要求分叉
|
||||||
|
if not source_thread_id or is_branching:
|
||||||
|
# 如果用户传了标签,构造 SystemMessage 注入上下文
|
||||||
|
if request.config_params:
|
||||||
|
cp = request.config_params
|
||||||
|
system_prompt = (
|
||||||
|
f"Current furniture design background settings:\n"
|
||||||
|
f"- type: {cp.type}\n"
|
||||||
|
f"- space/region: {cp.region}\n"
|
||||||
|
f"- style tendency: {cp.style}\n"
|
||||||
|
f"Please strictly follow the above settings in subsequent conversations。"
|
||||||
|
)
|
||||||
|
initial_messages.append(SystemMessage(content=system_prompt))
|
||||||
|
|
||||||
|
# 4. 执行分叉逻辑(搬运旧数据)
|
||||||
if is_branching:
|
if is_branching:
|
||||||
# 获取旧状态
|
source_config = {
|
||||||
source_config = {"configurable": {"thread_id": source_thread_id, "checkpoint_id": checkpoint_id}}
|
"configurable": {
|
||||||
|
"thread_id": source_thread_id,
|
||||||
|
"checkpoint_id": checkpoint_id
|
||||||
|
}
|
||||||
|
}
|
||||||
older_state = await app.aget_state(source_config)
|
older_state = await app.aget_state(source_config)
|
||||||
|
|
||||||
# 将旧状态的消息,作为新 thread 的初始值注入
|
# 将旧消息和我们新定义的 SystemMessage 合并
|
||||||
# 注意:这里我们手动把旧消息塞给新 thread
|
# update_state 会将这些消息推送到新 thread 的存储中
|
||||||
new_config = {"configurable": {"thread_id": target_thread_id}}
|
combined_values = older_state.values.copy()
|
||||||
await app.aupdate_state(new_config, older_state.values)
|
if initial_messages:
|
||||||
|
combined_values["messages"] = list(combined_values["messages"]) + initial_messages
|
||||||
|
|
||||||
# 现在的 config 指向新 Thread
|
await app.aupdate_state(current_config, combined_values)
|
||||||
current_config = new_config
|
|
||||||
else:
|
|
||||||
current_config = {"configurable": {"thread_id": target_thread_id}}
|
|
||||||
|
|
||||||
async def event_generator():
|
async def event_generator():
|
||||||
# 告诉前端:现在是在哪个 Thread 上工作(如果是分叉,前端需要更新本地存储的 ID)
|
# 初始推送状态信息
|
||||||
yield f"data: {json.dumps({'thread_id': target_thread_id, 'is_branch': is_branching}, ensure_ascii=False)}\n\n"
|
yield f"data: {json.dumps({'thread_id': target_thread_id, 'is_branch': is_branching, 'status': 'start'}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
# 构造本次请求的输入
|
||||||
|
# 如果是第一次开始,且有 initial_messages,则连同 user message 一起发送
|
||||||
|
# --- 核心逻辑:构造本次请求的消息列表 ---
|
||||||
|
new_messages = []
|
||||||
|
if not source_thread_id and initial_messages:
|
||||||
|
new_messages.extend(initial_messages)
|
||||||
|
# 添加用户消息
|
||||||
|
new_messages.append(HumanMessage(content=request.message))
|
||||||
|
|
||||||
|
# --- 新增:强制绘图指令注入 ---
|
||||||
|
# if request.force_sketch:
|
||||||
|
# force_instruction = HumanMessage(
|
||||||
|
# content="[SYSTEM_DIRECTIVE]: 用户点击了强制生成按钮。请立即根据当前上下文调用 generate_furniture_sketch 工具生成草图,无需确认。"
|
||||||
|
# )
|
||||||
|
# new_messages.append(force_instruction)
|
||||||
|
|
||||||
|
input_data = {"messages": new_messages}
|
||||||
|
|
||||||
async for event in app.astream(
|
async for event in app.astream(
|
||||||
{"messages": [HumanMessage(content=request.message)]},
|
input_data,
|
||||||
current_config,
|
current_config,
|
||||||
stream_mode="updates"
|
stream_mode="updates"
|
||||||
):
|
):
|
||||||
# ... 发送流式内容的逻辑保持不变 ...
|
|
||||||
for node_name, output in event.items():
|
for node_name, output in event.items():
|
||||||
if "messages" in output:
|
if "messages" in output:
|
||||||
msg = output["messages"][-1]
|
# 获取最新 state 以获取 checkpoint_id
|
||||||
state = await app.aget_state(current_config)
|
state = await app.aget_state(current_config)
|
||||||
yield f"data: {json.dumps({'node': node_name, 'content': msg.content, 'checkpoint_id': state.config['configurable']['checkpoint_id']}, ensure_ascii=False)}\n\n"
|
current_cp_id = state.config["configurable"].get("checkpoint_id")
|
||||||
|
|
||||||
|
# 遍历本次 update 产生的所有消息
|
||||||
|
for msg in output["messages"]:
|
||||||
|
payload = {
|
||||||
|
"node": node_name,
|
||||||
|
"content": "",
|
||||||
|
"image_url": None,
|
||||||
|
"checkpoint_id": current_cp_id,
|
||||||
|
"suggestions": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 核心改动:提取建议按钮 ---
|
||||||
|
# 无论是不是 Suggester 节点,只要消息里带了建议就提取
|
||||||
|
if hasattr(msg, "additional_kwargs") and "suggestions" in msg.additional_kwargs:
|
||||||
|
payload["suggestions"] = msg.additional_kwargs["suggestions"]
|
||||||
|
|
||||||
|
content = msg.content
|
||||||
|
# 逻辑判断:MinIO 图片处理
|
||||||
|
if node_name == "Visualizer" and str(content).endswith("png") and "furniture/sketches" in str(content):
|
||||||
|
payload["image_url"] = content
|
||||||
|
payload["content"] = "已为您生成设计草图"
|
||||||
|
else:
|
||||||
|
payload["content"] = content
|
||||||
|
|
||||||
|
# 如果消息既没有文本、也没有图片、也没有建议(比如中间的 ToolCall 消息),则跳过
|
||||||
|
if not payload["content"] and not payload["image_url"] and not payload["suggestions"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
yield f"data: {json.dumps({'status': 'end'}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
||||||
|
|
||||||
@@ -147,11 +223,11 @@ async def get_chat_history(thread_id: str):
|
|||||||
last_msg = msgs[-1]
|
last_msg = msgs[-1]
|
||||||
# 获取内容并做摘要截断
|
# 获取内容并做摘要截断
|
||||||
content = getattr(last_msg, "content", str(last_msg))
|
content = getattr(last_msg, "content", str(last_msg))
|
||||||
msg_content = content[:50] + ("..." if len(content) > 50 else "")
|
msg_content = content
|
||||||
|
|
||||||
history_data.append(HistoryItem(
|
history_data.append(HistoryItem(
|
||||||
checkpoint_id=state.config["configurable"]["checkpoint_id"],
|
checkpoint_id=state.config["configurable"]["checkpoint_id"],
|
||||||
last_message=msg_content[:50],
|
last_message=msg_content,
|
||||||
node=state.metadata.get("source"),
|
node=state.metadata.get("source"),
|
||||||
timestamp=state.metadata.get("step")
|
timestamp=state.metadata.get("step")
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, confloat
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
class AgentConfig(BaseModel):
|
||||||
|
type: str = Field(..., description="家具类型,如:沙发、餐桌")
|
||||||
|
region: str = Field(..., description="地区/空间,如:客厅、卧室、户外")
|
||||||
|
style: str = Field(..., description="设计风格,如:极简、工业风、中式")
|
||||||
|
temperature: confloat(ge=0, le=2.0) = Field(default=0.7, description="模型温度")
|
||||||
|
|
||||||
|
|
||||||
class ChatRequest(BaseModel):
|
class ChatRequest(BaseModel):
|
||||||
message: str = Field(..., description="用户的输入指令")
|
message: str = Field(..., description="用户的输入指令")
|
||||||
thread_id: Optional[str] = Field(None, description="会话线程ID,不传则开启新会话")
|
thread_id: Optional[str] = Field(None, description="会话线程ID,不传则开启新会话")
|
||||||
checkpoint_id: Optional[str] = Field(None, description="回溯点的ID,用于从历史点开启新对话")
|
checkpoint_id: Optional[str] = Field(None, description="回溯点的ID,用于从历史点开启新对话")
|
||||||
|
config_params: Optional[AgentConfig] = None
|
||||||
|
# force_sketch: bool = False # 新增:是否强制绘图
|
||||||
|
|
||||||
|
|
||||||
class HistoryItem(BaseModel):
|
class HistoryItem(BaseModel):
|
||||||
|
|||||||
@@ -1,86 +1,117 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from google.oauth2 import service_account
|
from google.oauth2 import service_account
|
||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage, AIMessage
|
||||||
|
from langchain_core.runnables import RunnableConfig
|
||||||
from langchain_google_genai import ChatGoogleGenerativeAI
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
||||||
from src.server.agent.state import AgentState
|
from src.server.agent.state import AgentState
|
||||||
from src.server.agent.tools import generate_2025_report_tool, generate_furniture_sketch
|
from src.server.agent.tools import generate_2025_report_tool, generate_furniture_sketch
|
||||||
from src.server.agent.config_loader import get_agent_prompt
|
from src.server.agent.config_loader import get_agent_prompt
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
|
from src.server.utils.generate_suggestion import generate_chat_suggestions
|
||||||
|
|
||||||
creds = service_account.Credentials.from_service_account_file(
|
creds = service_account.Credentials.from_service_account_file(
|
||||||
settings.GOOGLE_GENAI_USE_VERTEXAI,
|
settings.GOOGLE_GENAI_USE_VERTEXAI,
|
||||||
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
||||||
)
|
)
|
||||||
# 初始化 Gemini 模型 (使用 Flash 以保证速度)
|
|
||||||
llm = ChatGoogleGenerativeAI(
|
|
||||||
model="gemini-2.0-flash", temperature=0.5, credentials=creds,
|
# 辅助函数:根据配置动态获取 LLM
|
||||||
project="aida-461108", location='us-central1', vertexai=True, api_key=settings.GOOGLE_API_KEY
|
def get_model(config: RunnableConfig):
|
||||||
)
|
# 从 configurable 中获取温度,默认为 0.5 (对应你之前的设置)
|
||||||
|
# 这个 key 必须与你在 chat_stream 路由里定义的 "llm_temperature" 一致
|
||||||
|
temp = config["configurable"].get("llm_temperature", 0.5)
|
||||||
|
|
||||||
|
return ChatGoogleGenerativeAI(
|
||||||
|
model="gemini-2.0-flash",
|
||||||
|
temperature=temp,
|
||||||
|
credentials=creds,
|
||||||
|
project=settings.GOOGLE_CLOUD_PROJECT,
|
||||||
|
location=settings.GOOGLE_CLOUD_LOCATION,
|
||||||
|
vertexai=True,
|
||||||
|
api_key=settings.GOOGLE_API_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- 1. Designer Agent (设计顾问) ---
|
# --- 1. Designer Agent (设计顾问) ---
|
||||||
def designer_node(state: AgentState):
|
async def designer_node(state: AgentState, config: RunnableConfig):
|
||||||
"""负责细化设计需求,提供专业参数"""
|
"""负责细化设计需求,提供专业参数"""
|
||||||
|
model = get_model(config) # 获取带动态温度的模型
|
||||||
|
|
||||||
messages = state["messages"]
|
messages = state["messages"]
|
||||||
system_text = get_agent_prompt("designer") or """
|
system_text = get_agent_prompt("designer")
|
||||||
你是一位资深的家具设计师。你的职责是:
|
|
||||||
1. 从用户的模糊描述中提取或补充具体的设计参数(尺寸、材质、人体工学数据)。
|
|
||||||
2. 如果用户想画图,不要直接画,而是先描述清楚细节,然后让 Visualizer 去画。
|
|
||||||
请以专业的口吻回复。
|
|
||||||
"""
|
|
||||||
system_prompt = SystemMessage(content=system_text)
|
system_prompt = SystemMessage(content=system_text)
|
||||||
response = llm.invoke([system_prompt] + messages)
|
# 改为异步调用 ainvoke
|
||||||
|
response = await model.ainvoke([system_prompt] + messages)
|
||||||
return {"messages": [response]}
|
return {"messages": [response]}
|
||||||
|
|
||||||
|
|
||||||
# --- 2. Researcher Agent (情报专家) ---
|
# --- 2. Researcher Agent (情报专家) ---
|
||||||
def researcher_node(state: AgentState):
|
async def researcher_node(state: AgentState, config: RunnableConfig):
|
||||||
"""负责调用报告生成工具"""
|
"""负责调用报告生成工具"""
|
||||||
# 绑定工具给 LLM
|
model = get_model(config)
|
||||||
tools = [generate_2025_report_tool]
|
tools = [generate_2025_report_tool]
|
||||||
llm_with_tools = llm.bind_tools(tools)
|
llm_with_tools = model.bind_tools(tools)
|
||||||
|
|
||||||
messages = state["messages"]
|
messages = state["messages"]
|
||||||
system_text = get_agent_prompt("researcher") or "你是情报专家,负责检索与整理参考资料并生成报告。"
|
system_text = get_agent_prompt("researcher")
|
||||||
system_prompt = SystemMessage(content=system_text)
|
system_prompt = SystemMessage(content=system_text)
|
||||||
response = llm_with_tools.invoke([system_prompt] + messages)
|
response = await llm_with_tools.ainvoke([system_prompt] + messages)
|
||||||
|
|
||||||
# 如果模型决定调用工具
|
|
||||||
if response.tool_calls:
|
if response.tool_calls:
|
||||||
# 这里为了简化,直接在节点内执行工具(LangGraph也可以用 ToolNode)
|
|
||||||
tool_call = response.tool_calls[0]
|
tool_call = response.tool_calls[0]
|
||||||
if tool_call["name"] == "generate_2025_report_tool":
|
if tool_call["name"] == "generate_2025_report_tool":
|
||||||
result = generate_2025_report_tool.invoke(tool_call["args"])
|
# 这里的工具调用如果也是异步的,建议加 await
|
||||||
|
result = await generate_2025_report_tool.ainvoke(tool_call["args"])
|
||||||
return {"messages": [response, HumanMessage(content=str(result))]}
|
return {"messages": [response, HumanMessage(content=str(result))]}
|
||||||
|
|
||||||
return {"messages": [response]}
|
return {"messages": [response]}
|
||||||
|
|
||||||
|
|
||||||
# --- 3. Visualizer Agent (视觉专家) ---
|
# --- 3. Visualizer Agent (视觉专家) ---
|
||||||
def visualizer_node(state: AgentState):
|
async def visualizer_node(state: AgentState, config: RunnableConfig):
|
||||||
"""负责将自然语言转化为绘图 Prompt 并调用绘图工具"""
|
"""负责将自然语言转化为绘图 Prompt 并调用绘图工具"""
|
||||||
|
model = get_model(config)
|
||||||
tools = [generate_furniture_sketch]
|
tools = [generate_furniture_sketch]
|
||||||
llm_with_tools = llm.bind_tools(tools)
|
llm_with_tools = model.bind_tools(tools)
|
||||||
|
|
||||||
messages = state["messages"]
|
messages = state["messages"]
|
||||||
system_text = get_agent_prompt("visualizer") or """
|
system_text = get_agent_prompt("visualizer")
|
||||||
你是视觉专家。你的目标是生成高质量的家具草图。
|
|
||||||
步骤:
|
|
||||||
1. 根据上下文,编写一个详细的 Stable Diffusion 风格的英文 Prompt。
|
|
||||||
2. 必须调用 generate_furniture_sketch 工具来生成图片。
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 强制它尝试调用工具
|
|
||||||
system_prompt = SystemMessage(content=system_text)
|
system_prompt = SystemMessage(content=system_text)
|
||||||
response = llm_with_tools.invoke([system_prompt] + messages)
|
response = await llm_with_tools.ainvoke([system_prompt] + messages)
|
||||||
|
|
||||||
if response.tool_calls:
|
if response.tool_calls:
|
||||||
tool_call = response.tool_calls[0]
|
tool_call = response.tool_calls[0]
|
||||||
if tool_call["name"] == "generate_furniture_sketch":
|
if tool_call["name"] == "generate_furniture_sketch":
|
||||||
result = generate_furniture_sketch.invoke(tool_call["args"])
|
img_url = await generate_furniture_sketch.ainvoke(tool_call["args"])
|
||||||
# 返回工具结果给 LLM,让它生成最终回复
|
return {
|
||||||
final_msg = f"已为您生成草图,链接如下:{result}"
|
"messages": [
|
||||||
return {"messages": [response, HumanMessage(content=final_msg)]}
|
response,
|
||||||
|
ToolMessage(content=img_url, tool_call_id=tool_call["id"]) # 标记这是一个图片结果
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return {"messages": [response]}
|
return {"messages": [response]}
|
||||||
|
|
||||||
|
|
||||||
|
# --- 4. Suggester Agent (推荐对话专家) ---
|
||||||
|
async def suggester_node(state: AgentState, config: RunnableConfig):
|
||||||
|
"""专门生成追问建议的节点,作为流程终点"""
|
||||||
|
model = get_model(config)
|
||||||
|
messages = state["messages"]
|
||||||
|
|
||||||
|
# 只需要分析最近的对话
|
||||||
|
suggestions = await generate_chat_suggestions(messages, model)
|
||||||
|
|
||||||
|
# 返回一个特殊消息,前端通过解析 additional_kwargs 获取按钮内容
|
||||||
|
return {
|
||||||
|
"messages": [
|
||||||
|
AIMessage(
|
||||||
|
content="",
|
||||||
|
additional_kwargs={"suggestions": suggestions},
|
||||||
|
name="Suggester"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ from pymongo import MongoClient
|
|||||||
|
|
||||||
from src.core.config import settings, MONGO_URI
|
from src.core.config import settings, MONGO_URI
|
||||||
from src.server.agent.state import AgentState
|
from src.server.agent.state import AgentState
|
||||||
from src.server.agent.agents import designer_node, researcher_node, visualizer_node
|
from src.server.agent.agents import designer_node, researcher_node, visualizer_node, suggester_node
|
||||||
from langgraph.checkpoint.mongodb import MongoDBSaver
|
from langgraph.checkpoint.mongodb import MongoDBSaver
|
||||||
|
|
||||||
|
|
||||||
# --- Supervisor (路由逻辑) ---
|
# --- Supervisor (路由逻辑) ---
|
||||||
# 定义路由的输出结构,强制 LLM 选择一个
|
# 定义路由的输出结构,强制 LLM 选择一个
|
||||||
class RouteResponse(BaseModel):
|
class RouteResponse(BaseModel):
|
||||||
next: Literal["Designer", "Researcher", "Visualizer", "FINISH"]
|
# 将 FINISH 替换或增加 Suggester
|
||||||
|
next: Literal["Designer", "Researcher", "Visualizer", "Suggester"]
|
||||||
|
|
||||||
|
|
||||||
creds = service_account.Credentials.from_service_account_file(
|
creds = service_account.Credentials.from_service_account_file(
|
||||||
@@ -34,30 +35,23 @@ llm_supervisor = ChatGoogleGenerativeAI(
|
|||||||
def supervisor_node(state: AgentState):
|
def supervisor_node(state: AgentState):
|
||||||
messages = state["messages"]
|
messages = state["messages"]
|
||||||
if not messages:
|
if not messages:
|
||||||
return {"next": "FINISH"}
|
return {"next": "Suggester"}
|
||||||
|
|
||||||
last_message = messages[-1]
|
last_message = messages[-1]
|
||||||
|
|
||||||
# --- 改进的拦截逻辑 ---
|
# --- 拦截逻辑修改 ---
|
||||||
# 如果最后一条消息是 AI 产生的(且没有调用工具),说明专家已经回复完了用户
|
# 如果专家已经回复完了(AIMessage 且无工具调用),则交给 Suggester 生成按钮
|
||||||
# 此时我们才拦截并结束,否则会导致专家没机会说话
|
|
||||||
if isinstance(last_message, AIMessage) and not last_message.tool_calls:
|
if isinstance(last_message, AIMessage) and not last_message.tool_calls:
|
||||||
return {"next": "FINISH"}
|
return {"next": "Suggester"}
|
||||||
|
|
||||||
# 如果最后一条是 HumanMessage,说明用户刚说完,Supervisor 必须派发任务
|
system_prompt = """你是家具设计主管。分配任务给专家:
|
||||||
system_prompt = """
|
- Designer: 设计建议、参数细化。
|
||||||
你是家具设计团队的主管(Supervisor)。
|
- Visualizer: 绘图需求。
|
||||||
请根据用户的意图,选择最合适的专家:
|
- Researcher: 市场报告。
|
||||||
- Designer: 设计建议、参数细化、闲聊、问候。
|
|
||||||
- Visualizer: 绘图、看草图。
|
|
||||||
- Researcher: 市场报告、趋势。
|
|
||||||
|
|
||||||
只需输出专家名称。
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
chain = llm_supervisor.with_structured_output(RouteResponse)
|
chain = llm_supervisor.with_structured_output(RouteResponse)
|
||||||
decision = chain.invoke([{"role": "system", "content": system_prompt}] + messages)
|
decision = chain.invoke([{"role": "system", "content": system_prompt}] + messages)
|
||||||
|
|
||||||
return {"next": decision.next}
|
return {"next": decision.next}
|
||||||
|
|
||||||
|
|
||||||
@@ -68,10 +62,11 @@ workflow.add_node("Supervisor", supervisor_node)
|
|||||||
workflow.add_node("Designer", designer_node)
|
workflow.add_node("Designer", designer_node)
|
||||||
workflow.add_node("Researcher", researcher_node)
|
workflow.add_node("Researcher", researcher_node)
|
||||||
workflow.add_node("Visualizer", visualizer_node)
|
workflow.add_node("Visualizer", visualizer_node)
|
||||||
|
workflow.add_node("Suggester", suggester_node) # 新增节点
|
||||||
|
|
||||||
workflow.add_edge(START, "Supervisor")
|
workflow.add_edge(START, "Supervisor")
|
||||||
|
|
||||||
# 这里的逻辑是关键:Supervisor 决定去向
|
# 修改条件边映射
|
||||||
workflow.add_conditional_edges(
|
workflow.add_conditional_edges(
|
||||||
"Supervisor",
|
"Supervisor",
|
||||||
lambda state: state["next"],
|
lambda state: state["next"],
|
||||||
@@ -79,16 +74,18 @@ workflow.add_conditional_edges(
|
|||||||
"Designer": "Designer",
|
"Designer": "Designer",
|
||||||
"Researcher": "Researcher",
|
"Researcher": "Researcher",
|
||||||
"Visualizer": "Visualizer",
|
"Visualizer": "Visualizer",
|
||||||
"FINISH": END
|
"Suggester": "Suggester" # 原本的 FINISH 现在指向 Suggester
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 重点修改:专家执行完后,必须回到 Supervisor 进行状态检查
|
# 专家执行完依然回到 Supervisor
|
||||||
# 如果 Supervisor 发现专家刚说完话,它会触发上面的逻辑返回 FINISH
|
|
||||||
workflow.add_edge("Designer", "Supervisor")
|
workflow.add_edge("Designer", "Supervisor")
|
||||||
workflow.add_edge("Researcher", "Supervisor")
|
workflow.add_edge("Researcher", "Supervisor")
|
||||||
workflow.add_edge("Visualizer", "Supervisor")
|
workflow.add_edge("Visualizer", "Supervisor")
|
||||||
|
|
||||||
|
# 重点:Suggester 是整个流程的终点
|
||||||
|
workflow.add_edge("Suggester", END)
|
||||||
|
|
||||||
client = MongoClient(MONGO_URI)
|
client = MongoClient(MONGO_URI)
|
||||||
checkpointer = MongoDBSaver(
|
checkpointer = MongoDBSaver(
|
||||||
client=client["furniture_agent_db"],
|
client=client["furniture_agent_db"],
|
||||||
|
|||||||
@@ -1,4 +1,30 @@
|
|||||||
|
import base64
|
||||||
|
import uuid
|
||||||
|
from google.oauth2 import service_account
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
|
from google import genai
|
||||||
|
from google.genai.types import GenerateContentConfig, Modality
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from minio import Minio
|
||||||
|
|
||||||
|
from src.core.config import settings
|
||||||
|
from src.server.utils.new_oss_client import oss_upload_image
|
||||||
|
|
||||||
|
# 初始化全局凭证和客户端
|
||||||
|
creds = service_account.Credentials.from_service_account_file(
|
||||||
|
settings.GOOGLE_GENAI_USE_VERTEXAI,
|
||||||
|
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
||||||
|
)
|
||||||
|
|
||||||
|
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
|
||||||
|
client = genai.Client(
|
||||||
|
credentials=creds,
|
||||||
|
project=settings.GOOGLE_CLOUD_PROJECT,
|
||||||
|
location=settings.GOOGLE_CLOUD_LOCATION,
|
||||||
|
vertexai=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- 模拟你已经开发好的报告生成功能 ---
|
# --- 模拟你已经开发好的报告生成功能 ---
|
||||||
@@ -13,13 +39,76 @@ def generate_2025_report_tool(topic: str) -> str:
|
|||||||
return f"【报告生成成功】已生成关于 {topic} 的 PDF 报告。核心洞察:2025年趋势倾向于生物嗜好设计(Biophilic Design)和可持续软木材质。"
|
return f"【报告生成成功】已生成关于 {topic} 的 PDF 报告。核心洞察:2025年趋势倾向于生物嗜好设计(Biophilic Design)和可持续软木材质。"
|
||||||
|
|
||||||
|
|
||||||
# --- 绘图工具 ---
|
# --- 2. 绘图工具 (接入 Nano Banana 逻辑) ---
|
||||||
@tool
|
@tool
|
||||||
def generate_furniture_sketch(prompt: str) -> str:
|
def generate_furniture_sketch(prompt: str) -> str:
|
||||||
"""
|
"""
|
||||||
用于生成家具草图。输入必须是详细的英文绘画提示词(Prompt)。
|
使用 Gemini 图像生成模型根据详细的英文提示词生成家具设计草图。
|
||||||
"""
|
"""
|
||||||
print(f"\n[系统日志] 正在调用 Gemini/Imagen 绘图 API,Prompt: {prompt}...")
|
print(f"\n[系统日志] 正在调用 Nano Banana (Gemini Image Gen) ...")
|
||||||
# 在真实场景中,这里调用 Google Imagen API 或 Midjourney API
|
|
||||||
# 示例返回一个模拟的图片链接
|
try:
|
||||||
return "https://furniture-design-db.com/generated_sketch_v1.jpg"
|
response = client.models.generate_content(
|
||||||
|
model="gemini-2.5-flash-image",
|
||||||
|
contents=(f"Generate a professional furniture design sketch: {prompt}"),
|
||||||
|
config=GenerateContentConfig(
|
||||||
|
response_modalities=[Modality.TEXT, Modality.IMAGE],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
image_bytes = None
|
||||||
|
for part in response.candidates[0].content.parts:
|
||||||
|
if part.inline_data:
|
||||||
|
image_bytes = part.inline_data.data
|
||||||
|
break
|
||||||
|
|
||||||
|
if not image_bytes:
|
||||||
|
return "未能生成图像数据。"
|
||||||
|
object_name = f"furniture/sketches/{uuid.uuid4()}.png"
|
||||||
|
bucket = "fida-test" # 替换为你的 bucket 名称
|
||||||
|
# 3. 调用你的上传函数
|
||||||
|
upload_res = oss_upload_image(
|
||||||
|
oss_client=minio_client,
|
||||||
|
bucket=bucket,
|
||||||
|
object_name=object_name,
|
||||||
|
image_bytes=image_bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
if upload_res:
|
||||||
|
# 4. 构造访问链接 (如果是私有 bucket,需使用 presigned_get_object)
|
||||||
|
# 这里简单示例为直接访问地址
|
||||||
|
image_url = f"{bucket}/{object_name}"
|
||||||
|
return image_url
|
||||||
|
else:
|
||||||
|
return "图片生成成功,但上传至存储服务器失败。"
|
||||||
|
except Exception as e:
|
||||||
|
return f"绘图流程异常: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(generate_furniture_sketch("椅子"))
|
||||||
|
# creds = service_account.Credentials.from_service_account_file(
|
||||||
|
# settings.GOOGLE_GENAI_USE_VERTEXAI,
|
||||||
|
# scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
||||||
|
# )
|
||||||
|
# client = genai.Client(
|
||||||
|
# credentials=creds,
|
||||||
|
# project=settings.GOOGLE_CLOUD_PROJECT,
|
||||||
|
# location=settings.GOOGLE_CLOUD_LOCATION,
|
||||||
|
# vertexai=True
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# response = client.models.generate_content(
|
||||||
|
# model="gemini-2.5-flash-image",
|
||||||
|
# contents=("Generate an image of the Eiffel tower with fireworks in the background."),
|
||||||
|
# config=GenerateContentConfig(
|
||||||
|
# response_modalities=[Modality.TEXT, Modality.IMAGE],
|
||||||
|
# ),
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# for part in response.candidates[0].content.parts:
|
||||||
|
# if part.text:
|
||||||
|
# print(part.text)
|
||||||
|
# elif part.inline_data:
|
||||||
|
# image = Image.open(BytesIO((part.inline_data.data)))
|
||||||
|
# image.save("example-image-eiffel-tower.png")
|
||||||
|
|||||||
0
src/server/utils/__init__.py
Normal file
0
src/server/utils/__init__.py
Normal file
43
src/server/utils/generate_suggestion.py
Normal file
43
src/server/utils/generate_suggestion.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
|
from langchain_core.output_parsers import JsonOutputParser
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
# 定义输出结构,保证稳定性
|
||||||
|
class SuggestionOutput(BaseModel):
|
||||||
|
suggestions: list[str] = Field(description="A list of 3 short follow-up questions or actions for the user, max 10 chars each.")
|
||||||
|
|
||||||
|
|
||||||
|
async def generate_chat_suggestions(messages, model) -> list[str]:
|
||||||
|
"""
|
||||||
|
根据对话历史生成 3 个推荐追问按钮
|
||||||
|
"""
|
||||||
|
# 只需要最近的几次交互即可判断意图
|
||||||
|
recent_msgs = messages[-4:]
|
||||||
|
|
||||||
|
parser = JsonOutputParser(pydantic_object=SuggestionOutput)
|
||||||
|
|
||||||
|
prompt = ChatPromptTemplate.from_messages([
|
||||||
|
("system", """
|
||||||
|
你是家具设计系统的交互助手。请根据用户的对话历史,预测用户接下来最可能想做的 3 件事。
|
||||||
|
|
||||||
|
【判断逻辑】
|
||||||
|
1. 如果用户已经确定了【类型、材质、风格】但还没有生成过草图 -> 必须推荐 "生成设计草图"。
|
||||||
|
2. 如果刚生成了草图 -> 推荐 "调整材质"、"查看三维视图"、"下载报价单" 等。
|
||||||
|
3. 如果用户还在犹豫 -> 推荐具体的风格或材质询问。
|
||||||
|
|
||||||
|
请直接输出 JSON 格式,包含 suggestions 字段。按钮文案要简短(中文,不超过8个字)。
|
||||||
|
"""),
|
||||||
|
("user", "对话历史:{history}"),
|
||||||
|
])
|
||||||
|
|
||||||
|
chain = prompt | model | parser
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 将消息对象转为字符串喂给模型
|
||||||
|
history_str = "\n".join([f"{m.type}: {m.content}" for m in recent_msgs])
|
||||||
|
result = await chain.ainvoke({"history": history_str})
|
||||||
|
return result.get("suggestions", [])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"建议生成失败: {e}")
|
||||||
|
return []
|
||||||
65
src/server/utils/new_oss_client.py
Normal file
65
src/server/utils/new_oss_client.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import io
|
||||||
|
import logging
|
||||||
|
from io import BytesIO
|
||||||
|
import urllib3
|
||||||
|
from PIL import Image
|
||||||
|
from minio import Minio
|
||||||
|
|
||||||
|
from src.core.config import settings
|
||||||
|
|
||||||
|
minio_client = Minio(settings.MINIO_URL, access_key=settings.MINIO_ACCESS, secret_key=settings.MINIO_SECRET, secure=settings.MINIO_SECURE)
|
||||||
|
|
||||||
|
|
||||||
|
# 自定义 Retry 类
|
||||||
|
class CustomRetry(urllib3.Retry):
|
||||||
|
def increment(self, method=None, url=None, response=None, error=None, **kwargs):
|
||||||
|
# 调用父类的 increment 方法
|
||||||
|
new_retry = super(CustomRetry, self).increment(method, url, response, error, **kwargs)
|
||||||
|
# 打印重试信息
|
||||||
|
logger.info(f"重试连接: {method} {url},错误: {error},重试次数: {self.total - new_retry.total}")
|
||||||
|
return new_retry
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
timeout = urllib3.Timeout(connect=1, read=10.0) # 连接超时 5 秒,读取超时 10 秒
|
||||||
|
http_client = urllib3.PoolManager(
|
||||||
|
num_pools=10, # 设置连接池大小
|
||||||
|
maxsize=10,
|
||||||
|
timeout=timeout,
|
||||||
|
cert_reqs='CERT_REQUIRED', # 需要证书验证
|
||||||
|
retries=CustomRetry(
|
||||||
|
total=5,
|
||||||
|
backoff_factor=0.2,
|
||||||
|
status_forcelist=[500, 502, 503, 504],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 获取图片
|
||||||
|
def oss_get_image(oss_client, bucket, object_name, data_type):
|
||||||
|
# cv2 默认全通道读取
|
||||||
|
image_object = None
|
||||||
|
try:
|
||||||
|
image_data = oss_client.get_object(bucket_name=bucket, object_name=object_name)
|
||||||
|
data_bytes = BytesIO(image_data.read())
|
||||||
|
image_object = Image.open(data_bytes)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f" | 获取图片出现异常 ######: {e}")
|
||||||
|
return image_object
|
||||||
|
|
||||||
|
|
||||||
|
def oss_upload_image(oss_client, bucket, object_name, image_bytes):
|
||||||
|
req = None
|
||||||
|
try:
|
||||||
|
req = oss_client.put_object(bucket_name=bucket, object_name=object_name, data=io.BytesIO(image_bytes), length=len(image_bytes), content_type='image/png')
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f" | 上传图片出现异常 ######: {e}")
|
||||||
|
return req
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
url = "aida-users/89/sketch/123-89.png"
|
||||||
|
read_type = "2"
|
||||||
|
img = oss_get_image(oss_client=minio_client, bucket=url.split('/')[0], object_name=url[url.find('/') + 1:], data_type=read_type)
|
||||||
|
img.show()
|
||||||
|
img.save("result.png")
|
||||||
246
uv.lock
generated
246
uv.lock
generated
@@ -1,6 +1,10 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.14'",
|
||||||
|
"python_full_version < '3.14'",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-doc"
|
name = "annotated-doc"
|
||||||
@@ -33,6 +37,58 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2-cffi"
|
||||||
|
version = "25.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "argon2-cffi-bindings" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2-cffi-bindings"
|
||||||
|
version = "25.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cffi" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asgiref"
|
||||||
|
version = "3.11.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2026.1.4"
|
version = "2026.1.4"
|
||||||
@@ -239,6 +295,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django"
|
||||||
|
version = "6.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "asgiref" },
|
||||||
|
{ name = "sqlparse" },
|
||||||
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/26/3e/a1c4207c5dea4697b7a3387e26584919ba987d8f9320f59dc0b5c557a4eb/django-6.0.2.tar.gz", hash = "sha256:3046a53b0e40d4b676c3b774c73411d7184ae2745fe8ce5e45c0f33d3ddb71a7", size = 10886874, upload-time = "2026-02-03T13:50:31.596Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/ba/a6e2992bc5b8c688249c00ea48cb1b7a9bc09839328c81dc603671460928/django-6.0.2-py3-none-any.whl", hash = "sha256:610dd3b13d15ec3f1e1d257caedd751db8033c5ad8ea0e2d1219a8acf446ecc6", size = 8339381, upload-time = "2026-02-03T13:50:15.501Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -402,15 +472,19 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
|
{ name = "image" },
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core" },
|
||||||
{ name = "langchain-google-genai" },
|
{ name = "langchain-google-genai" },
|
||||||
{ name = "langgraph" },
|
{ name = "langgraph" },
|
||||||
{ name = "langgraph-checkpoint-mongodb" },
|
{ name = "langgraph-checkpoint-mongodb" },
|
||||||
|
{ name = "minio" },
|
||||||
|
{ name = "modality" },
|
||||||
{ name = "motor" },
|
{ name = "motor" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-settings" },
|
{ name = "pydantic-settings" },
|
||||||
{ name = "pymongo" },
|
{ name = "pymongo" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "uuid" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -418,15 +492,19 @@ dependencies = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.128.0" },
|
{ name = "fastapi", extras = ["standard"], specifier = ">=0.128.0" },
|
||||||
{ name = "gunicorn", specifier = ">=25.0.1" },
|
{ name = "gunicorn", specifier = ">=25.0.1" },
|
||||||
|
{ name = "image", specifier = ">=1.5.33" },
|
||||||
{ name = "langchain-core", specifier = ">=1.2.8" },
|
{ name = "langchain-core", specifier = ">=1.2.8" },
|
||||||
{ name = "langchain-google-genai", specifier = ">=4.2.0" },
|
{ name = "langchain-google-genai", specifier = ">=4.2.0" },
|
||||||
{ name = "langgraph", specifier = ">=1.0.7" },
|
{ name = "langgraph", specifier = ">=1.0.7" },
|
||||||
{ name = "langgraph-checkpoint-mongodb", specifier = ">=0.3.1" },
|
{ name = "langgraph-checkpoint-mongodb", specifier = ">=0.3.1" },
|
||||||
|
{ name = "minio", specifier = ">=7.2.20" },
|
||||||
|
{ name = "modality", specifier = ">=0.1.0" },
|
||||||
{ name = "motor", specifier = ">=3.7.1" },
|
{ name = "motor", specifier = ">=3.7.1" },
|
||||||
{ name = "pydantic", specifier = ">=2.12.5" },
|
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||||
{ name = "pydantic-settings", specifier = ">=2.12.0" },
|
{ name = "pydantic-settings", specifier = ">=2.12.0" },
|
||||||
{ name = "pymongo", extras = ["srv"], specifier = ">=4.15.5" },
|
{ name = "pymongo", extras = ["srv"], specifier = ">=4.15.5" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||||
|
{ name = "uuid", specifier = ">=1.30" },
|
||||||
{ name = "uvicorn", specifier = ">=0.40.0" },
|
{ name = "uvicorn", specifier = ">=0.40.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -609,6 +687,17 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "1.5.33"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
{ name = "pillow" },
|
||||||
|
{ name = "six" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/84/be/961693ed384aa91bcc07525c90e3a34bc06c75f131655dfe21310234c933/image-1.5.33.tar.gz", hash = "sha256:baa2e09178277daa50f22fd6d1d51ec78f19c12688921cb9ab5808743f097126", size = 15975, upload-time = "2020-10-27T09:58:36.538Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.6"
|
version = "3.1.6"
|
||||||
@@ -922,6 +1011,31 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minio"
|
||||||
|
version = "7.2.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "argon2-cffi" },
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "pycryptodome" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/40/df/6dfc6540f96a74125a11653cce717603fd5b7d0001a8e847b3e54e72d238/minio-7.2.20.tar.gz", hash = "sha256:95898b7a023fbbfde375985aa77e2cd6a0762268db79cf886f002a9ea8e68598", size = 136113, upload-time = "2025-11-27T00:37:15.569Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "modality"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/01/c8/33c2a80cd0957105fe602a3e3db88c471ed23a5b5d6ae1117b691a098c76/modality-0.1.0.tar.gz", hash = "sha256:abdcbe265d852c337f43b0df5dbe43af133605b9569d3d41d26f753246b1e0a8", size = 9716, upload-time = "2023-09-13T18:37:18.995Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/c3/9dc827e44c2b4893e3c7a6d69134c40aa7d6ea1eafa4305e5626ef167df8/modality-0.1.0-py3-none-any.whl", hash = "sha256:177618f600d1b60dc8b2459e834a154afc0504489238b78ac0f7e5ef203307b2", size = 9956, upload-time = "2023-09-13T18:37:17.925Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "motor"
|
name = "motor"
|
||||||
version = "3.7.1"
|
version = "3.7.1"
|
||||||
@@ -1096,6 +1210,75 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "12.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyasn1"
|
name = "pyasn1"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -1126,6 +1309,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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.12.5"
|
version = "2.12.5"
|
||||||
@@ -1536,6 +1749,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@@ -1587,6 +1809,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlparse"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "starlette"
|
name = "starlette"
|
||||||
version = "0.50.0"
|
version = "0.50.0"
|
||||||
@@ -1645,6 +1876,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2025.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.6.3"
|
version = "2.6.3"
|
||||||
@@ -1654,6 +1894,12 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.30"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ce/63/f42f5aa951ebf2c8dac81f77a8edcc1c218640a2a35a03b9ff2d4aa64c3d/uuid-1.30.tar.gz", hash = "sha256:1f87cc004ac5120466f36c5beae48b4c48cc411968eed0eaecd3da82aa96193f", size = 5811, upload-time = "2007-05-26T11:13:24Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid-utils"
|
name = "uuid-utils"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user