Compare commits
1
Commits
f18e8a443f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23085fa114 |
No files matched your search
@@ -0,0 +1,39 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
from openai.types.chat import (
|
||||||
|
ChatCompletion,
|
||||||
|
ChatCompletionMessageParam,
|
||||||
|
ChatCompletionToolChoiceOptionParam,
|
||||||
|
ChatCompletionToolParam,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncLLMClient:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model: str | None = None,
|
||||||
|
api_key: str | None = None,
|
||||||
|
base_url: str | None = None,
|
||||||
|
timeout: int | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.model = model or os.getenv("LLM_MODEL_ID")
|
||||||
|
self.base_url = base_url or os.getenv("LLM_BASE_URL")
|
||||||
|
self.api_key = api_key or os.getenv("LLM_API_KEY")
|
||||||
|
self.timeout = timeout if timeout is not None else int(os.getenv("LLM_TIMEOUT", "60"))
|
||||||
|
|
||||||
|
if not all([self.model, self.base_url, self.api_key]):
|
||||||
|
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
|
||||||
|
self.client = AsyncOpenAI(base_url=self.base_url, api_key=self.api_key, timeout=self.timeout)
|
||||||
|
|
||||||
|
async def think(
|
||||||
|
self,
|
||||||
|
messages: list[ChatCompletionMessageParam],
|
||||||
|
tools: list[ChatCompletionToolParam],
|
||||||
|
tool_choice: ChatCompletionToolChoiceOptionParam = "auto",
|
||||||
|
**kwargs,
|
||||||
|
) -> ChatCompletion:
|
||||||
|
assert self.model is not None
|
||||||
|
return await self.client.chat.completions.create(
|
||||||
|
model=self.model, messages=messages, tools=tools, tool_choice=tool_choice, **kwargs
|
||||||
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from collections.abc import Iterator
|
from collections.abc import AsyncIterator
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -65,5 +65,5 @@ class BaseAgent:
|
|||||||
return message_param
|
return message_param
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run(self, question: str, context: ConversationContext) -> Iterator[Message]:
|
def run(self, question: str, context: ConversationContext) -> AsyncIterator[Message]:
|
||||||
pass
|
pass
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from uuid import uuid7
|
||||||
|
|
||||||
from openai.types.chat import ChatCompletionMessageParam
|
from openai.types.chat import ChatCompletionMessageParam
|
||||||
from pydantic import BaseModel, Field, PrivateAttr
|
from pydantic import BaseModel, Field, PrivateAttr
|
||||||
|
|
||||||
@@ -5,6 +7,12 @@ from mini_agent.tools.tool import Tool
|
|||||||
|
|
||||||
|
|
||||||
class ConversationContext(BaseModel):
|
class ConversationContext(BaseModel):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(run_id:str = "")->ConversationContext:
|
||||||
|
context = ConversationContext(conversation_id=str(uuid7()),run_id=run_id)
|
||||||
|
return context
|
||||||
|
|
||||||
conversation_id: str = Field(description="会话ID")
|
conversation_id: str = Field(description="会话ID")
|
||||||
run_id: str = Field(description="当前会话轮id")
|
run_id: str = Field(description="当前会话轮id")
|
||||||
messages: list[ChatCompletionMessageParam] = Field(description="消息历史", default_factory=list)
|
messages: list[ChatCompletionMessageParam] = Field(description="消息历史", default_factory=list)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from collections.abc import Iterator
|
from collections.abc import AsyncIterator
|
||||||
import os
|
import os
|
||||||
from typing import override
|
from typing import override
|
||||||
from uuid import uuid7
|
from uuid import uuid7
|
||||||
|
|
||||||
|
from mini_agent.agent.async_llm_client import AsyncLLMClient
|
||||||
from mini_agent.agent.base_tool_agent import BaseToolAgent
|
from mini_agent.agent.base_tool_agent import BaseToolAgent
|
||||||
from mini_agent.agent.context import ConversationContext
|
from mini_agent.agent.context import ConversationContext
|
||||||
from mini_agent.agent.llm_client import LLMClient
|
|
||||||
from mini_agent.agent.message import (
|
from mini_agent.agent.message import (
|
||||||
ErrorContent,
|
ErrorContent,
|
||||||
Message,
|
Message,
|
||||||
@@ -24,7 +24,7 @@ class ReActAgent(BaseToolAgent):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tool_repository: ToolRepository,
|
tool_repository: ToolRepository,
|
||||||
llm_client: LLMClient,
|
llm_client: AsyncLLMClient,
|
||||||
max_steps: int = 10,
|
max_steps: int = 10,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
@@ -80,14 +80,14 @@ class ReActAgent(BaseToolAgent):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def run(self, question: str, context: ConversationContext) -> Iterator[Message]:
|
async def run(self, question: str, context: ConversationContext) -> AsyncIterator[Message]:
|
||||||
self._initialize_context(context, question)
|
self._initialize_context(context, question)
|
||||||
run_id = str(uuid7())
|
run_id = str(uuid7())
|
||||||
|
|
||||||
for _ in range(self.max_steps):
|
for _ in range(self.max_steps):
|
||||||
tools = [tool.to_openai() for tool in context.discovered_tools]
|
tools = [tool.to_openai() for tool in context.discovered_tools]
|
||||||
|
|
||||||
response = self.client.think(
|
response = await self.client.think(
|
||||||
messages=context.messages,
|
messages=context.messages,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
tool_choice="auto",
|
tool_choice="auto",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from collections.abc import Iterator
|
from collections.abc import AsyncIterator
|
||||||
from typing import Literal, override
|
from typing import Literal, override
|
||||||
from uuid import uuid7
|
from uuid import uuid7
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from mini_agent.agent.async_llm_client import AsyncLLMClient
|
||||||
from mini_agent.agent.base_agent import BaseAgent
|
from mini_agent.agent.base_agent import BaseAgent
|
||||||
from mini_agent.agent.context import ConversationContext
|
from mini_agent.agent.context import ConversationContext
|
||||||
from mini_agent.agent.llm_client import LLMClient
|
|
||||||
from mini_agent.agent.message import (
|
from mini_agent.agent.message import (
|
||||||
ErrorContent,
|
ErrorContent,
|
||||||
Message,
|
Message,
|
||||||
@@ -26,7 +26,7 @@ class RouteDecision(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RouteAgent(BaseAgent):
|
class RouteAgent(BaseAgent):
|
||||||
def __init__(self, llm_client: LLMClient, max_steps: int = 10) -> None:
|
def __init__(self, llm_client: AsyncLLMClient, max_steps: int = 10) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.sub_agents: dict[str, BaseAgent] = {}
|
self.sub_agents: dict[str, BaseAgent] = {}
|
||||||
self.llm_client = llm_client
|
self.llm_client = llm_client
|
||||||
@@ -143,11 +143,11 @@ class RouteAgent(BaseAgent):
|
|||||||
return template.format("\n".join(agent_descriptions))
|
return template.format("\n".join(agent_descriptions))
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def run(self, question: str, context: ConversationContext) -> Iterator[Message]:
|
async def run(self, question: str, context: ConversationContext) -> AsyncIterator[Message]:
|
||||||
self._initialize_context(context=context, question=question)
|
self._initialize_context(context=context, question=question)
|
||||||
run_id = context.run_id
|
run_id = context.run_id
|
||||||
for _ in range(self.max_steps):
|
for _ in range(self.max_steps):
|
||||||
response = self.llm_client.think(
|
response = await self.llm_client.think(
|
||||||
messages=context.messages,
|
messages=context.messages,
|
||||||
tools=[],
|
tools=[],
|
||||||
tool_choice="auto",
|
tool_choice="auto",
|
||||||
@@ -191,10 +191,12 @@ class RouteAgent(BaseAgent):
|
|||||||
run_id=context.run_id,
|
run_id=context.run_id,
|
||||||
run_status=RunStatus.RUNNING,
|
run_status=RunStatus.RUNNING,
|
||||||
role=MessageRole.ASSISTANT,
|
role=MessageRole.ASSISTANT,
|
||||||
content= [ThinkContent(text=decision.reason or "正在路由请求")]
|
content=[ThinkContent(text=decision.reason or "正在路由请求")],
|
||||||
)
|
)
|
||||||
agent_context = ConversationContext(conversation_id=context.conversation_id,run_id=str(uuid7()))
|
agent_context = ConversationContext(
|
||||||
for agent_message in agent.run(
|
conversation_id=context.conversation_id, run_id=str(uuid7())
|
||||||
|
)
|
||||||
|
async for agent_message in agent.run(
|
||||||
context=agent_context, question=decision.task or question
|
context=agent_context, question=decision.task or question
|
||||||
):
|
):
|
||||||
yield Message(
|
yield Message(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ router = APIRouter()
|
|||||||
async def websocket_endpoint(websocket: WebSocket):
|
async def websocket_endpoint(websocket: WebSocket):
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
agent = websocket.app.state.agent
|
agent = websocket.app.state.agent
|
||||||
context = ConversationContext(conversation_id=str(uuid7()),run_id="")
|
context = ConversationContext.create()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -40,8 +40,17 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for message in agent.run(question, context):
|
try:
|
||||||
|
async for message in agent.run(question, context):
|
||||||
await websocket.send_json(message.model_dump(mode="json"))
|
await websocket.send_json(message.model_dump(mode="json"))
|
||||||
|
except Exception as e:
|
||||||
|
await websocket.send_json(Message(
|
||||||
|
conversation_id=context.conversation_id,
|
||||||
|
role=MessageRole.ASSISTANT,
|
||||||
|
run_id=context.run_id,
|
||||||
|
run_status=RunStatus.FAILED,
|
||||||
|
content=[TextContent(text="执行出错")],
|
||||||
|
).model_dump(mode="json"))
|
||||||
|
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
print("客户端断开连接")
|
print("客户端断开连接")
|
||||||
+13
-7
@@ -5,8 +5,8 @@ from uuid import uuid7
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from mini_agent.agent.async_llm_client import AsyncLLMClient
|
||||||
from mini_agent.agent.context import ConversationContext
|
from mini_agent.agent.context import ConversationContext
|
||||||
from mini_agent.agent.llm_client import LLMClient
|
|
||||||
from mini_agent.agent.message import TextContent, ToolCallContent, ToolResultContent
|
from mini_agent.agent.message import TextContent, ToolCallContent, ToolResultContent
|
||||||
from mini_agent.agent.react_agent import ReActAgent
|
from mini_agent.agent.react_agent import ReActAgent
|
||||||
from mini_agent.agent.route_agent import RouteAgent
|
from mini_agent.agent.route_agent import RouteAgent
|
||||||
@@ -14,14 +14,15 @@ from mini_agent.api.websocket import router
|
|||||||
from mini_agent.tools.tool_repository import ToolRepository
|
from mini_agent.tools.tool_repository import ToolRepository
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||||
print(PROJECT_ROOT)
|
|
||||||
load_dotenv(PROJECT_ROOT / ".env")
|
load_dotenv(PROJECT_ROOT / ".env")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
react_agent = ReActAgent(tool_repository=ToolRepository(), llm_client=LLMClient(),max_steps=50)
|
react_agent = ReActAgent(
|
||||||
route_agent = RouteAgent(llm_client=LLMClient())
|
tool_repository=ToolRepository(), llm_client=AsyncLLMClient(), max_steps=50
|
||||||
|
)
|
||||||
|
route_agent = RouteAgent(llm_client=AsyncLLMClient())
|
||||||
route_agent.register_agent(react_agent)
|
route_agent.register_agent(react_agent)
|
||||||
app.state.agent = route_agent
|
app.state.agent = route_agent
|
||||||
|
|
||||||
@@ -41,14 +42,14 @@ async def health():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
async def main():
|
||||||
agent = ReActAgent(tool_repository=ToolRepository(), llm_client=LLMClient())
|
agent = ReActAgent(tool_repository=ToolRepository(), llm_client=AsyncLLMClient())
|
||||||
context = ConversationContext(conversation_id="1", run_id="")
|
context = ConversationContext(conversation_id="1", run_id="")
|
||||||
while True:
|
while True:
|
||||||
question = input("提问:")
|
question = input("提问:")
|
||||||
context.run_id = str(uuid7())
|
context.run_id = str(uuid7())
|
||||||
try:
|
try:
|
||||||
for message in agent.run(question=question, context=context):
|
async for message in agent.run(question=question, context=context):
|
||||||
for content in message.content:
|
for content in message.content:
|
||||||
match content:
|
match content:
|
||||||
case TextContent(text=text):
|
case TextContent(text=text):
|
||||||
@@ -64,3 +65,8 @@ if __name__ == "__main__":
|
|||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌{e}")
|
print(f"❌{e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import asyncio
|
||||||
|
asyncio.run(main())
|
||||||
@@ -292,11 +292,6 @@ dependencies = [
|
|||||||
{ name = "uvicorn", extra = ["standard"] },
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "ruff" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastapi", specifier = ">=0.162.0" },
|
{ name = "fastapi", specifier = ">=0.162.0" },
|
||||||
@@ -309,9 +304,6 @@ requires-dist = [
|
|||||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.54.0" },
|
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.54.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
|
||||||
dev = [{ name = "ruff", specifier = ">=0.17.3" }]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openai"
|
name = "openai"
|
||||||
version = "3.42.0"
|
version = "3.42.0"
|
||||||
@@ -473,31 +465,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ruff"
|
|
||||||
version = "0.17.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
|||||||
Reference in new issue
Block a user