diff --git a/.env.example b/.env.example index 2261013..e0e78aa 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,13 @@ DATABASE_POOL_SIZE=10 DATABASE_MAX_OVERFLOW=20 DATABASE_ECHO=False +# 数据库Postgres配置 +POSTGRES_USER="postgres" +POSTGRES_PASSWORD="123456" +POSTGRES_HOST="127.0.0.1" +POSTGRES_PORT=5432 +POSTGRES_DB="postgres" + # CORS配置 ALLOWED_ORIGINS=["*"] diff --git a/api/agent_api.py b/api/agent_api.py new file mode 100644 index 0000000..a5970b9 --- /dev/null +++ b/api/agent_api.py @@ -0,0 +1,52 @@ +from fastapi import APIRouter +from fastapi.responses import StreamingResponse +from pydantic import BaseModel, Field +from services.agent_server.chef_service import ChefService + +# agent相关的路由 +agent_router = APIRouter(prefix="/agent", tags=["Agent相关"]) + + +class ChatRequest(BaseModel): + thread_id: str = Field(description="当前会话的id") + message: str = Field(description="当前会话的问题") + + +class ChatResponse(BaseModel): + thread_id: str + content: str + + +@agent_router.post("/chat", response_model=ChatResponse) +def chat(request: ChatRequest): + + result = ChefService.chat( + thread_id=request.thread_id, + message=request.message, + ) + + messages = result["messages"] + + # 最后一条 AI 消息 + last_message = messages[-1] + + return ChatResponse( + thread_id=request.thread_id, + content=last_message.content, + ) + +@agent_router.post("/chat_stream") +def chat(request: ChatRequest): + + def generate_stream(): + result = ChefService.chat_stream( + thread_id=request.thread_id, + message=request.message, + ) + for chunk in result: + yield chunk + + return StreamingResponse( + generate_stream(), + media_type="text/plain; charset=utf-8" + ) \ No newline at end of file diff --git a/core/config/settings.py b/core/config/settings.py index 56ca8c6..a459319 100644 --- a/core/config/settings.py +++ b/core/config/settings.py @@ -94,6 +94,30 @@ class Settings(BaseSettings): description="是否打印SQL语句" ) + # ---------- 数据库Postgres配置 ---------- + POSTGRES_USER: str = Field( + default='postgres', + description="postgres用户名" + ) + POSTGRES_PASSWORD: str = Field( + default='123456', + description="postgres密码" + ) + POSTGRES_HOST: str = Field( + default='127.0.0.1', + description="postgres主机地址" + ) + POSTGRES_PORT: int = Field( + default=5432, + ge=1, + le=65535, + description="postgres端口" + ) + POSTGRES_DB: str = Field( + default='postgres', + description="postgres数据库名" + ) + # ---------- CORS配置 ---------- ALLOWED_ORIGINS: List[str] = Field( default=["*"], @@ -161,6 +185,12 @@ class Settings(BaseSettings): # 连接url格式:mysql+pymysql://user:password@host:port/dbname return f"mysql+pymysql://{self.MYSQL_USER}:{self.MYSQL_PASSWORD}@{self.MYSQL_HOST}:{self.MYSQL_PORT}/{self.MYSQL_DB}?charset=utf8mb4" + @property + def postgres_url(self) -> str: + """postgres连接URL""" + # 连接url格式:postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable + return f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}?sslmode=disable" + @property def is_production(self) -> bool: """是否为生产环境""" diff --git a/lc/chef_agent.py b/lc/chef_agent.py index 961fd96..c6555a4 100644 --- a/lc/chef_agent.py +++ b/lc/chef_agent.py @@ -1,71 +1,250 @@ -import os -import sqlite3 -from typing import cast, Optional +from __future__ import annotations -from langchain.agents import create_agent, AgentState +import logging +from typing import Any, cast + +from langchain.agents import create_agent from langchain.agents.middleware import SummarizationMiddleware from langchain.agents.middleware.summarization import ContextMessages from langchain.chat_models import init_chat_model from langchain_core.language_models import BaseChatModel -from langchain_core.messages import HumanMessage -from langchain_core.runnables import RunnableConfig -from langgraph.checkpoint.sqlite import SqliteSaver # 需要安装langgraph-checkpoint-sqlite from langchain_tavily import TavilySearch +from langgraph.checkpoint.postgres import PostgresSaver +from psycopg import Connection +from psycopg_pool import ConnectionPool from core.config.settings import settings -# 初始化模型 需要多模态大模型 deepseek已支持 -llm_model = init_chat_model( - model="deepseek-v4-flash", - model_provider="deepseek", - api_key=settings.DEEPSEEK_API_KEY -) +logger = logging.getLogger(__name__) -# 定义工具 使用tavily搜索工具 langchain-tavily -search_tool = TavilySearch( - tavily_api_key=settings.TAVILY_API_KEY, - max_results=5, # 最大搜索结果条数 - topic="general" -) +class ChefAgent: + """厨师 Agent。 -# 定义记忆策略 使用SummarizationMiddleware摘要策略中间件 -summary = SummarizationMiddleware( - model=cast(BaseChatModel, llm_model), # 消息摘要的记忆管理策略的模型 - trigger=cast(ContextMessages, ("messages", 10)), # 触发策略的条件 - keep=cast(ContextMessages, ("messages", 5)) # 触发策略后保留的消息条数 -) + 负责: + - LLM 初始化 + - Tool 初始化 + - CheckPointer 初始化 + - Middleware 初始化 + - Agent 初始化 + - 生命周期管理 + """ -# 创建数据库目录(如果不存在) -os.makedirs("sqlite", exist_ok=True) -# 创建 SQLite 数据库连接 check_same_thread=False 是为了确保在多线程环境下的安全性 -conn = sqlite3.connect("sqlite/checkpoints.db", check_same_thread=False) -# 初始化 checkpointer -checkpointer = SqliteSaver(conn) -# 自动建表 -checkpointer.setup() + def __init__(self) -> None: + self._pool: ConnectionPool[Connection[Any]] | None = None + self._checkpointer: PostgresSaver | None = None + self._agent = None -# agent提示词 -system_prompt = """ -你是一名有名的国宴厨师。收到用户提供的食材照片或清单后,按照以下流程步骤操作: -1.识别和评估食材:若用户提供照片,首先辨别所有可见食材,基于食材的外观状态,评估其新鲜度和可用量,整理出一份“可用食材清单”。 -2.智能食谱检索:优先调用search_tool工具,以“可用食材清单”为核心关键词,查找可行菜谱。 -3.多维度评估与排序:从营养与烹饪难度这2个维度对检索到的候选食谱进行量化打分,并根据得分进行排序,制作简单且营养丰富的排名靠前。 -4.结构化方案输出:把排序后的食谱整理成一份结构清晰的建议报告,要包括食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。 -""" + def initialize(self) -> ChefAgent: + """初始化 Agent。 -# 定义thread_config 用于记忆存储分组 -thread_config: RunnableConfig = {"configurable": {"thread_id": "2"}} + Returns: + 当前 Agent 实例,方便链式调用。 + """ -# 创建agent -agent = create_agent( - model=cast(BaseChatModel, llm_model), # 指定类型 避免idea报错 - tools=[search_tool], - checkpointer=checkpointer, - middleware=[summary], - system_prompt=system_prompt -) + logger.info("开始初始化chef_agent...") -if __name__ == "__main__": - question = input("> ") - res = agent.invoke({"messages": [HumanMessage(content=question)]}, thread_config) - print(res) + if self._agent is not None: + return self + + # 1. 初始化 LLM + llm = self._create_llm() + + # 2. 初始化工具 + tools = self._create_tools() + + # 3. 初始化 Middleware + middleware = self._create_middleware(llm) + + # 4. 初始化 CheckPointer + self._checkpointer = self._create_checkpointer() + + # 5. 初始化 Agent + logger.info("chef_agent的agent开始初始化...") + self._agent = create_agent( + model=llm, + tools=tools, + middleware=middleware, + checkpointer=self._checkpointer, + system_prompt=self._system_prompt(), + ) + logger.info("chef_agent的agent初始化完成") + + logger.info("chef_agent初始化完成") + + return self + + @staticmethod + def _create_llm() -> BaseChatModel: + """创建 LLM。""" + + logger.info("chef_agent初始化llm...") + + llm = init_chat_model( + model="deepseek-v4-flash", + api_key=settings.DEEPSEEK_API_KEY, + ) + + logger.info("chef_agent的llm初始化完成") + return cast(BaseChatModel, llm) + + @staticmethod + def _create_tools() -> list[Any]: + """创建 Agent Tools。""" + + logger.info("chef_agent工具开始初始化...") + search_tool = TavilySearch( + tavily_api_key=settings.TAVILY_API_KEY, + max_results=5, + topic="general", + ) + + logger.info("chef_agent工具初始化完成") + return [search_tool] + + @staticmethod + def _create_middleware( + llm: BaseChatModel, + ) -> list[Any]: + """创建 Agent Middleware。""" + + logger.info("chef_agent中间件开始初始化...") + summarization = SummarizationMiddleware( + model=llm, + trigger=cast(ContextMessages, ("messages", 10)), + keep=cast(ContextMessages, ("messages", 5)), + ) + + logger.info("chef_agent中间件初始化完成") + return [summarization] + + def _create_checkpointer(self) -> PostgresSaver: + """创建 PostgreSQL CheckPointer。""" + + logger.info("chef_agent的CheckPointer开始初始化") + logger.info("创建PostgreSQL连接池") + self.pool: ConnectionPool[Connection[Any]] = ConnectionPool( + conninfo=settings.postgres_url, + max_size=10, + kwargs={ + "autocommit": True, + }, + ) + + logger.info("创建checkpointer") + checkpointer = PostgresSaver(self.pool) + + logger.info("初始化checkpoint数据表") + # 创建 LangGraph checkpoint 所需的数据表 + checkpointer.setup() + + logger.info("chef_agent的CheckPointer初始化完成") + return checkpointer + + @staticmethod + def _system_prompt() -> str: + """Agent System Prompt。""" + + return """ + 你是一名专业厨师。 + + 你的任务是根据用户提供的食材照片或食材清单,为用户推荐合适的菜谱。 + + 请严格按照以下流程执行: + + ## 1. 识别和评估食材 + + 如果用户提供的是食材照片: + + - 识别照片中可见的食材 + - 根据外观判断食材的新鲜程度 + - 估算大致可用量 + - 排除明显不可食用或状态异常的食材 + + 整理成「可用食材清单」。 + + 如果用户直接提供食材清单,则直接使用用户提供的信息。 + + ## 2. 搜索菜谱 + + 优先使用搜索工具。 + + 以「可用食材清单」作为核心关键词,搜索适合这些食材的菜谱。 + + 优先考虑: + + - 食材匹配度高 + - 操作简单 + - 营养均衡 + - 家庭烹饪可执行 + + 除非搜索不到合适结果,否则不要直接凭经验编造菜谱。 + + ## 3. 评估和排序 + + 对搜索到的候选菜谱进行综合评价。 + + 从以下维度进行评分: + + - 食材匹配度 + - 营养价值 + - 烹饪难度 + - 烹饪时间 + + 综合评分后进行排序。 + + 优先推荐: + + 「简单 + 食材匹配度高 + 营养丰富」 + + 的菜谱。 + + ## 4. 输出结果 + + 最终输出结构化的菜谱推荐报告。 + + 每个推荐至少包含: + + - 菜谱名称 + - 所需食材 + - 核心烹饪步骤 + - 烹饪时间 + - 难度 + - 综合评分 + - 推荐理由 + - 参考来源 + + 如果搜索结果中存在可靠的菜谱图片,则提供图片参考。 + + 如果搜索不到合适菜谱,再根据已有知识进行合理推荐,并明确说明这是基于模型知识给出的建议。 + """.strip() + + @property + def agent(self): + """获取 Agent。""" + + if self._agent is None: + raise RuntimeError( + "ChefAgent 尚未初始化,请先调用 initialize()" + ) + + return self._agent + + def close(self) -> None: + """释放资源。""" + + if self._pool is not None: + self._pool.close() + self._pool = None + + self._checkpointer = None + self._agent = None + + # 使用with上下文管理方式来使用的话需要这2个方法 + # def __enter__(self) -> ChefAgent: + # return self.initialize() + # + # def __exit__(self, exc_type, exc_value, traceback) -> None: + # self.close() + + +chef_agent = ChefAgent() diff --git a/main.py b/main.py index 82b8548..d9efddc 100644 --- a/main.py +++ b/main.py @@ -5,11 +5,14 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from sqlmodel import SQLModel + +from api.agent_api import agent_router from core.db import engine from core.config.settings import settings from api.scheduler_api import scheduler_router from api.lottery_api import lottery_router from core.task.dlt_scheduler import scheduler +from lc.chef_agent import chef_agent # 配置日志 logging.basicConfig( @@ -30,14 +33,19 @@ async def lifespan(app: FastAPI): # 启动定时调度器 scheduler.start() + # 初始化chef_agent + chef_agent.initialize() + yield # 此处交出控制权,服务开始运行 # ========== 关闭时执行 ========== logger.info("服务关闭,释放资源") + chef_agent.close() scheduler.shutdown() app = FastAPI(lifespan=lifespan) app.include_router(scheduler_router) app.include_router(lottery_router) +app.include_router(agent_router) app.add_middleware( CORSMiddleware, # type: ignore[arg-type] diff --git a/pyproject.toml b/pyproject.toml index ffb6048..3fe9317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,9 +11,13 @@ dependencies = [ "langchain-deepseek==1.1.0", "langchain-tavily==0.2.18", "langgraph>=1.2.11", + "langgraph-checkpoint-postgres>=3.1.2", "langgraph-checkpoint-sqlite==3.1.1", "notebook>=7.6.2", "openai==3.8.0", + "psycopg>=3.3.5", + "psycopg-binary>=3.3.5", + "psycopg-pool>=3.3.1", "pydantic==2.13.5", "pydantic-settings==2.15.0", "pymysql==1.2.0", diff --git a/services/agent_server/__init__.py b/services/agent_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/agent_server/chef_service.py b/services/agent_server/chef_service.py new file mode 100644 index 0000000..308a62d --- /dev/null +++ b/services/agent_server/chef_service.py @@ -0,0 +1,23 @@ +from langchain_core.messages import HumanMessage +from langchain_core.runnables import RunnableConfig + +from lc.chef_agent import chef_agent + + +class ChefService: + + @staticmethod + def chat( + thread_id: str, + message: str, + ): + config: RunnableConfig = {"configurable": {"thread_id": thread_id}} + result = chef_agent.agent.invoke({"messages": [HumanMessage(content=message)]}, config) + + return result + + @staticmethod + def chat_stream(thread_id: str, message: str): + config: RunnableConfig = {"configurable": {"thread_id": thread_id}} + result = chef_agent.agent.stream({"messages": [("user", message)]}, config=config, stream_mode="messages") + return result \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..7a8c24a --- /dev/null +++ b/test.py @@ -0,0 +1,11 @@ +sys_prompt = """ +你是一名有名的厨师。收到用户提供的食材照片或清单后,按照以下流程步骤操作: +1.识别和评估食材:若用户提供照片,首先辨别所有可见食材,基于食材的外观状态,评估其新鲜度和可用量,整理出一份“可用食材清单”。 +2.智能食谱检索:优先调用搜索工具,以“可用食材清单”为核心关键词,查找可行菜谱。 +3.多维度评估与排序:从营养与烹饪难度这2个维度对检索到的候选食谱进行量化打分,并根据得分进行排序,制作简单且营养丰富的排名靠前。 +4.结构化方案输出:把排序后的食谱整理成一份结构清晰的建议报告,要包括食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。 +严格按照流程进行操作,搜索不到再自己发挥。 +""" + +if __name__ == "__main__": + print(sys_prompt) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 6a3f1fa..bb3572b 100644 --- a/uv.lock +++ b/uv.lock @@ -1309,6 +1309,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/71/3b475f09bd57d3a5649792c66353312b4432afd843f301739dfcebd157f0/langgraph_checkpoint-4.2.0-py3-none-any.whl", hash = "sha256:0547fd228935a0b758865de3a3d6d7a2537c308895d0f9ab092ce9151b5da942", size = 56833, upload-time = "2026-08-07T20:05:02.655Z" }, ] +[[package]] +name = "langgraph-checkpoint-postgres" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langgraph-checkpoint" }, + { name = "orjson" }, + { name = "psycopg" }, + { name = "psycopg-pool" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/52/6e732f7bf702ef4918b64ea4107e7da21d4276b808f9a651fe34be9b0abe/langgraph_checkpoint_postgres-3.1.2.tar.gz", hash = "sha256:1cd404803ff895a2b79f3ac04ce92b775e6b999715f8333fce674c6d927bba95", size = 155091, upload-time = "2026-08-07T20:40:00.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/62/8899c9f4d9b97d5b3eb0c07c13cdf5f63342f15f5ef93eaf3477958df602/langgraph_checkpoint_postgres-3.1.2-py3-none-any.whl", hash = "sha256:6a7e38ef16985b54e356cba7bdaf447943aae33d5aaf290026c593bb6b4a6264", size = 52084, upload-time = "2026-08-07T20:39:58.994Z" }, +] + [[package]] name = "langgraph-checkpoint-sqlite" version = "3.1.1" @@ -1399,9 +1414,13 @@ dependencies = [ { name = "langchain-deepseek" }, { name = "langchain-tavily" }, { name = "langgraph" }, + { name = "langgraph-checkpoint-postgres" }, { name = "langgraph-checkpoint-sqlite" }, { name = "notebook" }, { name = "openai" }, + { name = "psycopg" }, + { name = "psycopg-binary" }, + { name = "psycopg-pool" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "pymysql" }, @@ -1422,9 +1441,13 @@ requires-dist = [ { name = "langchain-deepseek", specifier = "==1.1.0" }, { name = "langchain-tavily", specifier = "==0.2.18" }, { name = "langgraph", specifier = ">=1.2.11" }, + { name = "langgraph-checkpoint-postgres", specifier = ">=3.1.2" }, { name = "langgraph-checkpoint-sqlite", specifier = "==3.1.1" }, { name = "notebook", specifier = ">=7.6.2" }, { name = "openai", specifier = "==3.8.0" }, + { name = "psycopg", specifier = ">=3.3.5" }, + { name = "psycopg-binary", specifier = ">=3.3.5" }, + { name = "psycopg-pool", specifier = ">=3.3.1" }, { name = "pydantic", specifier = "==2.13.5" }, { name = "pydantic-settings", specifier = "==2.15.0" }, { name = "pymysql", specifier = "==1.2.0" }, @@ -1944,6 +1967,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] +[[package]] +name = "psycopg" +version = "3.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/73/8fb739d0f6bba247b9b93c9840c402a4f88545be5f1d4b02b23366371c00/psycopg-3.3.5.tar.gz", hash = "sha256:d0a3d9ccf5788af054cbd745278cb02401b5c312aeaafbf2c6144460aec47da4", size = 166508, upload-time = "2026-08-31T22:45:43.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/2e/d0a645bcaadde68bd6d93c43f02f14b0191bdda367ce3f7722abe3da744a/psycopg-3.3.5-py3-none-any.whl", hash = "sha256:ce5aa5cdb4f9379f00f487590e5890bfa7df9a164648c969ffa628505e21af4e", size = 213598, upload-time = "2026-08-31T22:39:02.184Z" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.3.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/1c/e718752cc63cf4e99e4a10fd36e3a3364dabdd0819484a24c0d79fbb9685/psycopg_binary-3.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6e85d50b87257fb117675a19ee59daa7bf9a57f6431500adf7059df799232ef4", size = 4704421, upload-time = "2026-08-31T22:42:59.564Z" }, + { url = "https://files.pythonhosted.org/packages/af/cf/a0e748e27c09b92738e4460582d121ba1908be3e36791e150f435e54b332/psycopg_binary-3.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e5becd311f9af8d180bad372f51fb2252fd02cb2073056e2b170c9274f95fe7f", size = 4765054, upload-time = "2026-08-31T22:43:05.421Z" }, + { url = "https://files.pythonhosted.org/packages/39/62/0cbac0266d56c94dd1f702d9af2b5d54bf80b6e658048f4f2c5bd63dd7e3/psycopg_binary-3.3.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:19e5bf9872dbd164c220567fd385ba2309c7d9df1541f78343510c6b0f36a1b7", size = 5547137, upload-time = "2026-08-31T22:43:11.768Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3a/73c6f8871f38fc07a9c0b4cbc9467beb116a2783cecf87dfa53900a396dc/psycopg_binary-3.3.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb3b3bffebfe07110730626e76238161124f35ac87b748d663316a28d22f58b0", size = 5227577, upload-time = "2026-08-31T22:43:20.767Z" }, + { url = "https://files.pythonhosted.org/packages/59/7b/9f17b9f4d297b774dc574199c4dcd02dadc32a00c4056265918de5c70635/psycopg_binary-3.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2111f880add40fb03c60556069ad68e884a0908a74d2debafc603caf93b73552", size = 6824606, upload-time = "2026-08-31T22:43:33.698Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/d84dadd94a004dbbb43ce0579f1f766fdc6b8cbef745090e24bea77b5283/psycopg_binary-3.3.5-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:40505676b1526b9ea387dace034040a8c8b0bcf984cd6bd4720a2ab15e813586", size = 5060854, upload-time = "2026-08-31T22:43:42.258Z" }, + { url = "https://files.pythonhosted.org/packages/30/d0/e5078be2c7d2490c0d6cd4cb7b3601aec44be2fa8b3fe927e9419b06004f/psycopg_binary-3.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5816472e3bb05615f33a741e0835043d1f4bf9709ff30d2f4aed71815cfc6b5e", size = 4589511, upload-time = "2026-08-31T22:43:50.012Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/08dfb5b18fa482e025864fd91a022310d0782c42e4cf5dda5d0e010b6790/psycopg_binary-3.3.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:358748fc4c8ccdc0e2bdf55420494930e19c3ade586ea9c3a6de3dad1f897311", size = 4268144, upload-time = "2026-08-31T22:43:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/88/b9/cb01dc1d63f3241b49b2ca7fb9f98d0f5c76127f0dbb9440635a6ad0233e/psycopg_binary-3.3.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1ef2e498be47800f6202b9a2304c22646325ca6d54001b7c785bcfdb24a1e8ab", size = 4001036, upload-time = "2026-08-31T22:44:04.429Z" }, + { url = "https://files.pythonhosted.org/packages/95/49/7c17dd832c05b380562ff2ff5f6ab2bcaeca8b7fff2c2b355854368a5bdb/psycopg_binary-3.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:88e01aa2e938a45655a8a5213fc3a44ba78cb4cab8a569b3e0bcb3d1d0eaba16", size = 4313112, upload-time = "2026-08-31T22:44:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2c/b0b2f887185d6a2ec0b3bef948cc07656d2d1a5d96fa7f2bb03f6ef06ca4/psycopg_binary-3.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:ba466011569297114449df9d523438e1adeedf3e4f31ffb78e897ec3fef3076b", size = 3647313, upload-time = "2026-08-31T22:44:19.139Z" }, + { url = "https://files.pythonhosted.org/packages/46/7f/4e2395da194558533bd9c31f35e4dc58ecbbae6a7176b0d2f72d629e8a51/psycopg_binary-3.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f8b132c7243ef5f503f0b6f986bf16d38a51b0df1c6ba2577743f128be03e3", size = 4712612, upload-time = "2026-08-31T22:44:25.723Z" }, + { url = "https://files.pythonhosted.org/packages/ae/94/fdb2093c8ccd7048449156db526ad746740cf34fe9c01e5dc1b7a7a8b257/psycopg_binary-3.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c0cac998b9b1e82dec853d2e53b3d34d56a525cf231f9441a636cfd5992929a9", size = 4775139, upload-time = "2026-08-31T22:44:36.719Z" }, + { url = "https://files.pythonhosted.org/packages/31/52/5195e87960715f7be2005761b72d56fce4e7757d5333fd40384f071c2de1/psycopg_binary-3.3.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:479b96fd78149cfa10369dc53fbfb89ee729be13146b584a23dbc7e164c0cf1e", size = 5556807, upload-time = "2026-08-31T22:44:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/f7/42/2d616210a91e1327516ed5ae71961aaa31bb740e4a61d7190f2221685a40/psycopg_binary-3.3.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f45d77e398542ce0937d9fa3cd9d84e9c5fc6b34c50a66404ae840bada312750", size = 5236206, upload-time = "2026-08-31T22:44:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/08/2e/e54b0d4cc263b3526e3728bb50a69660d5e79e52255ccd2a1a71e40e6f9a/psycopg_binary-3.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98a388509306e5e08a4203253ac52846bc1b034e5cbd0ae6da1211593cc28594", size = 6838066, upload-time = "2026-08-31T22:44:55.701Z" }, + { url = "https://files.pythonhosted.org/packages/77/80/ec22a110f81a44c411965982097efeee86006bdd5a1f51f628318106c84c/psycopg_binary-3.3.5-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab39e2794b95af61a2ff69e33e5ab6ac5df36e9ffea9a3b18e38b2aaca8c5ad5", size = 5072036, upload-time = "2026-08-31T22:45:02.838Z" }, + { url = "https://files.pythonhosted.org/packages/93/55/7bc3c3ac769ab4fe0c619f2179b4aab3ae043f4d478283dd8279d24c0be4/psycopg_binary-3.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9c071bf78e5c2e6efa40bc9089a954d7b41221347a72f35c6bf2d8c96e632f75", size = 4604058, upload-time = "2026-08-31T22:45:10.444Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/8e7414f7dc10b2959e664330cdcf393e412f67355975dafa876cef265264/psycopg_binary-3.3.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:14fdfd65a96ecbd8b586d14546105641f4a6ac7cbe335c786830ea4de94bbe60", size = 4284766, upload-time = "2026-08-31T22:45:17.881Z" }, + { url = "https://files.pythonhosted.org/packages/c1/72/33f293c1d3ee9114f47e9ef4880f31c9de864e84a9b09454c26e106c25ed/psycopg_binary-3.3.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8dbd694f3741dd4ac5bc60b70e17f7841aefb3f0f38cef4d2756de270e03af43", size = 4011958, upload-time = "2026-08-31T22:45:24.792Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c9/8e38840e5a7d006987bbc9acb29951912253fa50549a4db92b3aa535f089/psycopg_binary-3.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:14f432430fd9e1a9e7d9ab2fe14956c77f5d074ebdc556a1ad04e9a1bd3fca04", size = 4323273, upload-time = "2026-08-31T22:45:32.973Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c7/b7ebf601c307f93e7c4c4ebac0edc9db3b2729ca038efe700a18f86b5517/psycopg_binary-3.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:df209e64674a34b41662c67fdc8b4e0ffd77d2136393790691d086a09f9a6cab", size = 3745885, upload-time = "2026-08-31T22:45:40.537Z" }, +] + +[[package]] +name = "psycopg-pool" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/82/7a23d26039827ecd4ebe93905651029ddd307c5182ad59296dfb6f67b528/psycopg_pool-3.3.1.tar.gz", hash = "sha256:b10b10b7a175d5cc1592147dc5b7eec8a9e0834eb3ed2c4a92c858e2f51eb63c", size = 31661, upload-time = "2026-05-01T23:31:59.809Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/ed/89c2c620af0e1660354cd8aabf9f5b21f911597ce22acb37c805d6c86bc8/psycopg_pool-3.3.1-py3-none-any.whl", hash = "sha256:2af5b432941c4c9ad5c87b3fa410aec910ec8f7c122855897983a06c45f2e4b5", size = 40023, upload-time = "2026-05-01T23:31:53.136Z" }, +] + [[package]] name = "ptyprocess" version = "0.7.0"