aida agent (基础版)搭建完成

This commit is contained in:
zcr
2026-06-15 14:48:17 +08:00
parent b602c47fc9
commit dbbaa7503c
25 changed files with 1953 additions and 717 deletions

View File

@@ -0,0 +1,47 @@
import json
import logging
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from app.schemas.fashion_agent import FashionAgentRequest
from app.service.fashion_agent.service import FashionAgentService
router = APIRouter()
logger = logging.getLogger()
@router.post("/agent/stream")
async def fashion_agent_stream(request_item: FashionAgentRequest):
"""
服装设计 Agent(流式输出)
- **message**: 用户输入的消息(必填)
- **user_id**: 用户ID,默认 "agent"
- **enable_thinking**: 是否开启思考模式
- **call_print**: 是否直接调用 print 生成印花
- **print_need_prompt_generation**: print 是否需要 LLM 生成 prompt
- **call_logo**: 是否直接调用 logo 生成装饰图案
- **call_sketch**: 是否直接调用 sketch 生成草图
- **sketch_need_prompt_generation**: sketch 是否需要 LLM 生成 prompt
- **call_design**: 是否直接调用 design 生成设计系列
- **design_request_data**: design 请求参数(objects, process_id, requestId, callback_url)
- **call_trending**: 是否直接调用 trending 趋势分析
- **call_explor**: 是否直接调用 explorer 灵感探索
- **provider**: 图片源 (pexels/unsplash),默认 unsplash
返回 SSE 事件流:
- **tools** 事件:工具调用的 started/finished 状态
- **custom** 事件:design 工具的逐个生成结果
- **Done** 事件:流结束标记
"""
try:
logger.info(f"fashion_agent stream request: {json.dumps(request_item.model_dump(), indent=4, ensure_ascii=False)}")
service = FashionAgentService()
return StreamingResponse(
service.run_stream(request_item),
media_type="text/event-stream",
)
except Exception as e:
logger.warning(f"fashion_agent stream exception: {e}")
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -4,6 +4,7 @@ from app.api import api_brand_dna
from app.api import api_clothing_seg
from app.api import api_design
from app.api import api_design_pre_processing
from app.api import api_fashion_agent
from app.api import api_generate_image
from app.api import api_mannequins_edit
from app.api import api_pose_transform
@@ -28,6 +29,7 @@ router.include_router(api_mannequins_edit.router, tags=['api_mannequins_edit'],
router.include_router(api_pose_transform.router, tags=['api_pose_transform'], prefix="/api")
router.include_router(api_clothing_seg.router, tags=['api_clothing_seg'], prefix="/api")
router.include_router(api_sketch_to_garment.router, tags=['sketch_to_garment'], prefix="/api")
router.include_router(api_fashion_agent.router, tags=['fashion_agent'], prefix="/api")
"""停用"""
# from app.api import api_chat_robot

View File

@@ -0,0 +1,27 @@
from typing import Optional
from pydantic import BaseModel, Field
class FashionAgentRequest(BaseModel):
"""服装设计 Agent 请求"""
message: str = Field(default="", description="用户输入的消息")
user_id: str = Field(default="test-agent", description="用户ID,用于生成图片存储路径")
enable_thinking: bool = Field(default=False, description="模型思考是否开启")
call_print: bool = Field(default=False, description="是否直接调用 print 生成印花")
print_need_prompt_generation: bool = Field(default=False, description="print 是否需要 LLM 生成 prompt")
call_logo: bool = Field(default=False, description="是否直接调用 logo 生成装饰图案")
call_sketch: bool = Field(default=False, description="是否直接调用 sketch 生成草图")
sketch_need_prompt_generation: bool = Field(default=False, description="sketch 是否需要 LLM 生成 prompt")
call_design: bool = Field(default=False, description="是否直接调用 design 生成设计系列")
design_request_data: dict = Field(default={}, description="design 请求参数")
call_trending: bool = Field(default=False, description="是否直接调用 trending 趋势分析")
call_explor: bool = Field(default=False, description="是否直接调用 explorer 灵感探索")
provider: Optional[str] = Field(default="unsplash", description="图片源: pexels 或 unsplash")

View File

@@ -294,12 +294,11 @@ def design_generate_v2(request_data):
# 打印结果
logger.info(response.text)
for step, object in enumerate(objects_data):
t = threading.Thread(target=process_object, args=(object, callback_url))
threads.append(t)
t.start()
# 等待所有线程完成,释放局部变量(图片等大对象)
for t in threads:
t.join()
def post_request(url, data=None, json_data=None, headers=None, auth=None, timeout=5):
"""
@@ -320,611 +319,3 @@ def post_request(url, data=None, json_data=None, headers=None, auth=None, timeou
except requests.RequestException as e:
print(f"POST请求出错: {e}")
return None
if __name__ == "__main__":
object_data = {
"objects": [
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 98419,
"offset": [1, 1],
"path": "aida-sys-image/images/female/dress/0825000526.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Dress",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 98420,
"offset": [1, 1],
"path": "aida-sys-image/images/female/skirt/903000127.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Skirt",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 69140,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0902001100.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 81604,
"offset": [1, 1],
"path": "aida-sys-image/images/female/outwear/outwear_p5_729.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 63964,
"offset": [1, 1],
"path": "aida-sys-image/images/female/outwear/0825001572.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 98421,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/blouse_506.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 98422,
"offset": [1, 1],
"path": "aida-sys-image/images/female/trousers/0628001244.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Trousers",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 79927,
"offset": [1, 1],
"path": "aida-sys-image/images/female/outwear/0825000378.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 67473,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0825001350.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 80046,
"offset": [1, 1],
"path": "aida-sys-image/images/female/skirt/0628001443.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Skirt",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 84148,
"offset": [1, 1],
"path": "aida-sys-image/images/female/trousers/0628000751.jpeg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Trousers",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 97321,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0902000222.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 90718,
"offset": [1, 1],
"path": "aida-sys-image/images/female/outwear/0825000314.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 86403,
"offset": [1, 1],
"path": "aida-sys-image/images/female/skirt/0902000231.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Skirt",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 87135,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0902001315.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 87428,
"offset": [1, 1],
"path": "aida-sys-image/images/female/outwear/0902000566.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 98423,
"offset": [1, 1],
"path": "aida-sys-image/images/female/dress/0916001596.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Dress",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"shoulder_right": [212, 107],
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": True,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "28 26 26",
"icon": "none",
"image_id": 86345,
"offset": [1, 1],
"path": "aida-sys-image/images/female/outwear/0825000695.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 78743,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0902001412.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "28 26 26",
"icon": "none",
"image_id": 68988,
"offset": [1, 1],
"path": "aida-sys-image/images/female/trousers/0825000403.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [0.0, 0.0],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Trousers",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
},
],
"process_id": "123",
}
start_time = time.time()
X = design_generate(object_data)
print(time.time() - start_time)
print(X)

View File

View File

@@ -0,0 +1,159 @@
import logging
from typing import Annotated, Required, TypedDict
from langchain_core.messages import AIMessage, AnyMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from app.service.fashion_agent.graph_node.design_graph.tools import design_tool # noqa: E402
logger = logging.getLogger()
"""定义状态"""
class DesignState(TypedDict):
messages: Required[Annotated[list[AnyMessage], add_messages]]
design_request_data: dict = {}
request_objects: list[dict] = []
results: list[dict] = []
"""节点"""
def run_design_node(state: DesignState) -> dict:
"""调用 design_tool 执行设计任务,逐个推送结果"""
from langgraph.config import get_stream_writer
writer = get_stream_writer()
request_data = state.get("design_request_data")
request_objects = request_data.get("objects")
results = []
for item in design_tool.invoke({"objects": request_objects}):
logger.info(f"design result: {item}")
results.append(item)
writer({"event": "tool-output-delta", "tool_name": "design_tool", "type": "design_result", "data": item})
writer({"event": "tool-finished", "tool_name": "design_tool", "type": "design_result", "data": results})
result_text = f"设计完成,共处理 {len(results)} 个对象"
return {"results": results, "messages": [AIMessage(content=result_text)]}
"""构建 Graph"""
def build_design_graph():
"""构建 design graph"""
workflow = StateGraph(DesignState)
workflow.add_node("run_design", run_design_node)
workflow.add_edge(START, "run_design")
workflow.add_edge("run_design", END)
return workflow.compile()
design_graph = build_design_graph()
if __name__ == "__main__":
request_data = {
"objects": [
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"relation_type": "System",
"shoulder_right": [212, 107],
"relation_id": 1020356,
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": False,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "209 196 171",
"image_id": 84093,
"offset": [1, 1],
"path": "aida-users/89/sketchboard/female/Outwear/0943d209-7ce0-408c-bc61-83f15da94138.png",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [[0.0, 0.0]],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"color": "63 71 73",
"image_id": 100496,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0628001684.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [[0.0, 0.0]],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "111 78 63",
"gradient": "aida-gradient/f69b98e8-4248-4f7a-98a2-21bac41bf3e0.png",
"image_id": 92193,
"offset": [1, 1],
"path": "aida-sys-image/images/female/trousers/0825001160.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [[0.0, 0.0]],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Trousers",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
"objectSign": "65830966",
}
],
"process_id": "4802946666428422",
"requestId": "1d1e7641-0d62-4da2-adc0-b4404910723c",
"callback_url": "https://api.aida.com.hk/api/third/party/receiveDesignResults",
}
result = design_graph.invoke({"design_request_data": request_data})
print(result)

View File

@@ -0,0 +1,206 @@
import logging
import queue
import threading
from langchain.tools import tool
from pydantic import BaseModel, Field
from app.service.design_fast.design_generate import process_item, process_layer
from app.service.design_fast.utils.synthesis_item import synthesis, synthesis_single, update_base_size_priority
logger = logging.getLogger()
class DesignModel(BaseModel):
objects: list[dict] = Field(description="")
@tool(args_schema=DesignModel, description="design tool")
def design_tool(objects: list[dict]):
"""design tool"""
result_queue = queue.Queue()
def process_object(obj):
basic = obj["basic"]
design_type = basic.get("design_type", "default")
items_response = {
"layers": [],
"objectSign": obj["objectSign"] if "objectSign" in obj.keys() else "",
}
if basic["single_overall"] == "overall":
item_results = []
for item in obj["items"]:
item_results.append(process_item(item, basic, design_type))
layers = []
for item in item_results:
process_layer(item, layers)
layers = sorted(layers, key=lambda s: s.get("priority", float("inf")))
layers, new_size = update_base_size_priority(layers)
for lay in layers:
items_response["layers"].append(
{
"image_category": "body" if lay["name"] == "mannequin" else lay["name"],
"position": lay["position"],
"priority": lay.get("priority", None),
"resize_scale": lay["resize_scale"] if "resize_scale" in lay.keys() else None,
"image_size": lay["image"] if lay["image"] is None else lay["image"].size,
"gradient_string": lay["gradient_string"] if "gradient_string" in lay.keys() else "",
"mask_url": lay["mask_url"],
"image_url": lay["image_url"] if "image_url" in lay.keys() else None,
"pattern_overall_image_url": (
lay["pattern_overall_image_url"] if "pattern_overall_image_url" in lay.keys() else None
),
"pattern_print_image_url": lay["pattern_print_image_url"] if "pattern_print_image_url" in lay.keys() else None,
}
)
items_response["synthesis_url"] = synthesis(layers, new_size, basic)
else:
item_result = process_item(obj["items"][0], basic, design_type)
items_response["layers"].append(
{
"image_category": f"{item_result['name']}_front",
"image_size": item_result["back_image"].size if item_result["back_image"] else None,
"position": None,
"priority": 0,
"image_url": item_result["front_image_url"],
"mask_url": item_result["mask_url"],
"gradient_string": item_result["gradient_string"] if "gradient_string" in item_result.keys() else "",
"pattern_overall_image_url": (
item_result["pattern_overall_image_url"] if "pattern_overall_image_url" in item_result.keys() else None
),
"pattern_print_image_url": (
item_result["pattern_print_image_url"] if "pattern_print_image_url" in item_result.keys() else None
),
}
)
items_response["layers"].append(
{
"image_category": f"{item_result['name']}_back",
"image_size": item_result["front_image"].size if item_result["front_image"] else None,
"position": None,
"priority": 0,
"image_url": item_result["back_image_url"],
"mask_url": item_result["mask_url"],
"gradient_string": item_result["gradient_string"] if "gradient_string" in item_result.keys() else "",
"pattern_overall_image_url": (
item_result["pattern_overall_image_url"] if "pattern_overall_image_url" in item_result.keys() else None
),
"pattern_print_image_url": (
item_result["pattern_print_image_url"] if "pattern_print_image_url" in item_result.keys() else None
),
}
)
items_response["synthesis_url"] = synthesis_single(item_result["front_image"], item_result["back_image"])
logger.info(items_response)
result_queue.put(items_response)
# 启动所有线程
threads = []
for obj in objects:
t = threading.Thread(target=process_object, args=(obj,))
threads.append(t)
t.start()
# 主线程逐个取出结果 yield
finished = 0
total = len(objects)
while finished < total:
result = result_queue.get()
yield result
finished += 1
if __name__ == "__main__":
request_objects = [
{
"basic": {
"body_point_test": {
"waistband_right": [203, 249],
"hand_point_right": [229, 343],
"waistband_left": [119, 248],
"hand_point_left": [97, 343],
"shoulder_left": [108, 107],
"relation_type": "System",
"shoulder_right": [212, 107],
"relation_id": 1020356,
},
"layer_order": False,
"scale_bag": 0.7,
"scale_earrings": 0.16,
"self_template": False,
"single_overall": "overall",
"switch_category": "",
},
"items": [
{
"color": "209 196 171",
"image_id": 84093,
"offset": [1, 1],
"path": "aida-users/89/sketchboard/female/Outwear/0943d209-7ce0-408c-bc61-83f15da94138.png",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [[0.0, 0.0]],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Outwear",
},
{
"color": "63 71 73",
"image_id": 100496,
"offset": [1, 1],
"path": "aida-sys-image/images/female/blouse/0628001684.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [[0.0, 0.0]],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Blouse",
},
{
"color": "111 78 63",
"gradient": "aida-gradient/f69b98e8-4248-4f7a-98a2-21bac41bf3e0.png",
"image_id": 92193,
"offset": [1, 1],
"path": "aida-sys-image/images/female/trousers/0825001160.jpg",
"print": {
"element": {"element_angle_list": [], "element_path_list": [], "element_scale_list": [], "location": []},
"overall": {
"location": [[0.0, 0.0]],
"print_angle_list": [0.0, 0.0],
"print_path_list": [],
"print_scale_list": [[0.0, 0.0]],
},
"single": {"location": [], "print_angle_list": [], "print_path_list": [], "print_scale_list": []},
},
"resize_scale": [1.0, 1.0],
"type": "Trousers",
},
{
"body_path": "aida-sys-image/models/female/2e4815b9-1191-419d-94ed-5771239ca4a5.png",
"image_id": 67277,
"offset": [1, 1],
"resize_scale": [1.0, 1.0],
"type": "Body",
},
],
"objectSign": "65830966",
}
]
result = design_tool.invoke({"objects": request_objects})
for item in result:
print(item)

View File

@@ -0,0 +1,138 @@
import asyncio
import logging
from typing import Annotated, Required, TypedDict
from langchain.tools import ToolRuntime
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage, SystemMessage
from langchain_qwq import ChatQwen
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from pydantic import BaseModel, Field
from langchain_core.runnables import RunnableConfig
from app.service.fashion_agent.graph_node.explorer_graph.tools import explor_tool
from app.service.fashion_agent.init_llm import build_llm
logger = logging.getLogger()
"""定义状态"""
class ExplorerState(TypedDict):
messages: Required[Annotated[list[AnyMessage], add_messages]]
input_text: str
search_query: str
image_results: list[dict] # 每项包含 image_url 和 minio_path
provider: str = "unsplash" # 图片源: "pexels" 或 "unsplash"
"""节点"""
def extract_input_node(state: ExplorerState) -> dict:
"""从 messages 中提取用户输入"""
input_text = state["messages"][0].content if state.get("messages") else ""
return {"input_text": input_text}
class SearchQuery(BaseModel):
"""搜索关键词"""
query: str = Field(description="用于搜索灵感图片的英文关键词,简洁有力")
# TODO 要考虑搜索图片失败或者图片不存在的情况, 搜索不到 需要调整搜索词或者拆分搜索词最终失败的话调用mood board生成工具生成 保证绝对有图片
async def generate_query_node(state: ExplorerState) -> dict:
"""使用 LLM 分析用户输入,生成搜索关键词"""
input_text = state["input_text"]
logger.info(f"[Explorer] 用户输入: {input_text}")
llm = build_llm()
structured_llm = llm.with_structured_output(SearchQuery)
messages = [
SystemMessage(content="""你是一个专业的服装设计师助手。
根据用户输入生成一个英文搜索关键词用于在图片库中搜索服装设计灵感图片moodboard
要求:
1. 使用英文,简洁有力
2. 适合搜索高质量的设计灵感图片
例如:
用户输入:"夏季连衣裙,清新风格"
输出summer dress fresh style"""),
HumanMessage(content=input_text),
]
result = structured_llm.invoke(messages)
logger.info(f"[Explorer] LLM 生成的搜索关键词: {result.query}")
return {"search_query": result.query}
async def search_and_upload_node(state: ExplorerState, config: RunnableConfig) -> dict:
"""使用搜索关键词获取图片并上传到 minio"""
query = state.get("search_query", "")
user_id = state.get("user_id", "agent")
provider = state.get("provider", "unsplash")
try:
results = await explor_tool.ainvoke({"query": query, "per_page": 4, "user_id": user_id, "method": provider}, config=config)
except Exception as e:
logger.error(f"[Explorer] 搜索失败 '{query}': {e}")
results = []
return {"image_results": results}
def summarize_node(state: ExplorerState) -> dict:
"""汇总结果"""
input_text = state.get("input_text", "")
query = state.get("search_query", "")
results = state.get("image_results", [])
result_text = f"【灵感探索 Moodboard】\n\n"
result_text += f"基于您的需求:「{input_text}\n"
result_text += f"搜索关键词:{query}\n\n"
result_text += f"已为您找到 {len(results)} 张灵感图片:\n"
for i, item in enumerate(results, 1):
result_text += f" {i}. 原图: {item.get('image_url', '')}\n"
result_text += f" Minio: {item.get('minio_path', '')}\n"
return {"messages": [AIMessage(content=result_text)]}
"""构建图"""
def build_explorer_graph():
"""构建灵感探索图"""
workflow = StateGraph(ExplorerState)
workflow.add_node("extract_input", extract_input_node)
workflow.add_node("generate_query", generate_query_node)
workflow.add_node("search_and_upload", search_and_upload_node)
workflow.add_node("summarize", summarize_node)
workflow.add_edge(START, "extract_input")
workflow.add_edge("extract_input", "generate_query")
workflow.add_edge("generate_query", "search_and_upload")
workflow.add_edge("search_and_upload", "summarize")
workflow.add_edge("summarize", END)
return workflow.compile()
if __name__ == "__main__":
async def test():
graph = build_explorer_graph()
result = await graph.ainvoke(
{
"messages": [HumanMessage(content="夏季连衣裙,清新自然风格")],
"provider": "unsplash",
}
)
print(result["messages"][-1].content)
asyncio.run(test())

View File

@@ -0,0 +1,54 @@
from langchain.tools import ToolRuntime, tool
from pydantic import BaseModel, Field
from langchain_core.runnables import RunnableConfig
from app.service.fashion_agent.graph_node.node_tools.pexels_search import search_photos
from app.service.fashion_agent.graph_node.node_tools.unsplash_search import get_random_photos
class SearchInput(BaseModel):
"""Input schema for Pexels Search Tool."""
query: str = Field(description="Search query for Pexels, e.g., 'minimalist fashion moodboard', 'summer dress inspiration'")
per_page: int = Field(description="Number of images to return (1-80)", default=4)
user_id: str = Field(description="User ID for image storage", default="agent")
method: str = Field(description="", default="unsplash")
@tool(args_schema=SearchInput)
async def explor_tool(
query: str, per_page: int = 4, user_id: str = "agent", method: str = "unsplash", config: RunnableConfig = None
) -> list[dict]:
"""Search for fashion inspiration images on Unsplash and upload to minio. Returns a list of dicts with image_url and minio_path."""
if config:
# 方式 1从 configurable 获取
user_id = config.get("configurable", {}).get("user_id", "agent")
if method == "unsplash":
return await get_random_photos(query, count=per_page, user_id=user_id)
elif method == "pexels":
return await search_photos(query, per_page=per_page, user_id=user_id)
else:
pass
if __name__ == "__main__":
import asyncio
async def test():
urls = await get_random_photos("summer dress fresh natural style", count=4)
print(f"Uploaded {len(urls)} images to minio:")
for url in urls:
print(f" {url}")
asyncio.run(test())
if __name__ == "__main__":
import asyncio
async def test():
urls = await search_photos("minimalist fashion moodboard", per_page=4)
print(f"Uploaded {len(urls)} images to minio:")
for url in urls:
print(f" {url}")
asyncio.run(test())

View File

@@ -0,0 +1,152 @@
import asyncio
from typing import Annotated, Required, TypedDict
from langchain_core.messages import AIMessage, AnyMessage
from langchain_qwq import ChatQwen
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from pydantic import BaseModel, Field
from app.service.fashion_agent.graph_node.node_tools.generate_logo import generate_logo_tool
from app.service.fashion_agent.init_llm import qwen_plus_llm
"""初始化 LLM TODO 将 API Key 替换为环境变量或者配置文件中的值,避免在代码中硬编码敏感信息"""
"""定义状态"""
class LogoState(TypedDict):
messages: Required[Annotated[list[AnyMessage], add_messages]]
input_text: str
user_id: str = "agent"
role: str = ""
gender: str = ""
style: str = ""
need_prompt_generation: bool = True # 是否需要使用 prompt 生成节点
logo_num: int = 1
logo_prompts: list[str] = []
logo_img_urls: list[str] = []
"""生成 Logo 的提示词节点"""
# 定义输出结构
class LogoPrompt(BaseModel):
"""生成的 Logo 图像提示词"""
prompts: list[str] = Field(description="用于生成 Logo 的详细提示词")
def extract_input_node(state: LogoState) -> dict:
"""从 messages 中提取用户输入"""
input_text = state["messages"][0].content if state.get("messages") else ""
return {"input_text": input_text}
def generate_logo_prompt_node(state: LogoState) -> dict:
"""根据用户输入生成 Logo 的图像生成提示词"""
structured_llm = qwen_plus_llm.with_structured_output(LogoPrompt)
messages = [
SystemMessage(content="""从用户输入中提取核心主题词,只输出一个简单的英文单词。
例如:
- "我想要一个猫咪图案" -> "cat"
- "设计一个花朵" -> "flower"
- "可爱的狗" -> "dog"
只输出单词,不要其他内容。"""),
HumanMessage(content=state["input_text"]),
]
result = structured_llm.invoke(messages)
prompts = result.prompts
return {
"logo_prompts": prompts,
}
"""生成 Logo 图案节点"""
async def generate_logo_img_node(state: LogoState) -> dict:
"""根据生成的提示词,生成 Logo 图案"""
# 如果 logo_prompts 为空,使用 input_text 作为 prompt
prompts = state["logo_prompts"] if state["logo_prompts"] else [state["input_text"]]
logo_img_urls = []
for i in range(state.get("logo_num", 1)):
image_url = await generate_logo_tool.ainvoke({"prompt": prompts[i], "user_id": state.get("user_id", "agent")})
logo_img_urls.append(image_url)
result_text = f"Logo 生成完成,共生成 {len(logo_img_urls)} 张图片:\n"
return {"logo_img_urls": logo_img_urls, "messages": [AIMessage(content=result_text)]}
"""条件分支 判断是否需要生成 prompt"""
def should_generate_prompt(state: LogoState) -> str:
"""条件分支:判断是否需要生成 prompt"""
if state.get("need_prompt_generation", True):
return "gen_prompt"
else:
return "gen_logo"
def build_logo_graph():
"""构建独立的画像收集 Graph"""
workflow = StateGraph(LogoState)
workflow.add_node("extract_input", extract_input_node)
workflow.add_node("gen_prompt", generate_logo_prompt_node)
workflow.add_node("gen_logo", generate_logo_img_node)
# 添加边
workflow.add_edge(START, "extract_input")
workflow.add_conditional_edges(
"extract_input",
should_generate_prompt,
{
"gen_prompt": "gen_prompt",
"gen_logo": "gen_logo",
},
)
workflow.add_edge("gen_prompt", "gen_logo")
workflow.add_edge("gen_logo", END)
graph = workflow.compile()
return graph
async def main(test_input, user_id="agent", need_prompt_generation=True):
graph = build_logo_graph()
result = await graph.ainvoke(
{
"input_text": test_input,
"user_id": user_id,
"logo_prompts": [] if need_prompt_generation else [test_input],
"need_prompt_generation": need_prompt_generation,
"role": "",
"gender": "",
"style": "",
}
)
return result
if __name__ == "__main__":
# 测试示例 1: 需要 prompt 生成(默认)- 简单关键词输入
test_input = "我想要一个金毛图案"
result = asyncio.run(main(test_input, need_prompt_generation=True))
print("=== 需要 prompt 生成 ===")
print(f"Result: {result}")
# 测试示例 2: 直接使用用户提供的 prompt
user_prompt = "golden retriever"
result = asyncio.run(main(user_prompt, need_prompt_generation=False))
print("\n=== 直接使用 prompt ===")
print(f"Result: {result}")

View File

@@ -0,0 +1,27 @@
import httpx
async def generate_image(
bucket_name="fida-public-bucket",
object_name=f"furniture/sketches/123456.png",
prompt="Generate a modern minimalist dining chair made of light "
"oak wood and white leather, with slim metal legs, photographed "
"in a bright Scandinavian living room with natural sunlight, high detail, "
"8k resolution.",
):
request_data = {
"input_image_paths": [],
"prompt": prompt,
"bucket_name": bucket_name,
"object_name": object_name,
"width": 1024,
"height": 1024,
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(
f"http://20.1.1.33:14202/predict",
json=request_data,
)
result = resp.json()
image_url = result.get("output_path", None)
return image_url

View File

@@ -0,0 +1,79 @@
import asyncio
import concurrent.futures
import random
import numpy as np
import tritonclient.grpc as grpcclient
from langchain.tools import tool
from PIL import Image
from pydantic import BaseModel, Field
from tritonclient.utils import np_to_triton_dtype
from uuid_utils import uuid7
from app.core.config import settings
from app.service.generate_image.utils.upload_sd_image import upload_SDXL_image
# 模型配置
GSL_MODEL_URL = f"{settings.B_4_X_4090_SERVICE_HOST}:10041"
GSL_MODEL_NAME = "stable_diffusion_xl_transparent"
# 线程池用于执行同步推理
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
def _generate_logo_sync(prompt: str) -> Image.Image:
"""同步生成 Logo 的内部函数"""
seed = random.randint(0, 2**32 - 1)
grpc_client = grpcclient.InferenceServerClient(url=GSL_MODEL_URL)
# 准备输入
prompts = [prompt]
text_obj = np.array(prompts, dtype="object").reshape((-1, 1))
input_text = grpcclient.InferInput("prompt", text_obj.shape, np_to_triton_dtype(text_obj.dtype))
input_text.set_data_from_numpy(text_obj)
negative_prompts = "bad, ugly"
text_obj_neg = np.array(negative_prompts, dtype="object").reshape((-1, 1))
input_text_neg = grpcclient.InferInput("negative_prompt", text_obj_neg.shape, np_to_triton_dtype(text_obj_neg.dtype))
input_text_neg.set_data_from_numpy(text_obj_neg)
seed_input = np.array(seed, dtype="object").reshape((-1, 1))
input_seed = grpcclient.InferInput("seed", seed_input.shape, np_to_triton_dtype(seed_input.dtype))
input_seed.set_data_from_numpy(seed_input)
inputs = [input_text, input_text_neg, input_seed]
# 同步推理
result = grpc_client.infer(model_name=GSL_MODEL_NAME, inputs=inputs)
image = result.as_numpy("generated_image")
return Image.fromarray(np.squeeze(image.astype(np.uint8)))
async def generate_logo(prompt: str) -> Image.Image:
"""异步生成透明背景的 Logo 图片"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(executor, _generate_logo_sync, prompt)
class GenerateLogoToolInput(BaseModel):
"""Input schema for the Generate Logo Tool."""
prompt: str = Field(description="Simple keyword for logo generation, e.g., 'cat', 'flower', 'dog'")
user_id: str = Field(description="User ID for image storage", default="agent")
@tool(args_schema=GenerateLogoToolInput)
async def generate_logo_tool(prompt: str, user_id: str = "agent") -> str:
"""Generate a transparent background logo image based on a simple keyword."""
image = await generate_logo(prompt=prompt)
# 上传到 minio使用线程池避免阻塞事件循环
file_name = f"{uuid7()}.png"
loop = asyncio.get_event_loop()
image_url = await loop.run_in_executor(executor, upload_SDXL_image, image, user_id, "logo", file_name)
return image_url
if __name__ == "__main__":
result = asyncio.run(generate_logo_tool.ainvoke({"prompt": "golden retriever"}))
print(f"Logo saved to: {result}")

View File

@@ -0,0 +1,72 @@
import asyncio
import concurrent.futures
import io
import logging
import os
import httpx
from dotenv import load_dotenv
from PIL import Image
from uuid_utils import uuid7
from app.service.generate_image.utils.upload_sd_image import upload_SDXL_image
load_dotenv()
logger = logging.getLogger()
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY", "")
PEXELS_BASE_URL = os.environ.get("PEXELS_BASE_URL", "")
# 线程池用于执行同步上传
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
async def search_photos(query: str, per_page: int = 4, user_id: str = "agent") -> list[dict]:
"""从 Pexels 搜索图片并上传到 minio
Args:
query: 搜索关键词
per_page: 返回图片数量 (1-80)
user_id: 用户 ID
Returns:
图片信息列表,每项包含 image_url 和 minio_path
"""
# 搜索图片
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(
f"{PEXELS_BASE_URL}/search",
headers={"Authorization": PEXELS_API_KEY},
params={
"query": query,
"per_page": per_page,
"orientation": "square",
"size": "medium",
},
)
if response.status_code != 200:
raise Exception(f"Pexels API error: {response.status_code} - {response.text}")
data = response.json()
photos = data.get("photos", [])
# 下载并上传到 minio
results = []
for photo in photos:
try:
# 下载图片(使用 large 尺寸)
image_url = photo["src"]["original"]
async with httpx.AsyncClient(timeout=60) as dl_client:
dl_response = await dl_client.get(image_url)
image = Image.open(io.BytesIO(dl_response.content))
# 上传到 minio使用线程池避免阻塞事件循环
file_name = f"{uuid7()}.jpg"
loop = asyncio.get_event_loop()
minio_url = await loop.run_in_executor(executor, upload_SDXL_image, image, user_id, "explorer", file_name)
results.append({"image_url": image_url, "minio_path": minio_url})
logger.info(f"[Explorer] 上传成功: {minio_url}")
except Exception as e:
logger.error(f"[Explorer] 上传失败: {e}")
return results

View File

@@ -0,0 +1,90 @@
import asyncio
import concurrent.futures
import io
import logging
import os
import httpx
from PIL import Image
from uuid_utils import uuid7
from dotenv import load_dotenv
from app.service.generate_image.utils.upload_sd_image import upload_SDXL_image
load_dotenv()
# Unsplash API 配置
UNSPLASH_ACCESS_KEY = os.environ.get("UNSPLASH_ACCESS_KEY", "")
UNSPLASH_BASE_URL = os.environ.get("UNSPLASH_BASE_URL", "")
logger = logging.getLogger()
# 线程池用于执行同步上传
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
async def get_random_photos(query: str, count: int = 4, user_id: str = "agent") -> list[dict]:
"""从 Unsplash 获取随机图片并上传到 minio
Args:
query: 搜索关键词
count: 返回图片数量 (1-30)
user_id: 用户 ID
Returns:
图片信息列表,每项包含 image_url 和 minio_path
"""
# 获取随机图片
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(
f"{UNSPLASH_BASE_URL}/search/photos",
headers={"Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}"},
params={
"query": query,
"per_page": count,
"page": 1,
},
)
if response.status_code != 200:
raise Exception(f"Unsplash API error: {response.status_code} - {response.text}")
data = response.json()
# /search/photos 返回 {"results": [...], "total": ...}
photos = data.get("results", [])
# 下载并上传到 minio
results = []
for photo in photos:
try:
# 下载图片
image_url = photo["urls"]["raw"]
async with httpx.AsyncClient(timeout=60) as dl_client:
dl_response = await dl_client.get(image_url)
image = Image.open(io.BytesIO(dl_response.content))
# 上传到 minio使用线程池避免阻塞事件循环
file_name = f"{uuid7()}.jpg"
loop = asyncio.get_event_loop()
minio_url = await loop.run_in_executor(executor, upload_SDXL_image, image, user_id, "explorer", file_name)
results.append({"image_url": image_url, "minio_path": minio_url})
logger.info(f"[Explorer] 上传成功: {minio_url}")
except Exception as e:
logger.error(f"[Explorer] 上传失败: {e}")
return results
if __name__ == "__main__":
import asyncio
async def test():
"""测试 Unsplash 搜索"""
query = "summer dress fresh natural style"
print(f"搜索关键词: {query}")
print("=" * 50)
results = await get_random_photos(query, count=4, user_id="test")
print(f"\n找到 {len(results)} 张图片:")
for i, item in enumerate(results, 1):
print(f" {i}. 原图: {item.get('image_url', '')}")
print(f" Minio: {item.get('minio_path', '')}")
asyncio.run(test())

View File

@@ -0,0 +1,158 @@
import asyncio
import logging
from typing import Annotated, Required, TypedDict
from langchain_qwq import ChatQwen
from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from pydantic import BaseModel, Field
from app.service.fashion_agent.init_llm import qwen_plus_llm
from app.service.fashion_agent.graph_node.print_graph.tools import generate_print_tool, test
logger = logging.getLogger()
"""定义状态"""
class PrintState(TypedDict):
messages: Required[Annotated[list[AnyMessage], add_messages]]
input_text: str
role: str = ""
gender: str = ""
style: str = ""
print_need_prompt_generation: bool = False # 是否需要使用 prompt 生成节点
print_num: int = 1
print_prompts: list[str] = []
print_img_urls: list[str] = []
"""生成印花图案的提示词节点"""
# 定义输出结构
class PrintPrompt(BaseModel):
"""生成的印花图像提示词"""
prompts: list[str] = Field(description="用于生成印花图案的详细提示词")
def extract_input_node(state: PrintState) -> dict:
"""从 messages 中提取用户输入"""
input_text = state["messages"][0].content if state.get("messages") else ""
return {"input_text": input_text}
def generate_print_prompt_node(state: PrintState) -> dict:
"""根据用户输入生成印花图案的图像生成提示词"""
structured_llm = qwen_plus_llm.with_structured_output(PrintPrompt)
messages = [
SystemMessage(content=f"""你是一个专业的印花图案设计师。
请根据用户输入生成用于AI图像生成的印花图案提示词。
要求:
1. 提示词应该详细描述印花图案的样式、元素、颜色、布局
2. 提示词应该适合用于 Stable Diffusion 图像生成模型
3. 提示词应该使用英文,因为图像生成模型对英文理解更好
4. 提示词数量为 {state.get("print_num", 1)}
"""),
HumanMessage(content=state["input_text"]),
]
result = structured_llm.invoke(messages)
prompts = result.prompts
logger.info(f"[Print Graph] Generated print prompts: {prompts}")
return {
"print_prompts": prompts,
}
"""生成印花图案节点"""
async def generate_print_img_node(state: PrintState) -> dict:
"""根据生成的提示词,生成印花图案"""
# 如果 print_prompts 为空,使用 input_text 作为 prompt
if state.get("print_need_prompt_generation", False):
prompts = state["print_prompts"] if state["print_prompts"] else [state["input_text"]]
else:
input_text = state.get("input_text", "")
prompts = [input_text]
print_img_urls = []
for prompt in prompts:
image_url = await generate_print_tool.ainvoke({"prompt": prompt})
print_img_urls.append(image_url)
logger.info(f"[Print Graph] Generated print image URL: {image_url}")
return {"print_img_urls": print_img_urls}
"""条件分支 判断是否需要生成 prompt"""
def should_generate_prompt(state: PrintState) -> str:
"""条件分支:判断是否需要生成 prompt"""
logger.info(
f"[Print Graph] should_generate_prompt: print_need_prompt_generation={state.get('print_need_prompt_generation')}, print_prompts={state.get('print_prompts')}"
)
if state.get("print_need_prompt_generation", True):
return "gen_prompt"
else:
return "gen_print"
def build_print_graph():
workflow = StateGraph(PrintState)
workflow.add_node("extract_input", extract_input_node)
workflow.add_node("gen_prompt", generate_print_prompt_node)
workflow.add_node("gen_print", generate_print_img_node)
# 添加边
workflow.add_edge(START, "extract_input")
workflow.add_conditional_edges(
"extract_input",
should_generate_prompt,
{
"gen_prompt": "gen_prompt",
"gen_print": "gen_print",
},
)
workflow.add_edge("gen_prompt", "gen_print")
workflow.add_edge("gen_print", END)
graph = workflow.compile()
return graph
async def main(test_input, print_need_prompt_generation=True):
graph = build_print_graph()
result = await graph.ainvoke(
{
"input_text": test_input,
"print_prompts": [] if print_need_prompt_generation else [test_input],
"print_need_prompt_generation": print_need_prompt_generation,
"role": "",
"gender": "",
"style": "",
}
)
return result
if __name__ == "__main__":
# 测试示例 1: 需要 prompt 生成(默认)
test_input = "我想要一个优雅的花卉印花,适合用于连衣裙,颜色以粉色和白色为主"
result = asyncio.run(main(test_input, print_need_prompt_generation=True))
print("=== 需要 prompt 生成 ===")
print(f"Result: {result}")
# 测试示例 2: 直接使用用户提供的 prompt
user_prompt = "Elegant floral print pattern, pink and white colors, suitable for dress fabric, seamless tileable design"
result = asyncio.run(main(user_prompt, print_need_prompt_generation=False))
print("\n=== 直接使用 prompt ===")
print(f"Result: {result}")

View File

@@ -0,0 +1,39 @@
import asyncio
from langchain.tools import tool
from langsmith import uuid7
from pydantic import BaseModel, Field
from app.service.fashion_agent.graph_node.node_tools.generate_image import generate_image
class GenerateImageToolInput(BaseModel):
"""Input schema for the Generate Image Tool."""
prompt: str = Field(description="Description of the desired image, e.g., 'A cozy living room with warm lighting and natural textures.'")
@tool(args_schema=GenerateImageToolInput)
async def generate_print_tool(prompt: str) -> str:
"""Generate an image based on the provided prompt."""
bucket_name = "aida-users"
object_name = f"agent_generate_print/{uuid7()}.png"
image_url = await generate_image(prompt=prompt, bucket_name=bucket_name, object_name=object_name)
return image_url
@tool
async def test(text: str):
"""测试工具函数,返回固定字符串"""
return text
async def run_test():
result = await generate_print_tool.ainvoke({"prompt": "A cozy living room with warm lighting and natural textures."})
return result
if __name__ == "__main__":
result = asyncio.run(run_test())
print(result)

View File

@@ -0,0 +1,178 @@
import asyncio
import logging
from typing import Annotated, Required, TypedDict
from langchain_qwq import ChatQwen
from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage, AIMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from pydantic import BaseModel, Field
from app.service.fashion_agent.init_llm import qwen_plus_llm
from app.service.fashion_agent.graph_node.sketch_graph.tools import generate_sketch_tool
logger = logging.getLogger()
"""定义状态"""
class SketchState(TypedDict):
messages: Required[Annotated[list[AnyMessage], add_messages]]
input_text: str
role: str = ""
gender: str = ""
style: str = ""
sketch_need_prompt_generation: bool = False # 是否需要使用 prompt 生成节点
sketch_num: int = 1
sketch_prompts: list[str] = []
sketch_img_urls: list[str] = []
"""生成服装草图的提示词节点"""
# 定义输出结构
class SketchPrompt(BaseModel):
"""生成的印花图像提示词"""
prompts: list[str] = Field(description="用于生成服装草图的详细提示词")
def extract_input_node(state: SketchState) -> dict:
"""从 messages 中提取用户输入"""
input_text = state["messages"][0].content if state.get("messages") else ""
return {"input_text": input_text}
def generate_sketch_prompt_node(state: SketchState) -> dict:
"""根据用户输入生成服装草图的图像生成提示词"""
structured_llm = qwen_plus_llm.with_structured_output(SketchPrompt)
messages = [
SystemMessage(content=f"""你是一个专业的服装设计师。
请根据用户输入生成用于AI图像生成的服装草图提示词。
要求:
1. 提示词必须包含clean black and white line drawing only, pure white background, centered composition
2. 提示词应该详细描述服装的廓形、结构、细节
3. 提示词应该适合用于 Stable Diffusion 图像生成模型
4. 提示词应该使用英文,因为图像生成模型对英文理解更好
5. 草图风格必须是黑白线稿,不要添加颜色
6. 提示词数量为 {state.get("sketch_num", 1)}
"""),
HumanMessage(content=state["input_text"]),
]
result = structured_llm.invoke(messages)
prompts = result.prompts
return {
"sketch_prompts": prompts,
}
"""生成服装草图节点"""
async def generate_sketch_img_node(state: SketchState) -> dict:
"""根据生成的提示词,生成服装草图"""
# 如果 sketch_need_prompt_generation=False 且 sketch_prompts 为空,使用模板生成 prompt
# if not state.get("sketch_need_prompt_generation", False) and not state.get("sketch_prompts"):
# input_text = state.get("input_text", "")
# prompts = [build_sketch_template_prompt(input_text)]
# else:
# prompts = state["sketch_prompts"] if state["sketch_prompts"] else [state["input_text"]]
# sketch_img_urls = []
# for prompt in prompts:
# image_url = await generate_sketch_tool.ainvoke({"prompt": prompt})
# sketch_img_urls.append(image_url)
# result_text = f"服装草图生成完成,共生成 {len(sketch_img_urls)} 张图片:\n" + "\n".join(sketch_img_urls)
# return {"sketch_img_urls": sketch_img_urls, "messages": [AIMessage(content=result_text)]}
return {"messages": [AIMessage(content="hello")]}
"""条件分支 判断是否需要生成 prompt"""
def should_generate_prompt(state: SketchState) -> str:
"""条件分支:判断是否需要生成 prompt"""
if state.get("sketch_need_prompt_generation", False):
return "gen_prompt"
else:
return "gen_sketch"
def build_sketch_graph():
workflow = StateGraph(SketchState)
workflow.add_node("gen_sketch", generate_sketch_img_node)
workflow.add_edge(START, "gen_sketch")
workflow.add_edge("gen_sketch", END)
graph = workflow.compile()
return graph
# workflow = StateGraph(SketchState)
# workflow.add_node("extract_input", extract_input_node)
# workflow.add_node("gen_prompt", generate_sketch_prompt_node)
# workflow.add_node("gen_sketch", generate_sketch_img_node)
# # 添加边
# workflow.add_edge(START, "extract_input")
# workflow.add_conditional_edges(
# "extract_input",
# should_generate_prompt,
# {
# "gen_prompt": "gen_prompt",
# "gen_sketch": "gen_sketch",
# },
# )
# workflow.add_edge("gen_prompt", "gen_sketch")
# workflow.add_edge("gen_sketch", END)
# graph = workflow.compile()
# return graph
def build_sketch_template_prompt(input_text: str) -> str:
"""构建 sketch prompt 模板"""
return f"{input_text}, clean black and white line drawing only, pure white background, centered composition, fashion sketch style"
async def main(test_input, sketch_need_prompt_generation=False):
graph = build_sketch_graph()
# 如果不需要 LLM 生成 prompt使用模板
if not sketch_need_prompt_generation:
sketch_prompts = [build_sketch_template_prompt(test_input)]
else:
sketch_prompts = []
result = await graph.ainvoke(
{
"input_text": test_input,
"sketch_prompts": sketch_prompts,
"sketch_need_prompt_generation": sketch_need_prompt_generation,
"role": "",
"gender": "",
"style": "",
}
)
return result
if __name__ == "__main__":
# 测试示例 1: 直接使用模板 prompt默认
test_input = "dress"
result = asyncio.run(main(test_input, sketch_need_prompt_generation=False))
print("=== 使用模板 prompt ===")
print(f"Result: {result}")
# # 测试示例 2: 使用 LLM 生成 prompt
# test_input = "设计一条优雅的A字廓形连衣裙V领设计收腰裙摆到膝盖适合日常穿着"
# result = asyncio.run(main(test_input, sketch_need_prompt_generation=True))
# print("\n=== 使用 LLM 生成 prompt ===")
# print(f"Result: {result}")

View File

@@ -0,0 +1,33 @@
import asyncio
from langchain.tools import tool
from langsmith import uuid7
from pydantic import BaseModel, Field
from app.service.fashion_agent.graph_node.node_tools.generate_image import generate_image
class GenerateImageToolInput(BaseModel):
"""Input schema for the Generate Image Tool."""
prompt: str = Field(description="Description of the desired image, e.g., 'A cozy living room with warm lighting and natural textures.'")
@tool(args_schema=GenerateImageToolInput)
async def generate_sketch_tool(prompt: str) -> str:
"""Generate an image based on the provided prompt."""
bucket_name = "fida-public-bucket"
object_name = f"test/{uuid7()}.png"
image_url = await generate_image(prompt=prompt, bucket_name=bucket_name, object_name=object_name)
return image_url
async def run_test():
result = await generate_sketch_tool.ainvoke({"prompt": "A cozy living room with warm lighting and natural textures."})
return result
if __name__ == "__main__":
result = asyncio.run(run_test())
print(result)

View File

@@ -0,0 +1,69 @@
import asyncio
from typing import Annotated, Required, TypedDict
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
"""定义状态"""
class TrendingState(TypedDict):
messages: Required[Annotated[list[AnyMessage], add_messages]]
input_text: str
"""节点"""
def extract_input_node(state: TrendingState) -> dict:
"""从 messages 中提取用户输入"""
input_text = state["messages"][0].content if state.get("messages") else ""
return {"input_text": input_text}
async def trending_node(state: TrendingState) -> dict:
"""趋势分析节点(占位)"""
input_text = state.get("input_text", "")
# TODO: 接入真实的趋势分析逻辑
result_text = (
f"【趋势分析】\n基于您的输入「{input_text}」,以下是当前服装设计趋势:\n\n"
"1. 极简主义持续流行,黑白灰为主色调\n"
"2. 可持续时尚成为主流,环保面料受青睐\n"
"3. 复古风格回潮90年代元素重新流行\n"
"4. 功能性与美学结合,运动休闲风持续升温"
)
return {"messages": [AIMessage(content=result_text)]}
"""构建图"""
def build_trending_graph():
"""构建趋势分析图"""
workflow = StateGraph(TrendingState)
workflow.add_node("extract_input", extract_input_node)
workflow.add_node("trending", trending_node)
workflow.add_edge(START, "extract_input")
workflow.add_edge("extract_input", "trending")
workflow.add_edge("trending", END)
return workflow.compile()
if __name__ == "__main__":
async def test():
graph = build_trending_graph()
result = await graph.ainvoke(
{
"messages": [HumanMessage(content="女装连衣裙")],
}
)
print(result["messages"][-1].content)
asyncio.run(test())

View File

@@ -0,0 +1,31 @@
import os
from dotenv import load_dotenv
from langchain_qwq import ChatQwen
load_dotenv()
QWEN_API_KEY_INTL = os.environ.get("QWEN_API_KEY_INTL", "")
def build_llm(enable_thinking: bool = False):
llm = ChatQwen(
model="qwen3.6-plus",
timeout=None,
max_retries=2,
enable_thinking=enable_thinking,
streaming=True,
api_key=QWEN_API_KEY_INTL,
)
return llm
qwen_plus_llm = ChatQwen(
model="qwen-plus",
timeout=None,
max_retries=2,
streaming=False,
temperature=0.25,
top_p=0.8,
api_key=QWEN_API_KEY_INTL,
)

View File

@@ -0,0 +1,144 @@
import sys
from pathlib import Path
from typing import Annotated, Required, TypedDict
from deepagents import CompiledSubAgent, create_deep_agent
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_core.messages import AnyMessage, HumanMessage
from langchain_qwq import ChatQwen
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from app.service.fashion_agent.graph_node.design_graph.graph import build_design_graph
from app.service.fashion_agent.graph_node.design_graph.tools import design_tool
from app.service.fashion_agent.graph_node.explorer_graph.tools import explor_tool
from app.service.fashion_agent.graph_node.logo_graph.graph import build_logo_graph
from app.service.fashion_agent.graph_node.node_tools.generate_logo import generate_logo_tool
from app.service.fashion_agent.graph_node.print_graph.graph import build_print_graph
from app.service.fashion_agent.graph_node.print_graph.tools import generate_print_tool
from app.service.fashion_agent.graph_node.sketch_graph.graph import build_sketch_graph
from app.service.fashion_agent.graph_node.sketch_graph.tools import generate_sketch_tool
from app.service.fashion_agent.graph_node.trending_graph.trending_graph import build_trending_graph
from app.service.fashion_agent.graph_node.explorer_graph.graph import build_explorer_graph
from app.service.fashion_agent.init_llm import build_llm
print_graph = build_print_graph()
logo_graph = build_logo_graph()
sketch_graph = build_sketch_graph()
design_graph = build_design_graph()
trending_graph = build_trending_graph()
explorer_graph = build_explorer_graph()
class MainState(TypedDict):
# 消息
messages: Required[Annotated[list[AnyMessage], add_messages]]
# 模块控制
call_design: bool = False
call_print: bool = False
call_logo: bool = False
call_sketch: bool = False
call_design: bool = False
call_trending: bool = False
call_explor: bool = False
# design参数
design_request_data: dict = {}
# 模块需求标志
print_need_prompt_generation: bool = False
sketch_need_prompt_generation: bool = False
# 公共参数
role: str = ""
gender: str = ""
style: str = ""
# print模块结果
print_img_urls: list[str] = []
tools = [explor_tool, generate_logo_tool, generate_print_tool, generate_sketch_tool]
def route_node(state: MainState) -> str:
"""根据标志决定走哪条路径"""
if state.get("call_print"):
return "direct_print"
if state.get("call_logo"):
return "direct_logo"
if state.get("call_sketch"):
return "direct_sketch"
if state.get("call_design"):
return "direct_design"
if state.get("call_trending"):
return "direct_trending"
if state.get("call_explor"):
return "direct_explor"
return "llm_agent"
def build_main_graph(enable_thinking: bool = False):
llm = build_llm(enable_thinking=enable_thinking)
chat_agent = create_agent(
model=llm, tools=tools, state_schema=MainState, system_prompt="你是一个专业的服装设计助手。根据用户需求,调用合适的工具完成任务."
)
"""构建主图"""
workflow = StateGraph(MainState)
# 添加节点
workflow.add_node("llm_agent", chat_agent)
workflow.add_node("direct_print", print_graph)
workflow.add_node("direct_logo", logo_graph)
workflow.add_node("direct_sketch", sketch_graph)
workflow.add_node("direct_design", design_graph)
workflow.add_node("direct_trending", trending_graph)
workflow.add_node("direct_explor", explorer_graph)
# 条件分支
workflow.add_conditional_edges(
START,
route_node,
{
"llm_agent": "llm_agent",
"direct_print": "direct_print",
"direct_logo": "direct_logo",
"direct_sketch": "direct_sketch",
"direct_design": "direct_design",
"direct_trending": "direct_trending",
"direct_explor": "direct_explor",
},
)
# 所有路径都到 END
workflow.add_edge("llm_agent", END)
workflow.add_edge("direct_print", END)
workflow.add_edge("direct_logo", END)
workflow.add_edge("direct_sketch", END)
workflow.add_edge("direct_design", END)
workflow.add_edge("direct_trending", END)
workflow.add_edge("direct_explor", END)
return workflow.compile()
agent = build_main_graph()
if __name__ == "__main__":
import asyncio
async def test_direct():
# 直接调用 sketch跳过 LLM
result = await agent.ainvoke(
{
"messages": [HumanMessage(content="我想设计衬衫,带有猫咪的图案")],
"call_sketch": True,
"sketch_need_prompt_generation": False,
}
)
print("=== 直接调用 sketch ===")
print(result["messages"][-1].content)
asyncio.run(test_direct())

View File

@@ -0,0 +1,132 @@
import json
import logging
import sys
from pathlib import Path
from langgraph.stream import ProtocolEvent, StreamChannel, StreamTransformer
from app.service.fashion_agent.main_agent import build_main_graph
from langgraph.prebuilt import ToolCallTransformer
from typing import AsyncGenerator, TypedDict
from langchain_core.messages import HumanMessage
from app.schemas.fashion_agent import FashionAgentRequest
logger = logging.getLogger()
class CustomTransformer(StreamTransformer):
required_stream_modes = ("custom",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self.log = StreamChannel()
def init(self) -> dict:
return {"custom": self.log}
def process(self, event: ProtocolEvent) -> bool:
if event["method"] == "custom":
self.log.push(event["params"]["data"])
return True
class FashionAgentService:
async def run_stream(self, request: FashionAgentRequest) -> AsyncGenerator[str, None]:
"""流式运行 agent - 使用 v3 projections"""
config = {"configurable": {"user_id": request.user_id}}
agent = build_main_graph(enable_thinking=request.enable_thinking)
state = {
"messages": [HumanMessage(content=request.message)],
"call_print": request.call_print,
"call_logo": request.call_logo,
"call_sketch": request.call_sketch,
"call_design": request.call_design,
"call_trending": request.call_trending,
"call_explor": request.call_explor,
"print_need_prompt_generation": request.print_need_prompt_generation,
"sketch_need_prompt_generation": request.sketch_need_prompt_generation,
"design_request_data": request.design_request_data,
}
stream = await agent.astream_events(state, config=config, version="v3", transformers=[ToolCallTransformer, CustomTransformer])
tool_names = {}
filter_tool_name = ["design_tool"]
async for event in stream:
if event["method"] == "tools":
data = event["params"]["data"]
tool_call_id = data.get("tool_call_id")
# 记录 tool_name
if data.get("event") == "tool-started":
tool_names[tool_call_id] = data.get("tool_name")
# 通过 ID 查找 tool_name
elif data.get("event") == "tool-finished":
tool_name = tool_names.get(tool_call_id, "unknown")
if tool_name in filter_tool_name:
continue
data["tool_name"] = tool_name
response_event = {"event_type": "tool", "data": data}
yield f"data: {json.dumps(response_event, ensure_ascii=False)}\n\n"
elif event["method"] == "custom":
data = event["params"]["data"]
response_event = {"event_type": "tool", "data": data}
yield f"data: {json.dumps(response_event, ensure_ascii=False)}\n\n"
elif event["method"] == "messages":
data = event["params"]["data"][0]
if not isinstance(data, dict):
continue
if data.get("event") != "content-block-delta":
continue
block = data.get("delta") or {}
if block.get("type") == "text-delta":
response_event = {"event_type": "messages", "data": {"event": data["event"]} | block}
yield f"data: {json.dumps(response_event, ensure_ascii=False)}\n\n"
elif block.get("type") == "reasoning-delta":
response_event = {"event_type": "messages", "data": {"event": data["event"]} | block}
yield f"data: {json.dumps(response_event, ensure_ascii=False)}\n\n"
else:
pass
# print(f"----------------{event}")
response_event = {"event_type": "done"}
yield f"data: {response_event}"
if __name__ == "__main__":
import asyncio
async def test_stream():
"""测试流式调用"""
with open("app/service/fashion_agent/graph_node/design_graph/design_request_data.json", encoding="utf-8") as f:
request_data = json.load(f)
service = FashionAgentService()
print("=" * 50)
print("测试流式输出")
print("=" * 50)
request = FashionAgentRequest(
message="生成一张草莓图案",
call_print=True,
# print_need_prompt_generation=False,
# call_sketch=True,
# sketch_need_prompt_generation=False,
# call_logo=True,
# call_explor=True,
# call_design=True,
# design_request_data=request_data,
)
async for chunk in service.run_stream(request):
print(chunk, end="")
# 运行测试
asyncio.run(test_stream())

View File

@@ -92,7 +92,7 @@ if __name__ == "__main__":
# url = "aida-users/89/sketchboard/female/Dress/e6724ab7-8d3f-4677-abe0-c3e42ab7af85.jpeg"
# url = "aida-users/87/print/956614a2-7e75-4fbe-9ed0-c1831e37a2c9-4-87.png"
# url = "aida-users/89/single_logo/123-89.png"
url = "aida-collection-element/26293/Sketchboard/b503d482-3334-46e7-9dee-44e380fb4294.png"
url = "aida-users/agent/logo/019e91c4-7b1f-74e2-b716-d652713101cd.png"
# url = "aida-collection-element/12148/Sketchboard/95ea577b-305b-4a62-b30a-39c0dd3ddb3f.png"
read_type = "2"

View File

@@ -22,6 +22,7 @@ dependencies = [
"langchain-community>=0.4.1",
"langchain-qwq>=0.3.5",
"langgraph>=1.0.5",
"langgraph-api>=0.4.28",
"langgraph-cli[inmem,redis]<=0.4.26",
"load>=1.0.14",
"load-dotenv>=0.1.0",

215
uv.lock generated
View File

@@ -83,7 +83,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.14.0"
version = "3.14.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -95,26 +95,26 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" }
sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/97/2b6889bfb6b6847520d50d95eb8c4307a45e28aaca39faf4a9454b3d1b2f/aiohttp-3.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b29518c9c2ec7e373e68259206a137c7f4f5439c58baaec4b5ab3ab799850a4e", size = 750194, upload-time = "2026-06-01T19:37:48.164Z" },
{ url = "https://files.pythonhosted.org/packages/21/e2/62634b7fff918ed98c3c6b2f0e70d520f7f28846cb412d451b04354c6459/aiohttp-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbec68ce61b64cb73cab4d33df9433427b1713c8bcccb181dce695c1b6f8e87c", size = 506966, upload-time = "2026-06-01T19:37:50.014Z" },
{ url = "https://files.pythonhosted.org/packages/dd/fb/5ce075150828c797a5106f1c2fb26034e709d4289b9d2bf8b07f1e59fac6/aiohttp-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cdf534aa455593e589302990c5097aa5c92c06c4262a20da22934f9186a5fff", size = 507527, upload-time = "2026-06-01T19:37:51.96Z" },
{ url = "https://files.pythonhosted.org/packages/01/d5/405a0ae4e6b081754a3609c1c97c63a950e000a2def16046f1e736933a0e/aiohttp-3.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb6c657104393b5fbff01a5f59b2023db74058a8077d94475d6c25d03882a108", size = 1762420, upload-time = "2026-06-01T19:37:53.839Z" },
{ url = "https://files.pythonhosted.org/packages/ae/1d/e05a7c896b15a6bc6fb8fc5319eb437861c2c49c34559ef928add6590315/aiohttp-3.14.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:46fbbec4e4fab7428d4396a3823f9320e4560aa3113b89eeebce712c27c9ed5a", size = 1733672, upload-time = "2026-06-01T19:37:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/cc/22/a72f7c459e195fa41bf4f7abd1f925b91fe91f8097e51c654229ba144a33/aiohttp-3.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c2c7e05dd5335b298085abf45ddf98673934c3ee1c083d0b9ea13d4186ad500", size = 1805064, upload-time = "2026-06-01T19:37:57.931Z" },
{ url = "https://files.pythonhosted.org/packages/80/50/e85bdaba0be59ca4838005ebfef4048fcdd5f35a02b07057a9a123394440/aiohttp-3.14.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c7139100fbaae76515b73051d8f0aa3a3ff02e415eec8a8eee8e2223d9ba955", size = 1902125, upload-time = "2026-06-01T19:38:00.225Z" },
{ url = "https://files.pythonhosted.org/packages/19/d8/51de5c6b971c27bb1ef620293b8d1ca611ec78736b34b3f6ccf68e4c8785/aiohttp-3.14.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78d6f9286a629ce52728430afe18f8ed2b6c39a1fddb3802d7244b9983910ad2", size = 1783112, upload-time = "2026-06-01T19:38:02.641Z" },
{ url = "https://files.pythonhosted.org/packages/73/ae/b4402bfde77e43dfb1b6ccff83c7b7ab63ed06b50c4754f0c5423fb374fe/aiohttp-3.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3c3e12cdaeb92d7dcf13db00e9f6b1956b910e47256e696df1cfa946d02159", size = 1586356, upload-time = "2026-06-01T19:38:04.637Z" },
{ url = "https://files.pythonhosted.org/packages/bc/05/750a3265ca4dc54a460bd0cb1121a8f2ce9171fce4a135fb47ea7fd594d2/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d6a998191f5ebe3b8c28463ff72bc030250008b3193c402464efadd08b5ca02", size = 1723119, upload-time = "2026-06-01T19:38:06.713Z" },
{ url = "https://files.pythonhosted.org/packages/37/01/8c0812c50b3b1b1c37b323bf170d6be8847a8f234060485b7d1e71953f60/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0fc2b75ae8d169d853be2862d960be8550da6c5c65711d5476407eb3fdb006bd", size = 1757216, upload-time = "2026-06-01T19:38:08.736Z" },
{ url = "https://files.pythonhosted.org/packages/47/2a/50fb98028a26887cbe48dcc1df92a90825615bc73b5584301304090cded8/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:16eee56bcc72d04600bc56c1759982c2385ec0b41d3fd3521f836bf64a0957ef", size = 1770500, upload-time = "2026-06-01T19:38:11.111Z" },
{ url = "https://files.pythonhosted.org/packages/bd/32/0ffd598a2fa2b9a423daf242e700cfdabda35d6e602394ad9ae58972c1c7/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5a2e7ca615c3ddc15b82687e05a624e5f5cba3f1d6c20cb81172d70ea498451e", size = 1576224, upload-time = "2026-06-01T19:38:13.391Z" },
{ url = "https://files.pythonhosted.org/packages/0b/f9/b9fc381dd9b66afb33f2634c40e229d106467be0afcabe79648631ab6712/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0b7b8bbbec3ce9467ee0ebe334622fd90624f593edd3136c567811453fc4fae", size = 1794252, upload-time = "2026-06-01T19:38:15.498Z" },
{ url = "https://files.pythonhosted.org/packages/a8/fb/05d9214c975f23225a8cd5c439325e338c7c377b315480ef3871db51f54e/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ba10966d4f03dd96a14365be4b8e37c327c76f11c3ca867116966cdd9f98066", size = 1760193, upload-time = "2026-06-01T19:38:17.624Z" },
{ url = "https://files.pythonhosted.org/packages/d9/4b/02992fc4fb9e1b6673ee3f888a8e587a6447afda1f6f4aca776c148c2876/aiohttp-3.14.0-cp312-cp312-win32.whl", hash = "sha256:101df7779c80c0636014a6b2c6642acd3efb5b355d48347c9d7dfb720aee9430", size = 448650, upload-time = "2026-06-01T19:38:19.545Z" },
{ url = "https://files.pythonhosted.org/packages/39/e9/246532214c3abda518477cbaaf16d420295ad8effa5233844cbb38f299ab/aiohttp-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:b0a5747586d4467efd1f932710b269131c9717a872dce082cd92a00c1c13123a", size = 476145, upload-time = "2026-06-01T19:38:21.505Z" },
{ url = "https://files.pythonhosted.org/packages/2b/c3/63f8c20090048915711598b0adf475b149216d736157961de06480a45b15/aiohttp-3.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:5f1c5be60add78fabb4aacd13c5a348ae79d2fcbfc7fa78da8f1eb192273b370", size = 444250, upload-time = "2026-06-01T19:38:24.027Z" },
{ url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" },
{ url = "https://files.pythonhosted.org/packages/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" },
{ url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" },
{ url = "https://files.pythonhosted.org/packages/8a/72/a60607cb849faa8af8a356c9329ea2eb6f395d49e82cc82ccba1fd8deb8f/aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2", size = 1766854, upload-time = "2026-06-07T21:06:45.391Z" },
{ url = "https://files.pythonhosted.org/packages/b5/d3/d9fe1c9ec7557ab4d0d82bebaa728c6418f0b93295ec2f4ab015f7710cc7/aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a", size = 1740884, upload-time = "2026-06-07T21:06:47.413Z" },
{ url = "https://files.pythonhosted.org/packages/c1/dc/f2cecfaf9337ba3e63f181500814ff502aa3d00d9c7ec93a9d23d10a27b2/aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264", size = 1810034, upload-time = "2026-06-07T21:06:50.165Z" },
{ url = "https://files.pythonhosted.org/packages/66/d7/2ff65c5e65c0d7476daf7e15c032e0805e36811185b9623e3238ad6c763e/aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842", size = 1904054, upload-time = "2026-06-07T21:06:52.035Z" },
{ url = "https://files.pythonhosted.org/packages/20/9c/d445818389df371f56d141d881153ba23183c4735a03f7356ffb43f7757d/aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c", size = 1790278, upload-time = "2026-06-07T21:06:54.049Z" },
{ url = "https://files.pythonhosted.org/packages/4d/aa/bf04cb4d865fc6101c2229a294ad744973b72e513fdc5a6b791e6983d72a/aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95", size = 1591795, upload-time = "2026-06-07T21:06:55.911Z" },
{ url = "https://files.pythonhosted.org/packages/dc/b4/4dac0038960427ba832f6609dfb4ea5437d7fd80c72001b9e48f834f428b/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199", size = 1728397, upload-time = "2026-06-07T21:06:57.777Z" },
{ url = "https://files.pythonhosted.org/packages/2b/f9/7cd4e8ad7aa3b75f17d56bb5498dd604a93d4e6eece822ba0568c413fff0/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817", size = 1766504, upload-time = "2026-06-07T21:07:00.009Z" },
{ url = "https://files.pythonhosted.org/packages/f9/df/fc01d9fcad0f73fed3f3d361f1f94f975947b50dff82919f6dc2bf4316cc/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a", size = 1777806, upload-time = "2026-06-07T21:07:02.064Z" },
{ url = "https://files.pythonhosted.org/packages/41/09/47e2d090bddcc8fb4ccb4c314aadc32d7c5d9bb55f50f6ad1c92fc15d501/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4", size = 1580707, upload-time = "2026-06-07T21:07:03.942Z" },
{ url = "https://files.pythonhosted.org/packages/3d/36/f1a4ce904ae0b6930cfe9afc96d0896f7ec1a620c400405d63783bb95a9c/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087", size = 1798121, upload-time = "2026-06-07T21:07:05.987Z" },
{ url = "https://files.pythonhosted.org/packages/70/0a/e0075ce9ca0279ee1d4f0c0b85f54fea02ebc83c3007651a72bece658fec/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3", size = 1767580, upload-time = "2026-06-07T21:07:07.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/a0c0a8f327a9c52095cdd8e312391b00d3ed64ab6c72bb5c33d8ec251cf7/aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4", size = 452771, upload-time = "2026-06-07T21:07:09.669Z" },
{ url = "https://files.pythonhosted.org/packages/df/d9/ea367c75f16ac9c6cdc8febb25e8318fa21a2b1bc8d6514d4b2d890bface/aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271", size = 479873, upload-time = "2026-06-07T21:07:11.538Z" },
{ url = "https://files.pythonhosted.org/packages/03/64/8d96784a7851156db8a4c6c3f6f91042fdf39fb15a4cc38c8b3c14833c45/aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847", size = 448073, upload-time = "2026-06-07T21:07:13.637Z" },
]
[[package]]
@@ -323,7 +323,7 @@ wheels = [
[[package]]
name = "anthropic"
version = "0.105.2"
version = "0.107.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -335,9 +335,9 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/46/46/47581b8c689c743ceabf6a0f9ff48472160900ce802d26c0fb50423997b3/anthropic-0.105.2.tar.gz", hash = "sha256:0e26b90841c2dced7cc6e98d21d5517d0be33f1876b8e779f478202e28bcaa07", size = 853789, upload-time = "2026-05-29T00:21:14.104Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b1/f1/c6076a92e0bf6b0dfa126e213b3f9e8a510acd73567953210713aae6c256/anthropic-0.107.1.tar.gz", hash = "sha256:8e7169a6ab57fb806b778d9af018c867bad688144efec8969cdb4c5ccecd6670", size = 856312, upload-time = "2026-06-07T17:18:57.358Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/75/be0c357e33a5a56c8f9db5b4212f886138d2bf59c0952d858f6b75d710ef/anthropic-0.105.2-py3-none-any.whl", hash = "sha256:e53ed5f6bf36fb1ecb9b25d8634cfd30e02fab9fb3374a0c2d5c585874757230", size = 837507, upload-time = "2026-05-29T00:21:15.528Z" },
{ url = "https://files.pythonhosted.org/packages/86/0e/71432f0777a263701955a23ebcc6650485c2753be9afbce2a6a8d72526e3/anthropic-0.107.1-py3-none-any.whl", hash = "sha256:b74338d08000ba105dfc8adae29af3713ece845a4bffec9986a20697e087c7b3", size = 838729, upload-time = "2026-06-07T17:18:58.729Z" },
]
[[package]]
@@ -465,15 +465,15 @@ wheels = [
[[package]]
name = "beautifulsoup4"
version = "4.14.3"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "soupsieve" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
{ url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" },
]
[[package]]
@@ -919,7 +919,7 @@ wheels = [
[[package]]
name = "dashscope"
version = "1.25.20"
version = "1.25.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -931,7 +931,7 @@ dependencies = [
{ name = "websocket-client" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/49/194907e6d4a8a159445c2f3234568f5d1816df46c31b65a4d4ecc569a4ef/dashscope-1.25.20-py3-none-any.whl", hash = "sha256:1d1257f205433f85ddc44a3193e6a99d1372071e34a17d284c2c5c421d44a7e3", size = 1477164, upload-time = "2026-06-01T10:23:21.234Z" },
{ url = "https://files.pythonhosted.org/packages/0f/22/a174358052f43aa6ad5183eeda44da5cc3ffac472f220dfba4b67f296267/dashscope-1.25.21-py3-none-any.whl", hash = "sha256:a730b3e9e41fb4261ab89192f667964aee2057779d87c26beaa9889aa07afbb9", size = 1479509, upload-time = "2026-06-04T08:13:27.256Z" },
]
[[package]]
@@ -945,7 +945,7 @@ wheels = [
[[package]]
name = "deepagents"
version = "0.6.7"
version = "0.6.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain" },
@@ -955,9 +955,9 @@ dependencies = [
{ name = "langsmith" },
{ name = "wcmatch" },
]
sdist = { url = "https://files.pythonhosted.org/packages/51/bb/bb837a2c51631fe9d7eedf6aca7629ddca6336831801e75efcd2f5fa9c27/deepagents-0.6.7.tar.gz", hash = "sha256:af7b5857b28e29a847a4ced4cc7aaa809d33d42107696b1ca5d978d17e96b831", size = 194236, upload-time = "2026-05-30T04:42:14.591Z" }
sdist = { url = "https://files.pythonhosted.org/packages/97/77/e3b7efd9bff9cd101c085a5a3bf74180c13ab6c41a96f725cd1cb1bf53e8/deepagents-0.6.8.tar.gz", hash = "sha256:70cdd4da920cc420a8a0f729792ec559688bbbff39f7ab1508110cce9f901c06", size = 196927, upload-time = "2026-06-03T17:08:36.724Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/25/3a7f23d04778ccb95542f1b1ed39826290916221cc8203d0fb8b26c3edc1/deepagents-0.6.7-py3-none-any.whl", hash = "sha256:3518c1e5f4b9f6588ba39912b668b42b5c864b99a638e94c166d1c7176c7388e", size = 218740, upload-time = "2026-05-30T04:42:13.225Z" },
{ url = "https://files.pythonhosted.org/packages/8e/19/1b7b76e958ac7f4e40886edc70f67aff4d7188770ab68105c9c48cbeb769/deepagents-0.6.8-py3-none-any.whl", hash = "sha256:087bdc1458202a3436854cf180f7ec059d07d2114a6c232819e9ad6533a5174a", size = 221469, upload-time = "2026-06-03T17:08:35.133Z" },
]
[[package]]
@@ -980,16 +980,16 @@ wheels = [
[[package]]
name = "django"
version = "6.0.5"
version = "6.0.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5e/f1/bf85f0d29ef76abf901f193fe8fef4769d3da7794197832bc30151c071d8/django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269", size = 10924131, upload-time = "2026-05-05T13:54:39.329Z" }
sdist = { url = "https://files.pythonhosted.org/packages/78/29/ac41e16097af67066d97a7d5775c5d8e7efc5d0284f6b0a159e07b9adb92/django-6.0.6.tar.gz", hash = "sha256:ad03916ba59523d781ae5c3f631960c23d69a9d9c43cecda52fc23b47e953713", size = 10905525, upload-time = "2026-06-03T13:02:46.503Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/5b/1328f8b84fce040c404f76822bf8c57d254e368e8cbd8bd67ec2b26d75f5/django-6.0.5-py3-none-any.whl", hash = "sha256:9d58a7cb49244e74c8e161d5e403a46d6209f1009ba40f5a66d6aa0d0786a8f0", size = 8368680, upload-time = "2026-05-05T13:54:33.532Z" },
{ url = "https://files.pythonhosted.org/packages/eb/50/23f9dc45483419a3cc2085b498b25adfbf10642b2941c73e6d2dfaffc9ab/django-6.0.6-py3-none-any.whl", hash = "sha256:25148b1194c47c2e685e5f5e9c5d59c78b075dfd282cb9618861ba6c1708f4d2", size = 8373354, upload-time = "2026-06-03T13:02:41.72Z" },
]
[[package]]
@@ -1147,11 +1147,11 @@ wheels = [
[[package]]
name = "filelock"
version = "3.29.0"
version = "3.29.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
{ url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" },
]
[[package]]
@@ -1305,7 +1305,7 @@ requests = [
[[package]]
name = "google-genai"
version = "2.7.0"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -1319,9 +1319,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/7b/6eb3b3d545b6bb4c374acba1ccf91b0f33b605e551536a6243cfcef2f07f/google_genai-2.7.0.tar.gz", hash = "sha256:3c6f32f5ced9877ededd1b384b5e5b7f09c20046ec3390b662b16d8cd1882ac5", size = 555853, upload-time = "2026-05-28T15:39:24.58Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5b/52/0244e310812f3063d09d60b30ae29ab7df9343bd005744cd5eeaa6ba39b4/google_genai-2.8.0.tar.gz", hash = "sha256:37a9b3cb127d763e7f4ca47452ae3562c87728773bd1b149f7b559c239da2bc1", size = 564955, upload-time = "2026-06-03T22:55:38.397Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/dd/7a8be39e9d698e80e9db796514efbc6083dbd787bdb9a101e8ba47248e5e/google_genai-2.7.0-py3-none-any.whl", hash = "sha256:21cac381e09a869151706aba797b6a4f96cfe92c484e13204d092caee7ff11cb", size = 822545, upload-time = "2026-05-28T15:39:22.907Z" },
{ url = "https://files.pythonhosted.org/packages/e2/de/747ad1aa49e902da9a4699081c282a1ed8ceed3b4d295fd99a6d286e09e4/google_genai-2.8.0-py3-none-any.whl", hash = "sha256:4da0a223a100f4b37f609a68b835e3326ab0fa313314dc0fd9d34e76ee293844", size = 832497, upload-time = "2026-06-03T22:55:36.598Z" },
]
[[package]]
@@ -1381,18 +1381,18 @@ wheels = [
[[package]]
name = "hf-xet"
version = "1.5.0"
version = "1.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" }
sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" },
{ url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" },
{ url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" },
{ url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" },
{ url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" },
{ url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" },
{ url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" },
{ url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" },
{ url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" },
{ url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" },
{ url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" },
{ url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" },
{ url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" },
{ url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" },
{ url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" },
{ url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" },
]
[[package]]
@@ -1469,11 +1469,11 @@ wheels = [
[[package]]
name = "idna"
version = "3.17"
version = "3.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" },
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
]
[[package]]
@@ -1698,16 +1698,16 @@ wheels = [
[[package]]
name = "langchain"
version = "1.3.2"
version = "1.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "langgraph" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d5/d0/c7f9d3d26c0e3f8bb146c6d707ee0fc1d30d8da65a59626e8a580085e929/langchain-1.3.2.tar.gz", hash = "sha256:ffd5f204a46b5fa1a38bf89ba3b45ca0902c02d18fa7d2a2eaeaeb1f5bf19d0a", size = 600598, upload-time = "2026-05-26T18:17:57.715Z" }
sdist = { url = "https://files.pythonhosted.org/packages/36/3f/034eb6cbef90bfccc89b7f8ed0c1d853dc9cb0bea17c7a269534c647ba3a/langchain-1.3.4.tar.gz", hash = "sha256:d6e0654c22848925534f5c0a706f9be481bb09a619ec60a738fbd1e5502e457a", size = 606617, upload-time = "2026-06-02T20:04:49.411Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/82/a54edcd1c48163de5642eb10fa2cb58b13a8889c659964f63f0306b58b1e/langchain-1.3.2-py3-none-any.whl", hash = "sha256:900f6b3f4ee08b9ba3cdbe667dbf42525bd6f66a4a07a7f1db26262673e41ed6", size = 121225, upload-time = "2026-05-26T18:17:56.075Z" },
{ url = "https://files.pythonhosted.org/packages/a5/29/9ffe99c7dc4891a0215ec59c423bea320f943c08a231bc5bae392a438a83/langchain-1.3.4-py3-none-any.whl", hash = "sha256:e51b05ab23d056bc6bf2d97d8c694fb92d6d5765126fef74565d007c27581672", size = 125286, upload-time = "2026-06-02T20:04:48.13Z" },
]
[[package]]
@@ -1766,7 +1766,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.4.0"
version = "1.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -1779,9 +1779,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/59/de/679a53472c25860837e32c0442c962fa86e95317a36460e2c9d5c91b17c2/langchain_core-1.4.0.tar.gz", hash = "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", size = 920260, upload-time = "2026-05-11T18:42:35.919Z" }
sdist = { url = "https://files.pythonhosted.org/packages/de/13/446580dc9f26e4e524d57f727a9007b4c2484decd2c00269b7fd4f51326d/langchain_core-1.4.2.tar.gz", hash = "sha256:242abe763db71de05fe0d7ecff03f9cc6022fbceba8be15902fb89e35b7292f9", size = 935103, upload-time = "2026-06-08T18:19:41.611Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/1a/86c38c27b81913a1c6c12448cab55defb5a1097c7dc9a4cea83f55477a2d/langchain_core-1.4.0-py3-none-any.whl", hash = "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c", size = 548120, upload-time = "2026-05-11T18:42:33.992Z" },
{ url = "https://files.pythonhosted.org/packages/d0/2c/92a6a6f5af07f1c347021d81aea307d405ed9d34a4f8ea6b78cfa3c3b189/langchain_core-1.4.2-py3-none-any.whl", hash = "sha256:a2906d339514e02a46d6c0888021dd2651ed5acc661a1f546fe33e1453adfcb9", size = 550103, upload-time = "2026-06-08T18:19:40.197Z" },
]
[[package]]
@@ -1861,7 +1861,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.2"
version = "1.2.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
@@ -1871,9 +1871,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/5a/ffc12434ee8aecab830d58b4d204ddea45073eae7639c963310f671a5bf5/langgraph-1.2.2.tar.gz", hash = "sha256:f54a98458976b3ff0774683867df125fb52d8dbedeb2441d0b0656a51331cee5", size = 695730, upload-time = "2026-05-26T18:07:28.49Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/43/dac5a2621c1e57f8eb7f0703f6f6fe34a5caf62f8f0fb4d2bb395bb454ea/langgraph-1.2.4.tar.gz", hash = "sha256:5df076973a2d23efb13eceb279d1e5b46feebcbbeded0a86a2ef669abd9e4399", size = 720374, upload-time = "2026-06-02T17:07:37.347Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/9b/b08d578bba73e25351152dfd3d6d21e81210a5fff1b6f26e56f33197c8f5/langgraph-1.2.2-py3-none-any.whl", hash = "sha256:0a851bf4ba5939c5474a2fd57e6b439b5315283e254e42943bd392c2d71a5e03", size = 236376, upload-time = "2026-05-26T18:07:26.577Z" },
{ url = "https://files.pythonhosted.org/packages/48/9e/31ca236104966d7bb14ea9e93cfd73350aea8c41008ddf057b65794ed10d/langgraph-1.2.4-py3-none-any.whl", hash = "sha256:ffe3e1e31dce28907640f82525858470f293506d2b272d07ea3b3ce97974b067", size = 245402, upload-time = "2026-06-02T17:07:35.977Z" },
]
[[package]]
@@ -1971,20 +1971,23 @@ wheels = [
[[package]]
name = "langgraph-sdk"
version = "0.3.15"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "langchain-core" },
{ name = "langchain-protocol" },
{ name = "orjson" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/af/cdd4d6f3c05b3c1112ed3f12ef830faf15951b21d22cbc622a4becbbe25c/langgraph_sdk-0.3.15.tar.gz", hash = "sha256:29e805003d2c6e296823dd71992610976fd0428cefaa8b3304fd91f2247037de", size = 201924, upload-time = "2026-05-22T16:54:27.678Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b4/2b/bd8ac26d4e97f6df88ef05ce5b6a38945a3903e1025d926f4752aa88aa97/langgraph_sdk-0.4.2.tar.gz", hash = "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738", size = 348327, upload-time = "2026-06-01T17:51:19.856Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/a5/0196d9c05749c25bc198e4909d68c998bc3120297e14944921baf2f4c384/langgraph_sdk-0.3.15-py3-none-any.whl", hash = "sha256:3838773acf7456d158165385d49f48f1e856f28b56ccd99ea139a8f27004815d", size = 98166, upload-time = "2026-05-22T16:54:26.013Z" },
{ url = "https://files.pythonhosted.org/packages/a0/05/aac507337cceae773c2cc9ab91eb6301963af7aeeb55b4217a00e15aff17/langgraph_sdk-0.4.2-py3-none-any.whl", hash = "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd", size = 160521, upload-time = "2026-06-01T17:51:18.849Z" },
]
[[package]]
name = "langsmith"
version = "0.8.8"
version = "0.8.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -1998,9 +2001,9 @@ dependencies = [
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/93/28df12b3b3c776077983b92f1299c623592b5999695af2a755fb90ff048b/langsmith-0.8.8.tar.gz", hash = "sha256:9d00e54f54d833c1914003527ff03ad0364741034330da72f0adbeaba852b6cf", size = 4468035, upload-time = "2026-05-31T22:14:57.698Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ea/0d/082410ece26ff9f3ed4f87b014a8675be47cbd7d65f06b922045dfc21c47/langsmith-0.8.11.tar.gz", hash = "sha256:d9b3496f8f7ca63f4f2d1dfd368afd6c527923fff2ce4026c82ce85f37db3965", size = 4495842, upload-time = "2026-06-08T22:54:44.395Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/71/94a8f2b573278a0b0b7dfd37663c0ddd36867f9e2bba69addd183de0cd56/langsmith-0.8.8-py3-none-any.whl", hash = "sha256:9d60d724c0d187c036e184b3ffdf9fa5c6822aa0bb88144a5fb898e79be645af", size = 402712, upload-time = "2026-05-31T22:14:55.908Z" },
{ url = "https://files.pythonhosted.org/packages/b4/65/f9c9dc19b21a9076286fafdb0ab732c9019ddf71aa7e7d720a830a98fe2a/langsmith-0.8.11-py3-none-any.whl", hash = "sha256:08aa5e84b00703ecc11dbeafda78d84b92da4e8c6114e0be9b59df9e71afc59b", size = 478985, upload-time = "2026-06-08T22:54:42.349Z" },
]
[[package]]
@@ -2467,7 +2470,7 @@ wheels = [
[[package]]
name = "openai"
version = "2.40.0"
version = "2.41.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -2479,9 +2482,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f9/9f/136562ec6c3b1a50fe06eb0bb34ed21f0d7426ec0140e5cc43ac785b69a5/openai-2.40.0.tar.gz", hash = "sha256:9a756f91f274a24ad6026cbcb2042fd356c8d4a10e8f347b08d34465e585f7a2", size = 781177, upload-time = "2026-06-01T21:48:23.878Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3c/a6/5815fe2e2aca74b36c650d1bd43b69827cee568073d0d2d9b6fc5aaac80c/openai-2.41.0.tar.gz", hash = "sha256:db5c362acd6604b84f076abbefa66826ea4b46ecba2954ed866e6a149a1352c0", size = 783525, upload-time = "2026-06-03T22:39:40.719Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f6/46/180e14be801a75bc13f234cb1b594b232adeb9c84e60a9ab1832e8333591/openai-2.40.0-py3-none-any.whl", hash = "sha256:2b205637ff214477f9ce9ab035e9f494db0e3fa8f1e599008953735fbf6ff1ff", size = 1350935, upload-time = "2026-06-01T21:48:21.462Z" },
{ url = "https://files.pythonhosted.org/packages/be/51/d82bb424e8aa372190c5233253a2ceb399a778747d18b42cff487411e663/openai-2.41.0-py3-none-any.whl", hash = "sha256:20cc7952e8501c7e5773dd2ef7be437bae9cb549044902e1041a83a54516e375", size = 1353378, upload-time = "2026-06-03T22:39:38.964Z" },
]
[[package]]
@@ -3136,11 +3139,11 @@ wheels = [
[[package]]
name = "python-multipart"
version = "0.0.30"
version = "0.0.32"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4b/82/c8cd43a6e0719bf5a3b034f6726dd701f75829c08944c83d4b95d02ed0e8/python_multipart-0.0.30.tar.gz", hash = "sha256:0edfe0475c1f46ddd3ff7785a626f6118af32bdcf359bb21260367313bb32118", size = 46316, upload-time = "2026-05-31T19:24:55.198Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/fd/0318007beb234790993d3ec5afd051d1dbceb733e81e3afe2b981ece3f37/python_multipart-0.0.30-py3-none-any.whl", hash = "sha256:830964def8c90607ac5daa00514e3987815865713ade8d20febc9177ac0c3c5b", size = 29730, upload-time = "2026-05-31T19:24:53.814Z" },
{ url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
]
[[package]]
@@ -3306,16 +3309,16 @@ wheels = [
[[package]]
name = "rich-toolkit"
version = "0.19.10"
version = "0.20.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fa/02/32217f3657ae91a0ea7cf1d74ade78f44352f830d00c468f753ddb3d4980/rich_toolkit-0.19.10.tar.gz", hash = "sha256:dc2e8c515ef9fbb4894e62bd41a2d2960dd7c2f505b5084894604d5ccfee3f09", size = 198167, upload-time = "2026-05-21T10:11:42.397Z" }
sdist = { url = "https://files.pythonhosted.org/packages/29/63/3e427c62f1992945c997d4ec31e2fcb37d26aadbe5aa44ae5b29f7f64d26/rich_toolkit-0.20.1.tar.gz", hash = "sha256:c7336ae281f435c785acecaedc4b71d4b663dc73d9c8079fea96372527e822a4", size = 203473, upload-time = "2026-06-05T08:56:57.679Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/35/84/a005adcb4d1e6846ba3d62768090c3b943e3f6d8dc5c47af64f33584c4a7/rich_toolkit-0.19.10-py3-none-any.whl", hash = "sha256:93a41f67a09aefe90379f1729495c2fee9ccbcc8cfda48e2ca2ae54a995e32b1", size = 33907, upload-time = "2026-05-21T10:11:43.578Z" },
{ url = "https://files.pythonhosted.org/packages/00/88/309f07d08155da2ba1d5ceb42d270fb42fbe34a807684543e3ffc10fe713/rich_toolkit-0.20.1-py3-none-any.whl", hash = "sha256:2a6d5f8e15759b9eba5a9ee63da10b275359ead20e5a0fc92bd5b4dbae8ce4bf", size = 35525, upload-time = "2026-06-05T08:56:58.586Z" },
]
[[package]]
@@ -3439,15 +3442,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.61.1"
version = "2.62.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/3b/4bc6b348bbd331daa14d4babe9f2b99bc854f4da41560eefb9488d78481d/sentry_sdk-2.61.1.tar.gz", hash = "sha256:9c6adccb3feefa9ba032c8d295ca477575c2f11896046a2b0ad686c47c4af555", size = 459429, upload-time = "2026-06-01T07:24:18.875Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/5d/a343201726150e05f2036eeb6e493e2e2f8bf8a66f5aa70f2f4ac96f9ca3/sentry_sdk-2.62.0.tar.gz", hash = "sha256:3c870b9f50d9fd15b58c817dbde1c7cfaa9fe3f05df0a4c6edd5571cb82f5491", size = 463986, upload-time = "2026-06-08T13:23:49.223Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/54/c9218db183846e08efaf68534889ef42e499dde432778881104a42f7071b/sentry_sdk-2.61.1-py3-none-any.whl", hash = "sha256:fa36eaf4b8ad708f718500d4bdcc1532637526a22beb874d88cbc0a46458b5ae", size = 483735, upload-time = "2026-06-01T07:24:17.027Z" },
{ url = "https://files.pythonhosted.org/packages/3d/07/05440381627877aae223fd68f330df9b9fc6641d08bf65328b55235617a2/sentry_sdk-2.62.0-py3-none-any.whl", hash = "sha256:27f61d13a86c3c1648dec666dd5a64f79772dd6a84b446f11866601ecab24f6f", size = 490586, upload-time = "2026-06-08T13:23:47.486Z" },
]
[[package]]
@@ -3705,14 +3708,14 @@ wheels = [
[[package]]
name = "tqdm"
version = "4.67.3"
version = "4.68.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
sdist = { url = "https://files.pythonhosted.org/packages/06/b3/36c8ecf72e8925200671613332db156d84b99b3aee742a41c1938ebb0808/tqdm-4.68.1.tar.gz", hash = "sha256:fc163d96b287bd031e1aa24421ce4411b25559bd0a1be4fe649bdaa4d2c02bf5", size = 171236, upload-time = "2026-06-05T17:23:15.267Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
{ url = "https://files.pythonhosted.org/packages/47/aa/218a0eb34de1f753c83e4d0d1c8e7c4cef27f20dcb8342e024f63a80dc86/tqdm-4.68.1-py3-none-any.whl", hash = "sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8", size = 78354, upload-time = "2026-06-05T17:23:13.654Z" },
]
[[package]]
@@ -3738,6 +3741,7 @@ dependencies = [
{ name = "langchain-community" },
{ name = "langchain-qwq" },
{ name = "langgraph" },
{ name = "langgraph-api" },
{ name = "langgraph-cli", extra = ["inmem"] },
{ name = "load" },
{ name = "load-dotenv" },
@@ -3794,6 +3798,7 @@ requires-dist = [
{ name = "langchain-community", specifier = ">=0.4.1" },
{ name = "langchain-qwq", specifier = ">=0.3.5" },
{ name = "langgraph", specifier = ">=1.0.5" },
{ name = "langgraph-api", specifier = ">=0.4.28" },
{ name = "langgraph-cli", extras = ["inmem", "redis"], specifier = "<=0.4.26" },
{ name = "load", specifier = ">=1.0.14" },
{ name = "load-dotenv", specifier = ">=0.1.0" },
@@ -3877,7 +3882,7 @@ wheels = [
[[package]]
name = "typer"
version = "0.26.5"
version = "0.26.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
@@ -3885,9 +3890,9 @@ dependencies = [
{ name = "rich" },
{ name = "shellingham" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/1a/2cf40b65b1d9c254fe5814bb0519f9b8f2ac38059df0810f9b866300c04a/typer-0.26.5.tar.gz", hash = "sha256:9b9b39e35c3afc9e1e51a06f21155246e457c0911279b09b35d8210ca74b935c", size = 201494, upload-time = "2026-06-01T14:42:49.744Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/d6/baac76fc04a6532883de3d8722c7f921dae94d10965e7ffba9e38e42a251/typer-0.26.5-py3-none-any.whl", hash = "sha256:4bfd901d564e41608920134aa5d4481200f4ba76d98e982d9f9d32dcb7b84da0", size = 122451, upload-time = "2026-06-01T14:42:51.021Z" },
{ url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" },
]
[[package]]
@@ -3974,15 +3979,15 @@ wheels = [
[[package]]
name = "uvicorn"
version = "0.48.0"
version = "0.49.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" },
{ url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" },
]
[package.optional-dependencies]
@@ -4058,11 +4063,11 @@ wheels = [
[[package]]
name = "wcwidth"
version = "0.7.0"
version = "0.8.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" }
sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" },
{ url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" },
]
[[package]]
@@ -4076,20 +4081,22 @@ wheels = [
[[package]]
name = "websockets"
version = "16.0"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
{ url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
{ url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
{ url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
{ url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
{ url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
{ url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
{ url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
{ url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
{ url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
{ url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
{ url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
{ url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
{ url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
{ url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
{ url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
{ url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
{ url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
{ url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
{ url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
{ url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
]
[[package]]