diff --git a/README.md b/README.md index 9812709..b421136 100644 --- a/README.md +++ b/README.md @@ -145,15 +145,19 @@ Common commands: `/help`, `/clear`, `/history`, `/stats`, `/exit` - ✅ **Agent Multi-round Execution Loop**: Complete tool calling pipeline - ✅ **Basic Tool Set**: Read / Write / Edit files + Bash commands - ✅ **Session Note Tool**: Agent actively records and retrieves session highlights ⭐ -- ✅ **Claude Skills Integration**: 20+ professional skills (documentation, design, testing, development) ⭐💡 🆕 -- ✅ **MCP Tool Integration**: Memory (knowledge graph) + MiniMax Search (web search) ⭐ 🆕 -- ✅ **MiniMax M2 Model**: Through Anthropic-compatible endpoint +- ✅ **Claude Skills Integration**: 20+ professional skills (documentation, design, testing, development) ⭐💡 +- ✅ **MCP Tool Integration**: Memory (knowledge graph) + MiniMax Search (web search) ⭐ +- ✅ **MiniMax M2 Model**: Through Anthropic-compatible endpoint with extended thinking support 🆕 ### Advanced Features ⭐ +- ✅ **Extended Thinking**: Full support for M2's thinking capability, preserves reasoning in context 🆕 +- ✅ **Intelligent Context Management**: Smart message history summarization (80K token limit) 🆕 +- ✅ **Precise Token Calculation**: Using tiktoken for accurate token counting 🆕 +- ✅ **Complete Run Logging**: Detailed logs of all requests, responses, and tool executions 🆕 - ✅ **Persistent Notes**: Agent maintains context across sessions and execution chains - ✅ **Intelligent Recording**: Agent autonomously determines what information needs to be recorded -- ✅ **Multi-round Sessions**: Supports session management, history clearing, statistics, etc. 🆕 -- ✅ **Beautiful Interaction**: Colorful terminal output, clear session interface 🆕 +- ✅ **Multi-round Sessions**: Supports session management, history clearing, statistics, etc. +- ✅ **Beautiful Interaction**: Colorful terminal output, clear session interface - ✅ **Simple yet Complete**: Showcases core functionality, avoids excessive complexity ## Project Structure @@ -172,15 +176,17 @@ mini-agent/ │ ├── config-example.yaml # API configuration example │ ├── agent.py # Core Agent │ ├── llm.py # LLM Client (Anthropic compatible) -│ ├── config.py # Configuration loader 🆕 +│ ├── logger.py # Agent run logger 🆕 +│ ├── retry.py # Retry mechanism +│ ├── config.py # Configuration loader │ └── tools/ │ ├── base.py # Tool base class │ ├── file_tools.py # File tools │ ├── bash_tool.py # Bash tool │ ├── note_tool.py # Session Note tool ⭐ │ ├── mcp_loader.py # MCP loader (supports external servers) ⭐ -│ ├── skill_loader.py # Skill loader 🆕 -│ └── skill_tool.py # Skill tool 🆕 +│ ├── skill_loader.py # Skill loader +│ └── skill_tool.py # Skill tool ├── tests/ │ ├── test_agent.py # Agent integration tests │ ├── test_llm.py # LLM tests @@ -212,10 +218,27 @@ External MCP Servers: ```python # Simplified core loop (from agent.py) async def run(self) -> str: + # Initialize run logging + self.logger.start_new_run() + step = 0 while step < self.max_steps: + # Check and summarize message history if needed + await self._summarize_messages() + + # Log LLM request + self.logger.log_request(messages=self.messages, tools=tool_schemas) + # 1. Call LLM response = await self.llm.generate(messages, tools) + + # Log LLM response + self.logger.log_response( + content=response.content, + thinking=response.thinking, + tool_calls=response.tool_calls, + finish_reason=response.finish_reason + ) # 2. If no tool calls, task complete if not response.tool_calls: @@ -224,12 +247,92 @@ async def run(self) -> str: # 3. Execute tool calls for tool_call in response.tool_calls: result = await tool.execute(**arguments) + + # Log tool result + self.logger.log_tool_result( + tool_name=function_name, + arguments=arguments, + result_success=result.success, + result_content=result.content + ) + self.messages.append(tool_result_message) step += 1 ``` -### 2. Session Note Tool - Session Note Recording ⭐ +### 2. Extended Thinking Support 🆕 + +The agent fully supports MiniMax M2's extended thinking capability: + +**Key Features**: +- **Thinking Preservation**: Thinking content is preserved in message history for context continuity +- **Display Control**: Thinking is displayed separately from response content in the terminal +- **Context Injection**: When replaying conversation history, thinking blocks are properly injected + +**Message Structure**: +```python +class Message(BaseModel): + role: str + content: str | List[Dict[str, Any]] + thinking: str | None = None # Extended thinking content + tool_calls: List[Dict[str, Any]] | None = None +``` + +**API Format Conversion**: +```python +# When sending to API, thinking is injected as content block +content_blocks = [] +if msg.thinking: + content_blocks.append({"type": "thinking", "thinking": msg.thinking}) +if msg.content: + content_blocks.append({"type": "text", "text": msg.content}) +``` + +### 3. Intelligent Context Management 🆕 + +The agent uses smart summarization instead of simple truncation to manage long conversations: + +**Token Limit**: 80,000 tokens (configurable) + +**Summarization Strategy** (Agent Mode): +1. **Precise Token Calculation**: Uses tiktoken (cl100k_base encoder) for accurate token counting +2. **Trigger Condition**: Automatically summarizes when token count exceeds the limit +3. **Per-Round Summarization**: Summarizes agent execution between each pair of user messages +4. **User Intent Preservation**: All user messages are preserved (never summarized) +5. **In-Progress Handling**: Even the current executing round gets summarized +6. **LLM-Generated Summary**: Uses LLM to create concise summaries (≤300 words) of each execution round + +**Message Structure After Summary**: +``` +system → user1 → summary1 → user2 → summary2 → user3 → summary3 (if executing) +``` + +**Benefits**: +- ✅ Maintains long-term context without token overflow +- ✅ Preserves all user task intents (never lost) +- ✅ Compresses only agent execution process (assistant/tool messages) +- ✅ Each round independently summarized for clarity +- ✅ Continuous context across long conversations + +### 4. Complete Run Logging 🆕 + +Every agent run is fully logged with detailed information: + +**Log File Format**: `workspace/agent_run_YYYYMMDD_HHMMSS.log` + +**Logged Information**: +- **[N] REQUEST**: LLM requests with all messages and available tools +- **[N+1] RESPONSE**: LLM responses with content, thinking, and tool calls +- **[N+2] TOOL_RESULT**: Tool execution results with arguments and outputs + +**Benefits**: +- ✅ Complete audit trail of all interactions +- ✅ Easy debugging and troubleshooting +- ✅ Sequential indexing for event tracking +- ✅ Separate log file per run + +### 5. Session Note Tool - Session Note Recording ⭐ This is one of the **core highlights** of this demo, showcasing a simple and efficient session memory management approach. @@ -298,7 +401,7 @@ Notes are stored in JSON format at `workspace/.agent_memory.json`: ] ``` -### 3. MiniMax Search - Web Search and Browse ⭐ +### 6. MiniMax Search - Web Search and Browse ⭐ This is an **independent MCP Server** integrated into the Agent via `mcp.json`. @@ -357,7 +460,7 @@ Agent: (calls browse tool) --- -### 4. Tool Definition +### 7. Tool Definition Each tool inherits from the `Tool` base class: @@ -428,18 +531,26 @@ pytest tests/test_agent.py tests/test_note_tool.py -v ## Summary -This project is an **educational-friendly** yet **technically complete** Agent implementation: +This project is an **educational-friendly** yet **production-ready** Agent implementation: + +✅ **Simple Enough**: Clean code structure, easy to understand +✅ **Complete Enough**: Includes all core Agent functionality with advanced features +✅ **Production Features**: Extended thinking, smart context management, complete logging +✅ **Shows the Gap**: Clearly contrasts differences between Demo and production -✅ **Simple Enough**: Minimal code, easy to understand -✅ **Complete Enough**: Includes core functionality and Session Note Tool -✅ **Shows the Gap**: Clearly contrasts the huge difference between Demo and production +**Key Highlights**: +- 🧠 **Extended Thinking Support**: Full M2 thinking capability with context preservation +- 📊 **Smart Context Management**: LLM-based summarization for long conversations (80K tokens) +- 📝 **Complete Logging**: Detailed audit trail of all interactions +- 🎯 **Precise Token Counting**: Using tiktoken for accurate calculation Suitable for: - 🎓 Learning Agent architecture and working principles - 🧪 Rapid experimentation and prototype validation - 📚 Understanding production environment complexity +- 🚀 Building production-grade Agent applications -**Not suitable** for direct production use. +This implementation demonstrates both simplicity and completeness, bridging the gap between educational demos and production systems. ## 📚 Related Documentation diff --git a/README_CN.md b/README_CN.md index fe89401..cce91a8 100644 --- a/README_CN.md +++ b/README_CN.md @@ -147,15 +147,19 @@ uv run python main.py - ✅ **Agent 多轮执行循环**: 完整的工具调用链路 - ✅ **基础工具集**: Read / Write / Edit 文件 + Bash 命令 - ✅ **Session Note Tool**: Agent 主动记录和检索会话要点 ⭐ -- ✅ **Claude Skills 集成**: 20+ 专业技能(文档、设计、测试、开发)⭐💡 🆕 -- ✅ **MCP 工具集成**: Memory(知识图谱)+ MiniMax Search(网页搜索)⭐ 🆕 -- ✅ **MiniMax M2 模型**: 通过 Anthropic 兼容端点 +- ✅ **Claude Skills 集成**: 20+ 专业技能(文档、设计、测试、开发)⭐💡 +- ✅ **MCP 工具集成**: Memory(知识图谱)+ MiniMax Search(网页搜索)⭐ +- ✅ **MiniMax M2 模型**: 通过 Anthropic 兼容端点,支持扩展思维 🆕 ### 进阶特性 ⭐ +- ✅ **扩展思维支持**: 完整支持 M2 的 thinking 能力,在上下文中保留推理过程 🆕 +- ✅ **智能上下文管理**: 智能消息历史摘要(80K token 限制)🆕 +- ✅ **精确 Token 计算**: 使用 tiktoken 进行准确的 token 统计 🆕 +- ✅ **完整运行日志**: 详细记录所有请求、响应和工具执行 🆕 - ✅ **持久化笔记**: Agent 跨会话和执行链路保持上下文 - ✅ **智能记录**: Agent 自主判断什么信息需要记录 -- ✅ **多轮会话**: 支持会话管理、历史清除、统计等功能 🆕 -- ✅ **美化交互**: 彩色终端输出,清晰的会话界面 🆕 +- ✅ **多轮会话**: 支持会话管理、历史清除、统计等功能 +- ✅ **美化交互**: 彩色终端输出,清晰的会话界面 - ✅ **简洁但完整**: 展示核心功能,避免过度复杂 ## 项目结构 @@ -174,15 +178,17 @@ mini-agent/ │ ├── config-example.yaml # API 配置示例 │ ├── agent.py # 核心 Agent │ ├── llm.py # LLM 客户端 (Anthropic 兼容) -│ ├── config.py # 配置加载器 🆕 +│ ├── logger.py # Agent 运行日志记录器 🆕 +│ ├── retry.py # 重试机制 +│ ├── config.py # 配置加载器 │ └── tools/ │ ├── base.py # 工具基类 │ ├── file_tools.py # 文件工具 │ ├── bash_tool.py # Bash 工具 │ ├── note_tool.py # Session Note 工具 ⭐ │ ├── mcp_loader.py # MCP 加载器(支持外部服务器)⭐ -│ ├── skill_loader.py # Skill 加载器 🆕 -│ └── skill_tool.py # Skill 工具 🆕 +│ ├── skill_loader.py # Skill 加载器 +│ └── skill_tool.py # Skill 工具 ├── tests/ │ ├── test_agent.py # Agent 集成测试 │ ├── test_llm.py # LLM 测试 @@ -214,10 +220,27 @@ mini-agent/ ```python # 简化的核心循环(来自 agent.py) async def run(self) -> str: + # 初始化运行日志 + self.logger.start_new_run() + step = 0 while step < self.max_steps: + # 检查并在需要时摘要消息历史 + await self._summarize_messages() + + # 记录 LLM 请求日志 + self.logger.log_request(messages=self.messages, tools=tool_schemas) + # 1. 调用 LLM response = await self.llm.generate(messages, tools) + + # 记录 LLM 响应日志 + self.logger.log_response( + content=response.content, + thinking=response.thinking, + tool_calls=response.tool_calls, + finish_reason=response.finish_reason + ) # 2. 如果没有工具调用,任务完成 if not response.tool_calls: @@ -226,12 +249,92 @@ async def run(self) -> str: # 3. 执行工具调用 for tool_call in response.tool_calls: result = await tool.execute(**arguments) + + # 记录工具结果日志 + self.logger.log_tool_result( + tool_name=function_name, + arguments=arguments, + result_success=result.success, + result_content=result.content + ) + self.messages.append(tool_result_message) step += 1 ``` -### 2. Session Note Tool - 会话笔记记录 ⭐ +### 2. 扩展思维支持 🆕 + +Agent 完整支持 MiniMax M2 的扩展思维能力: + +**核心特性**: +- **思维保留**:thinking 内容在消息历史中保留,确保上下文连续性 +- **展示控制**:thinking 在终端中与响应内容分开显示 +- **上下文注入**:回放对话历史时,thinking 块被正确注入 + +**消息结构**: +```python +class Message(BaseModel): + role: str + content: str | List[Dict[str, Any]] + thinking: str | None = None # 扩展思维内容 + tool_calls: List[Dict[str, Any]] | None = None +``` + +**API 格式转换**: +```python +# 发送到 API 时,thinking 作为 content block 注入 +content_blocks = [] +if msg.thinking: + content_blocks.append({"type": "thinking", "thinking": msg.thinking}) +if msg.content: + content_blocks.append({"type": "text", "text": msg.content}) +``` + +### 3. 智能上下文管理 🆕 + +Agent 使用智能摘要而非简单截断来管理长对话: + +**Token 限制**:80,000 tokens(可配置) + +**摘要策略**(Agent 模式): +1. **精确 Token 计算**:使用 tiktoken(cl100k_base 编码器)进行准确的 token 统计 +2. **触发条件**:当 token 数量超过限制时自动摘要 +3. **逐轮摘要**:对每一轮 user-user 之间的 agent 执行过程进行摘要 +4. **保留用户意图**:所有 user 消息都被保留(永不被摘要) +5. **执行中处理**:即使当前正在执行的轮次也会被摘要 +6. **LLM 生成摘要**:使用 LLM 为每轮执行创建简洁摘要(≤300 字) + +**摘要后的消息结构**: +``` +system → user1 → summary1 → user2 → summary2 → user3 → summary3 (如果执行中) +``` + +**优势**: +- ✅ 维持长期上下文而不会 token 溢出 +- ✅ 保留所有用户任务意图(永不丢失) +- ✅ 仅压缩 agent 执行过程(assistant/tool 消息) +- ✅ 每轮独立摘要,清晰明了 +- ✅ 长对话中的持续上下文 + +### 4. 完整运行日志 🆕 + +每次 agent 运行都有完整的日志记录: + +**日志文件格式**:`workspace/agent_run_YYYYMMDD_HHMMSS.log` + +**记录内容**: +- **[N] REQUEST**:LLM 请求,包含所有消息和可用工具 +- **[N+1] RESPONSE**:LLM 响应,包含内容、thinking 和工具调用 +- **[N+2] TOOL_RESULT**:工具执行结果,包含参数和输出 + +**优势**: +- ✅ 完整的交互审计轨迹 +- ✅ 便于调试和故障排查 +- ✅ 顺序索引用于事件追踪 +- ✅ 每次运行独立的日志文件 + +### 5. Session Note Tool - 会话笔记记录 ⭐ 这是本 demo 的**核心亮点**之一,展示了一种简洁高效的会话记忆管理方式。 @@ -300,7 +403,7 @@ Agent: (主动调用 recall_notes) ] ``` -### 3. MiniMax Search - 网页搜索和浏览 ⭐ +### 6. MiniMax Search - 网页搜索和浏览 ⭐ 这是一个**独立的 MCP Server**,通过 `mcp.json` 集成到 Agent 中。 @@ -360,7 +463,7 @@ Agent: (调用 browse 工具) --- -### 4. 工具定义 +### 7. 工具定义 每个工具继承自 `Tool` 基类: @@ -431,18 +534,26 @@ pytest tests/test_agent.py tests/test_note_tool.py -v ## 总结 -本项目是一个**教学友好**但**技术完整**的 Agent 实现: +本项目是一个**教学友好**且**生产级**的 Agent 实现: + +✅ **足够简单**: 代码结构清晰,易于理解 +✅ **足够完整**: 包含所有核心 Agent 功能和高级特性 +✅ **生产级特性**: 扩展思维、智能上下文管理、完整日志 +✅ **展示鸿沟**: 清晰对比 Demo 和生产环境的差异 -✅ **足够简单**: 代码量少,易于理解 -✅ **足够完整**: 包含核心功能和 Session Note Tool -✅ **展示鸿沟**: 清晰对比 Demo 和生产环境的巨大差异 +**核心亮点**: +- 🧠 **扩展思维支持**:完整的 M2 thinking 能力,保留上下文 +- 📊 **智能上下文管理**:基于 LLM 的长对话摘要(80K tokens) +- 📝 **完整日志记录**:所有交互的详细审计轨迹 +- 🎯 **精确 Token 统计**:使用 tiktoken 进行准确计算 适合用于: - 🎓 学习 Agent 架构和工作原理 - 🧪 快速实验和原型验证 - 📚 理解生产环境的复杂性 +- 🚀 构建生产级 Agent 应用 -**不适合**直接用于生产环境。 +本实现展示了简洁性和完整性的平衡,架起了教学演示和生产系统之间的桥梁。 ## 📚 相关文档 diff --git a/mini_agent/agent.py b/mini_agent/agent.py index 83d4e28..7224ebc 100644 --- a/mini_agent/agent.py +++ b/mini_agent/agent.py @@ -2,9 +2,12 @@ import json from pathlib import Path -from typing import Dict, List +from typing import List -from .llm import LLMClient, LLMResponse, Message +import tiktoken + +from .llm import LLMClient, Message +from .logger import AgentLogger from .tools.base import Tool, ToolResult @@ -45,13 +48,13 @@ def __init__( tools: List[Tool], max_steps: int = 50, workspace_dir: str = "./workspace", - max_messages: int = 20, # 最大消息数,防止 context 超限 + token_limit: int = 80000, # Token 超过此值时触发 summary ): self.llm = llm_client self.system_prompt = system_prompt self.tools = {tool.name: tool for tool in tools} self.max_steps = max_steps - self.max_messages = max_messages + self.token_limit = token_limit self.workspace_dir = Path(workspace_dir) # Ensure workspace exists @@ -60,31 +63,225 @@ def __init__( # Initialize message history self.messages: List[Message] = [Message(role="system", content=system_prompt)] + # 初始化日志记录器 + self.logger = AgentLogger(self.workspace_dir) + def add_user_message(self, content: str): """Add a user message to history.""" self.messages.append(Message(role="user", content=content)) - def _truncate_messages(self): - """简单的消息截断:保留 system prompt + 最近的消息 + def _estimate_tokens(self) -> int: + """使用 tiktoken 精确计算消息历史的 token 数量 - 策略: - - 永远保留第一条 system prompt - - 保留最近的 (max_messages - 1) 条消息 - - 当消息数超过 max_messages 时触发截断 + 使用 cl100k_base 编码器(GPT-4/Claude 兼容) + """ + try: + # 使用 cl100k_base 编码器(GPT-4 和大多数现代模型使用) + encoding = tiktoken.get_encoding("cl100k_base") + except Exception: + # Fallback: 如果 tiktoken 初始化失败,使用简单估算 + return self._estimate_tokens_fallback() + + total_tokens = 0 + + for msg in self.messages: + # 统计文本内容 + if isinstance(msg.content, str): + total_tokens += len(encoding.encode(msg.content)) + elif isinstance(msg.content, list): + for block in msg.content: + if isinstance(block, dict): + # 将字典转为字符串计算 + total_tokens += len(encoding.encode(str(block))) + + # 统计 thinking + if msg.thinking: + total_tokens += len(encoding.encode(msg.thinking)) + + # 统计 tool_calls + if msg.tool_calls: + total_tokens += len(encoding.encode(str(msg.tool_calls))) + + # 每条消息的元数据开销(约 4 tokens) + total_tokens += 4 + + return total_tokens + + def _estimate_tokens_fallback(self) -> int: + """Fallback token 估算方法(当 tiktoken 不可用时)""" + total_chars = 0 + for msg in self.messages: + if isinstance(msg.content, str): + total_chars += len(msg.content) + elif isinstance(msg.content, list): + for block in msg.content: + if isinstance(block, dict): + total_chars += len(str(block)) + + if msg.thinking: + total_chars += len(msg.thinking) + + if msg.tool_calls: + total_chars += len(str(msg.tool_calls)) + + # 粗略估算:平均 2.5 个字符 = 1 token + return int(total_chars / 2.5) + + async def _summarize_messages(self): + """消息历史摘要:当 token 超限时,对每一轮 user-user 之间的对话进行摘要 + + 策略 (Agent 模式): + - 保留所有 user 消息(这是用户的意图) + - 对每一轮 user-user 之间的内容(agent 执行过程)进行 summary + - 最后一轮如果还在执行中(有 agent/tool 消息但没有下一个 user),也要 summary + - 结构:system -> user1 -> summary1 -> user2 -> summary2 -> user3 -> summary3(如果执行中) + """ + estimated_tokens = self._estimate_tokens() + + # 如果未超限,不需要 summary + if estimated_tokens <= self.token_limit: + return + + print( + f"\n{Colors.BRIGHT_YELLOW}📊 Token 估算值: {estimated_tokens}/{self.token_limit}{Colors.RESET}" + ) + print(f"{Colors.BRIGHT_YELLOW}🔄 触发消息历史摘要...{Colors.RESET}") + + # 找到所有 user 消息的索引(跳过 system prompt) + user_indices = [ + i for i, msg in enumerate(self.messages) if msg.role == "user" and i > 0 + ] + + # 至少需要 1 个 user 消息才能进行 summary + if len(user_indices) < 1: + print(f"{Colors.BRIGHT_YELLOW}⚠️ 消息不足,无法进行摘要{Colors.RESET}") + return + + # 构建新的消息列表 + new_messages = [self.messages[0]] # 保留 system prompt + summary_count = 0 + + # 遍历每个 user 消息,对其后面的执行过程进行 summary + for i, user_idx in enumerate(user_indices): + # 添加当前 user 消息 + new_messages.append(self.messages[user_idx]) + + # 确定要 summary 的消息范围 + # 如果是最后一个 user,则到消息列表末尾;否则到下一个 user 之前 + if i < len(user_indices) - 1: + next_user_idx = user_indices[i + 1] + else: + next_user_idx = len(self.messages) + + # 提取这一轮的执行消息 + execution_messages = self.messages[user_idx + 1 : next_user_idx] + + # 如果这一轮有执行消息,进行 summary + if execution_messages: + summary_text = await self._create_summary( + execution_messages, user_idx, i + 1 + ) + if summary_text: + summary_message = Message( + role="user", content=f"[执行摘要]\n\n{summary_text}" + ) + new_messages.append(summary_message) + summary_count += 1 + + # 替换消息列表 + self.messages = new_messages + + new_tokens = self._estimate_tokens() + print( + f"{Colors.BRIGHT_GREEN}✓ 摘要完成,Token 从 {estimated_tokens} 降至 {new_tokens}{Colors.RESET}" + ) + print( + f"{Colors.DIM} 结构: system + {len(user_indices)} 个 user 消息 + {summary_count} 个 summary{Colors.RESET}" + ) + + async def _create_summary( + self, messages: List[Message], user_idx: int, round_num: int + ) -> str: + """为一轮执行创建摘要 + + Args: + messages: 要总结的消息列表 + user_idx: 用户消息的索引 + round_num: 轮次编号 + + Returns: + 摘要文本 """ - if len(self.messages) > self.max_messages: - self.messages = [ - self.messages[0], # system prompt - *self.messages[-(self.max_messages - 1) :], # 最近的消息 - ] + if not messages: + return "" + + # 构建摘要内容 + summary_content = f"第 {round_num} 轮执行过程:\n\n" + for msg in messages: + if msg.role == "assistant": + content_text = ( + msg.content if isinstance(msg.content, str) else str(msg.content) + ) + summary_content += f"助手: {content_text[:200]}\n" + if msg.tool_calls: + tool_names = [tc["function"]["name"] for tc in msg.tool_calls] + summary_content += f" → 调用工具: {', '.join(tool_names)}\n" + elif msg.role == "tool": + result_preview = ( + msg.content[:100] + if isinstance(msg.content, str) + else str(msg.content)[:100] + ) + summary_content += f" ← 工具返回: {result_preview}...\n" + + # 调用 LLM 生成简洁的摘要 + try: + summary_prompt = f"""请将以下 Agent 执行过程进行简洁总结: + +{summary_content} + +要求: +1. 重点记录完成了什么任务、调用了哪些工具 +2. 保留关键的执行结果和重要发现 +3. 简洁明了,控制在 300 字以内 +4. 使用中文 +5. 不要包含"用户"相关内容,只总结 Agent 的执行过程""" + + summary_msg = Message(role="user", content=summary_prompt) + response = await self.llm.generate( + messages=[ + Message( + role="system", + content="你是一个擅长总结 Agent 执行过程的助手。", + ), + summary_msg, + ] + ) + + summary_text = response.content + print(f"{Colors.BRIGHT_GREEN}✓ 第 {round_num} 轮摘要生成完成{Colors.RESET}") + return summary_text + + except Exception as e: + print( + f"{Colors.BRIGHT_RED}✗ 第 {round_num} 轮摘要生成失败: {e}{Colors.RESET}" + ) + # 失败时使用简单的文本摘要 + return summary_content async def run(self) -> str: """Execute agent loop until task is complete or max steps reached.""" + # 开始新的运行,初始化日志文件 + self.logger.start_new_run() + print( + f"{Colors.DIM}📝 日志文件: {self.logger.get_log_file_path()}{Colors.RESET}" + ) + step = 0 while step < self.max_steps: - # 截断消息历史,防止 context 超限 - self._truncate_messages() + # 检查并摘要消息历史,防止 context 超限 + await self._summarize_messages() # 步骤标题 print(f"\n{Colors.DIM}╭{'─' * 58}╮{Colors.RESET}") @@ -96,6 +293,9 @@ async def run(self) -> str: # Get tool schemas tool_schemas = [tool.to_schema() for tool in self.tools.values()] + # 记录 LLM 请求日志 + self.logger.log_request(messages=self.messages, tools=tool_schemas) + # Call LLM try: response = await self.llm.generate( @@ -118,10 +318,19 @@ async def run(self) -> str: print(f"\n{Colors.BRIGHT_RED}❌ Error:{Colors.RESET} {error_msg}") return error_msg + # 记录 LLM 响应日志 + self.logger.log_response( + content=response.content, + thinking=response.thinking, + tool_calls=response.tool_calls, + finish_reason=response.finish_reason, + ) + # Add assistant message assistant_msg = Message( role="assistant", content=response.content, + thinking=response.thinking, tool_calls=response.tool_calls, ) self.messages.append(assistant_msg) @@ -170,8 +379,29 @@ async def run(self) -> str: error=f"Unknown tool: {function_name}", ) else: - tool = self.tools[function_name] - result = await tool.execute(**arguments) + try: + tool = self.tools[function_name] + result = await tool.execute(**arguments) + except Exception as e: + # 捕获工具执行中的所有异常,转换为失败的 ToolResult + import traceback + + error_detail = f"{type(e).__name__}: {str(e)}" + error_trace = traceback.format_exc() + result = ToolResult( + success=False, + content="", + error=f"Tool execution failed: {error_detail}\n\nTraceback:\n{error_trace}", + ) + + # 记录工具执行结果日志 + self.logger.log_tool_result( + tool_name=function_name, + arguments=arguments, + result_success=result.success, + result_content=result.content if result.success else None, + result_error=result.error if not result.success else None, + ) # Print result if result.success: diff --git a/mini_agent/llm.py b/mini_agent/llm.py index a63a508..2bfa9e8 100644 --- a/mini_agent/llm.py +++ b/mini_agent/llm.py @@ -18,6 +18,7 @@ class Message(BaseModel): role: str # "system", "user", "assistant", "tool" content: str | List[Dict[str, Any]] # Can be string or list of content blocks + thinking: str | None = None # Extended thinking content for assistant messages tool_calls: List[Dict[str, Any]] | None = None tool_call_id: str | None = None name: str | None = None # For tool role @@ -129,23 +130,34 @@ async def generate( # For user and assistant messages if msg.role in ["user", "assistant"]: - # Handle tool results embedded in assistant messages - if msg.role == "assistant" and msg.tool_calls: - # Build content blocks for assistant with tool calls + # Handle assistant messages with thinking or tool calls + if msg.role == "assistant" and (msg.thinking or msg.tool_calls): + # Build content blocks for assistant with thinking and/or tool calls content_blocks = [] + + # Add thinking block if present + if msg.thinking: + content_blocks.append( + {"type": "thinking", "thinking": msg.thinking} + ) + + # Add text content if present if msg.content: content_blocks.append({"type": "text", "text": msg.content}) # Add tool use blocks - for tool_call in msg.tool_calls: - content_blocks.append( - { - "type": "tool_use", - "id": tool_call["id"], - "name": tool_call["function"]["name"], - "input": json.loads(tool_call["function"]["arguments"]), - } - ) + if msg.tool_calls: + for tool_call in msg.tool_calls: + content_blocks.append( + { + "type": "tool_use", + "id": tool_call["id"], + "name": tool_call["function"]["name"], + "input": json.loads( + tool_call["function"]["arguments"] + ), + } + ) api_messages.append( {"role": "assistant", "content": content_blocks} diff --git a/mini_agent/logger.py b/mini_agent/logger.py new file mode 100644 index 0000000..903542f --- /dev/null +++ b/mini_agent/logger.py @@ -0,0 +1,179 @@ +"""Agent 运行日志记录器""" + +import json +from datetime import datetime +from pathlib import Path +from typing import Any, Dict + + +class AgentLogger: + """Agent 运行日志记录器 + + 负责记录每次 agent 运行的完整交互过程,包括: + - LLM 请求和响应 + - 工具调用和结果 + """ + + def __init__(self, workspace_dir: Path): + """初始化日志记录器 + + Args: + workspace_dir: 工作目录路径 + """ + self.workspace_dir = workspace_dir + self.log_file = None + self.log_index = 0 + + def start_new_run(self): + """开始新的运行,创建新的日志文件""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_filename = f"agent_run_{timestamp}.log" + self.log_file = self.workspace_dir / log_filename + self.log_index = 0 + + # 写入日志头部 + with open(self.log_file, "w", encoding="utf-8") as f: + f.write("=" * 80 + "\n") + f.write(f"Agent Run Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write("=" * 80 + "\n\n") + + def log_request(self, messages: list, tools: list = None): + """记录 LLM 请求 + + Args: + messages: 消息列表 + tools: 工具列表(可选) + """ + self.log_index += 1 + + # 构建完整的请求数据结构 + request_data = { + "messages": [], + "tools": [], + } + + # 转换消息为 JSON 可序列化格式 + for msg in messages: + msg_dict = { + "role": msg.role, + "content": msg.content, + } + if msg.thinking: + msg_dict["thinking"] = msg.thinking + if msg.tool_calls: + msg_dict["tool_calls"] = msg.tool_calls + if msg.tool_call_id: + msg_dict["tool_call_id"] = msg.tool_call_id + if msg.name: + msg_dict["name"] = msg.name + + request_data["messages"].append(msg_dict) + + # 只记录工具名称 + if tools: + request_data["tools"] = [ + tool.get("function", {}).get("name", "unknown") for tool in tools + ] + + # 格式化为 JSON + content = "LLM Request:\n\n" + content += json.dumps(request_data, indent=2, ensure_ascii=False) + + self._write_log("REQUEST", content) + + def log_response( + self, + content: str, + thinking: str = None, + tool_calls: list = None, + finish_reason: str = None, + ): + """记录 LLM 响应 + + Args: + content: 响应内容 + thinking: 思考内容(可选) + tool_calls: 工具调用列表(可选) + finish_reason: 完成原因(可选) + """ + self.log_index += 1 + + # 构建完整的响应数据结构 + response_data = { + "content": content, + } + + if thinking: + response_data["thinking"] = thinking + + if tool_calls: + response_data["tool_calls"] = tool_calls + + if finish_reason: + response_data["finish_reason"] = finish_reason + + # 格式化为 JSON + log_content = "LLM Response:\n\n" + log_content += json.dumps(response_data, indent=2, ensure_ascii=False) + + self._write_log("RESPONSE", log_content) + + def log_tool_result( + self, + tool_name: str, + arguments: Dict[str, Any], + result_success: bool, + result_content: str = None, + result_error: str = None, + ): + """记录工具执行结果 + + Args: + tool_name: 工具名称 + arguments: 工具参数 + result_success: 是否成功 + result_content: 结果内容(成功时) + result_error: 错误信息(失败时) + """ + self.log_index += 1 + + # 构建完整的工具执行结果数据结构 + tool_result_data = { + "tool_name": tool_name, + "arguments": arguments, + "success": result_success, + } + + if result_success: + tool_result_data["result"] = result_content + else: + tool_result_data["error"] = result_error + + # 格式化为 JSON + content = "Tool Execution:\n\n" + content += json.dumps(tool_result_data, indent=2, ensure_ascii=False) + + self._write_log("TOOL_RESULT", content) + + def _write_log(self, log_type: str, content: str): + """写入日志条目 + + Args: + log_type: 日志类型(REQUEST, RESPONSE, TOOL_RESULT) + content: 日志内容 + """ + if self.log_file is None: + return + + with open(self.log_file, "a", encoding="utf-8") as f: + f.write("\n" + "-" * 80 + "\n") + f.write(f"[{self.log_index}] {log_type}\n") + f.write( + f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}\n" + ) + f.write("-" * 80 + "\n") + f.write(content + "\n") + + def get_log_file_path(self) -> Path: + """获取当前日志文件路径""" + return self.log_file diff --git a/pyproject.toml b/pyproject.toml index bfdcde6..206c7bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "mcp>=1.0.0", "pytest>=8.4.2", "requests>=2.31.0", + "tiktoken>=0.5.0", ] [project.optional-dependencies] diff --git a/system_prompt.txt b/system_prompt.txt index f1e8ab6..8596a36 100644 --- a/system_prompt.txt +++ b/system_prompt.txt @@ -1,32 +1,64 @@ -You are a helpful AI assistant that can execute tasks by using tools. +You are a versatile AI assistant powered by Claude, capable of executing complex tasks through a rich toolset and specialized skills. -You have access to the following capabilities: -- Read, write, and edit files -- Execute bash commands -- Use additional tools loaded from MCP servers (if configured) +## Core Capabilities -## Guidelines: +### 1. **Basic Tools** +- **File Operations**: Read, write, edit files with full path support +- **Bash Execution**: Run commands, manage git, packages, and system operations +- **MCP Tools**: Access additional tools from configured MCP servers -1. **Task Execution**: Break down user requests into clear steps and execute them using the available tools. +### 2. **Claude Skills** +You have access to 15+ specialized skills that provide expert guidance for specific tasks: -2. **File Operations**: - - Always use absolute paths or paths relative to the workspace directory - - Check if files exist before reading or editing them - - Create necessary directories before writing files +**Available Skills:** +- `list_skills` - 列出所有可用的专业技能 +- `get_skill ` - 获取某个技能的详细指导 +- `use_skill ` - 应用技能来执行特定任务 -3. **Bash Commands**: - - Use bash tool for system operations, git commands, package management, etc. - - Be careful with destructive commands - explain what you're doing - - Check command outputs for errors +**Skill Categories:** +- 🎨 **Creative & Design**: algorithmic-art, canvas-design, slack-gif-creator +- 💻 **Development**: artifacts-builder, mcp-builder, webapp-testing +- 📄 **Documents**: docx, pdf, pptx, xlsx (create/edit/analyze) +- 🏢 **Enterprise**: brand-guidelines, internal-comms, theme-factory +- 🛠️ **Meta**: skill-creator, template-skill -4. **Communication**: - - Be concise and clear in your responses - - Explain what you're doing before executing tools - - Report errors clearly and suggest solutions +**When to Use Skills:** +- When facing specialized tasks (e.g., creating PDFs, building web apps) +- For following best practices in specific domains +- When you need expert patterns and guidelines -5. **Task Completion**: - - When the task is complete, provide a summary of what was accomplished - - Do not call tools unnecessarily - - Stop when the user's request has been fulfilled +## Working Guidelines -Remember: You are working in a workspace directory. All file operations should be relative to this directory unless specified otherwise. +### Task Execution +1. **Analyze** the request and identify if a skill can help +2. **Break down** complex tasks into clear, executable steps +3. **Use skills** when appropriate for specialized guidance +4. **Execute** tools systematically and check results +5. **Report** progress and any issues encountered + +### File Operations +- Use absolute paths or workspace-relative paths +- Verify file existence before reading/editing +- Create parent directories before writing files +- Handle errors gracefully with clear messages + +### Bash Commands +- Explain destructive operations before execution +- Check command outputs for errors +- Use appropriate error handling +- Prefer specialized tools over raw commands when available + +### Communication +- Be concise but thorough in responses +- Explain your approach before tool execution +- Report errors with context and solutions +- Summarize accomplishments when complete + +### Best Practices +- **Don't guess** - use tools to discover missing information +- **Be proactive** - infer intent and take reasonable actions +- **Stay focused** - stop when the task is fulfilled +- **Use skills** - leverage specialized knowledge when relevant + +## Workspace Context +You are working in a workspace directory. All operations are relative to this context unless absolute paths are specified. diff --git a/uv.lock b/uv.lock index 458dbde..f683b7e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,46 +2,6 @@ version = 1 revision = 1 requires-python = ">=3.10" -[[package]] -name = "mini-agent" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "httpx" }, - { name = "mcp" }, - { name = "pydantic" }, - { name = "pytest" }, - { name = "pyyaml" }, - { name = "requests" }, -] - -[package.optional-dependencies] -dev = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest-asyncio" }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.27.0" }, - { name = "mcp", specifier = ">=1.0.0" }, - { name = "pydantic", specifier = ">=2.0.0" }, - { name = "pytest", specifier = ">=8.4.2" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" }, - { name = "pyyaml", specifier = ">=6.0.0" }, - { name = "requests", specifier = ">=2.31.0" }, -] -provides-extras = ["dev"] - -[package.metadata.requires-dev] -dev = [{ name = "pytest-asyncio", specifier = ">=1.2.0" }] - [[package]] name = "annotated-types" version = "0.7.0" @@ -328,6 +288,48 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/a3/3e71a875a08b6a830b88c40bc413bff01f1650f1efe8a054b5e90a9d4f56/mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572", size = 170105 }, ] +[[package]] +name = "mini-agent" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "httpx" }, + { name = "mcp" }, + { name = "pydantic" }, + { name = "pytest" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tiktoken" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest-asyncio" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.27.0" }, + { name = "mcp", specifier = ">=1.0.0" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pytest", specifier = ">=8.4.2" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" }, + { name = "pyyaml", specifier = ">=6.0.0" }, + { name = "requests", specifier = ">=2.31.0" }, + { name = "tiktoken", specifier = ">=0.5.0" }, +] +provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [{ name = "pytest-asyncio", specifier = ">=1.2.0" }] + [[package]] name = "packaging" version = "25.0" @@ -648,6 +650,113 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, ] +[[package]] +name = "regex" +version = "2025.10.23" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/c8/1d2160d36b11fbe0a61acb7c3c81ab032d9ec8ad888ac9e0a61b85ab99dd/regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26", size = 401266 } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/11/849d5d23633a77047465eaae4cc0cbf24ded7aa496c02e8b9710e28b1687/regex-2025.10.23-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:17bbcde374bef1c5fad9b131f0e28a6a24856dd90368d8c0201e2b5a69533daa", size = 487957 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/12/5985386e7e3200a0d6a6417026d2c758d783a932428a5efc0a42ca1ddf74/regex-2025.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4e10434279cc8567f99ca6e018e9025d14f2fded2a603380b6be2090f476426", size = 290419 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/cf/a8615923f962f8fdc41a3a6093a48726955e8b1993f4614b26a41d249f9b/regex-2025.10.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c9bb421cbe7012c744a5a56cf4d6c80829c72edb1a2991677299c988d6339c8", size = 288285 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/3d/6a3a1e12c86354cd0b3cbf8c3dd6acbe853609ee3b39d47ecd3ce95caf84/regex-2025.10.23-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275cd1c2ed8c4a78ebfa489618d7aee762e8b4732da73573c3e38236ec5f65de", size = 781458 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/47/76a8da004489f2700361754859e373b87a53d043de8c47f4d1583fd39d78/regex-2025.10.23-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b426ae7952f3dc1e73a86056d520bd4e5f021397484a6835902fc5648bcacce", size = 850605 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/05/fa886461f97d45a6f4b209699cb994dc6d6212d6e219d29444dac5005775/regex-2025.10.23-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5cdaf5b6d37c7da1967dbe729d819461aab6a98a072feef65bbcff0a6e60649", size = 898563 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/db/3ddd8d01455f23cabad7499f4199de0df92f5e96d39633203ff9d0b592dc/regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bfeff0b08f296ab28b4332a7e03ca31c437ee78b541ebc874bbf540e5932f8d", size = 791535 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/ae/0fa5cbf41ca92b6ec3370222fcb6c68b240d68ab10e803d086c03a19fd9e/regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f97236a67307b775f30a74ef722b64b38b7ab7ba3bb4a2508518a5de545459c", size = 782461 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/23/70af22a016df11af4def27870eb175c2c7235b72d411ecf75a4b4a422cb6/regex-2025.10.23-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be19e7de499940cd72475fb8e46ab2ecb1cf5906bebdd18a89f9329afb1df82f", size = 774583 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/ee/a54a6851f6905f33d3c4ed64e8737b1d85ed01b5724712530ddc0f9abdb1/regex-2025.10.23-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:883df76ee42d9ecb82b37ff8d01caea5895b3f49630a64d21111078bbf8ef64c", size = 845649 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/7d/c3ec1cae14e01fab00e38c41ed35f47a853359e95e9c023e9a4381bb122c/regex-2025.10.23-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2e9117d1d35fc2addae6281019ecc70dc21c30014b0004f657558b91c6a8f1a7", size = 836037 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/ae/45771140dd43c4d67c87b54d3728078ed6a96599d9fc7ba6825086236782/regex-2025.10.23-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ff1307f531a5d8cf5c20ea517254551ff0a8dc722193aab66c656c5a900ea68", size = 779705 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/95/074e2581760eafce7c816a352b7d3a322536e5b68c346d1a8bacd895545c/regex-2025.10.23-cp310-cp310-win32.whl", hash = "sha256:7888475787cbfee4a7cd32998eeffe9a28129fa44ae0f691b96cb3939183ef41", size = 265663 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/c7/a25f56a718847e34d3f1608c72eadeb67653bff1a0411da023dd8f4c647b/regex-2025.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ec41a905908496ce4906dab20fb103c814558db1d69afc12c2f384549c17936a", size = 277587 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/e5/63eb17c6b5deaefd93c2bbb1feae7c0a8d2157da25883a6ca2569cf7a663/regex-2025.10.23-cp310-cp310-win_arm64.whl", hash = "sha256:b2b7f19a764d5e966d5a62bf2c28a8b4093cc864c6734510bdb4aeb840aec5e6", size = 269979 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/e5/74b7cd5cd76b4171f9793042045bb1726f7856dd56e582fc3e058a7a8a5e/regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd", size = 487960 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/08/854fa4b3b20471d1df1c71e831b6a1aa480281e37791e52a2df9641ec5c6/regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb", size = 290425 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/d3/6272b1dd3ca1271661e168762b234ad3e00dbdf4ef0c7b9b72d2d159efa7/regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f", size = 288278 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/8f/c7b365dd9d9bc0a36e018cb96f2ffb60d2ba8deb589a712b437f67de2920/regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33", size = 793289 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/fb/b8fbe9aa16cf0c21f45ec5a6c74b4cecbf1a1c0deb7089d4a6f83a9c1caa/regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca", size = 860321 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/81/bf41405c772324926a9bd8a640dedaa42da0e929241834dfce0733070437/regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490", size = 907011 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/fb/5ad6a8b92d3f88f3797b51bb4ef47499acc2d0b53d2fbe4487a892f37a73/regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2", size = 800312 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/48/b4efba0168a2b57f944205d823f8e8a3a1ae6211a34508f014ec2c712f4f/regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40", size = 782839 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/2a/c9efb4c6c535b0559c1fa8e431e0574d229707c9ca718600366fcfef6801/regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82", size = 854270 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/2d/68eecc1bdaee020e8ba549502291c9450d90d8590d0552247c9b543ebf7b/regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83", size = 845771 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/cd/a1ae499cf9b87afb47a67316bbf1037a7c681ffe447c510ed98c0aa2c01c/regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd", size = 788778 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/f9/70765e63f5ea7d43b2b6cd4ee9d3323f16267e530fb2a420d92d991cf0fc/regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575", size = 265666 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/1a/18e9476ee1b63aaec3844d8e1cb21842dc19272c7e86d879bfc0dcc60db3/regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8", size = 277600 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/1b/c019167b1f7a8ec77251457e3ff0339ed74ca8bce1ea13138dc98309c923/regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566", size = 269974 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/57/eeb274d83ab189d02d778851b1ac478477522a92b52edfa6e2ae9ff84679/regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc", size = 489187 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/5c/7dad43a9b6ea88bf77e0b8b7729a4c36978e1043165034212fd2702880c6/regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2", size = 291122 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/21/38b71e6f2818f0f4b281c8fba8d9d57cfca7b032a648fa59696e0a54376a/regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45", size = 288797 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/95/888f069c89e7729732a6d7cca37f76b44bfb53a1e35dda8a2c7b65c1b992/regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e", size = 798442 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/70/4f903c608faf786627a8ee17c06e0067b5acade473678b69c8094b248705/regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43", size = 864039 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/19/2df67b526bf25756c7f447dde554fc10a220fd839cc642f50857d01e4a7b/regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca", size = 912057 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/14/9a39b7c9e007968411bc3c843cc14cf15437510c0a9991f080cab654fd16/regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc", size = 803374 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/f7/3495151dd3ca79949599b6d069b72a61a2c5e24fc441dccc79dcaf708fe6/regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a", size = 787714 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/65/ee882455e051131869957ee8597faea45188c9a98c0dad724cfb302d4580/regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32", size = 858392 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/25/9287fef5be97529ebd3ac79d256159cb709a07eb58d4be780d1ca3885da8/regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288", size = 850484 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/b4/b49b88b4fea2f14dc73e5b5842755e782fc2e52f74423d6f4adc130d5880/regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147", size = 789634 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/3c/2f8d199d0e84e78bcd6bdc2be9b62410624f6b796e2893d1837ae738b160/regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079", size = 266060 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/67/c35e80969f6ded306ad70b0698863310bdf36aca57ad792f45ddc0e2271f/regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842", size = 276931 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/a1/4ed147de7d2b60174f758412c87fa51ada15cd3296a0ff047f4280aaa7ca/regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335", size = 270103 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/c6/195a6217a43719d5a6a12cc192a22d12c40290cecfa577f00f4fb822f07d/regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193", size = 488956 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/93/181070cd1aa2fa541ff2d3afcf763ceecd4937b34c615fa92765020a6c90/regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b", size = 290997 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/c5/9d37fbe3a40ed8dda78c23e1263002497540c0d1522ed75482ef6c2000f0/regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f", size = 288686 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/e7/db610ff9f10c2921f9b6ac0c8d8be4681b28ddd40fc0549429366967e61f/regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a", size = 798466 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/10/aab883e1fa7fe2feb15ac663026e70ca0ae1411efa0c7a4a0342d9545015/regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b", size = 863996 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/b0/8f686dd97a51f3b37d0238cd00a6d0f9ccabe701f05b56de1918571d0d61/regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb", size = 912145 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/ca/639f8cd5b08797bca38fc5e7e07f76641a428cf8c7fca05894caf045aa32/regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368", size = 803370 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/1e/a40725bb76959eddf8abc42a967bed6f4851b39f5ac4f20e9794d7832aa5/regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351", size = 787767 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/d8/8ee9858062936b0f99656dce390aa667c6e7fb0c357b1b9bf76fb5e2e708/regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673", size = 858335 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/0a/ed5faaa63fa8e3064ab670e08061fbf09e3a10235b19630cf0cbb9e48c0a/regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313", size = 850402 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/14/d05f617342f4b2b4a23561da500ca2beab062bfcc408d60680e77ecaf04d/regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87", size = 789739 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/7b/e8ce8eef42a15f2c3461f8b3e6e924bbc86e9605cb534a393aadc8d3aff8/regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1", size = 266054 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/2d/55184ed6be6473187868d2f2e6a0708195fc58270e62a22cbf26028f2570/regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0", size = 276917 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/d4/927eced0e2bd45c45839e556f987f8c8f8683268dd3c00ad327deb3b0172/regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f", size = 270105 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/b3/95b310605285573341fc062d1d30b19a54f857530e86c805f942c4ff7941/regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044", size = 491850 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/8f/207c2cec01e34e56db1eff606eef46644a60cf1739ecd474627db90ad90b/regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc", size = 292537 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/3b/025240af4ada1dc0b5f10d73f3e5122d04ce7f8908ab8881e5d82b9d61b6/regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656", size = 290904 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/8e/104ac14e2d3450c43db18ec03e1b96b445a94ae510b60138f00ce2cb7ca1/regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58", size = 807311 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/63/78aef90141b7ce0be8a18e1782f764f6997ad09de0e05251f0d2503a914a/regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e", size = 873241 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/a8/80eb1201bb49ae4dba68a1b284b4211ed9daa8e74dc600018a10a90399fb/regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a", size = 914794 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/d5/1984b6ee93281f360a119a5ca1af6a8ca7d8417861671388bf750becc29b/regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e", size = 812581 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/39/11ebdc6d9927172a64ae237d16763145db6bd45ebb4055c17b88edab72a7/regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8", size = 795346 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/b4/89a591bcc08b5e436af43315284bd233ba77daf0cf20e098d7af12f006c1/regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6", size = 868214 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/ff/58ba98409c1dbc8316cdb20dafbc63ed267380a07780cafecaf5012dabc9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8", size = 854540 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/f2/4a9e9338d67626e2071b643f828a482712ad15889d7268e11e9a63d6f7e9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494", size = 799346 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/be/543d35c46bebf6f7bf2be538cca74d6585f25714700c36f37f01b92df551/regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9", size = 268657 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/9f/4dd6b7b612037158bb2c9bcaa710e6fb3c40ad54af441b9c53b3a137a9f1/regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6", size = 280075 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/7a/5bd0672aa65d38c8da6747c17c8b441bdb53d816c569e3261013af8e83cf/regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473", size = 271219 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/f6/0caf29fec943f201fbc8822879c99d31e59c1d51a983d9843ee5cf398539/regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6", size = 488960 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/7d/ebb7085b8fa31c24ce0355107cea2b92229d9050552a01c5d291c42aecea/regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5", size = 290932 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/41/43906867287cbb5ca4cee671c3cc8081e15deef86a8189c3aad9ac9f6b4d/regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10", size = 288766 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/9e/ea66132776700fc77a39b1056e7a5f1308032fead94507e208dc6716b7cd/regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34", size = 798884 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/99/aed1453687ab63819a443930770db972c5c8064421f0d9f5da9ad029f26b/regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a", size = 864768 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/5d/732fe747a1304805eb3853ce6337eea16b169f7105a0d0dd9c6a5ffa9948/regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c", size = 911394 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/48/58a1f6623466522352a6efa153b9a3714fc559d9f930e9bc947b4a88a2c3/regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224", size = 803145 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/f6/7dea79be2681a5574ab3fc237aa53b2c1dfd6bd2b44d4640b6c76f33f4c1/regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4", size = 787831 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/ad/07b76950fbbe65f88120ca2d8d845047c401450f607c99ed38862904671d/regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462", size = 859162 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/87/374f3b2021b22aa6a4fc0b750d63f9721e53d1631a238f7a1c343c1cd288/regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627", size = 849899 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/4a/7f7bb17c5a5a9747249807210e348450dab9212a46ae6d23ebce86ba6a2b/regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec", size = 789372 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/dd/9c7728ff544fea09bbc8635e4c9e7c423b11c24f1a7a14e6ac4831466709/regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8", size = 271451 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/f8/ef7837ff858eb74079c4804c10b0403c0b740762e6eedba41062225f7117/regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796", size = 280173 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/d0/d576e1dbd9885bfcd83d0e90762beea48d9373a6f7ed39170f44ed22e336/regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9", size = 273206 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/d0/2025268315e8b2b7b660039824cb7765a41623e97d4cd421510925400487/regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422", size = 491854 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/35/5681c2fec5e8b33454390af209c4353dfc44606bf06d714b0b8bd0454ffe/regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788", size = 292542 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/17/184eed05543b724132e4a18149e900f5189001fcfe2d64edaae4fbaf36b4/regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10", size = 290903 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/d0/5e3347aa0db0de382dddfa133a7b0ae72f24b4344f3989398980b44a3924/regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7", size = 807546 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/bb/40c589bbdce1be0c55e9f8159789d58d47a22014f2f820cf2b517a5cd193/regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f", size = 873322 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/56/a7e40c01575ac93360e606278d359f91829781a9f7fb6e5aa435039edbda/regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61", size = 914855 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/4b/d55587b192763db3163c3f508b3b67b31bb6f5e7a0e08b83013d0a59500a/regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c", size = 812724 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/20/18bac334955fbe99d17229f4f8e98d05e4a501ac03a442be8facbb37c304/regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432", size = 795439 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/46/c57266be9df8549c7d85deb4cb82280cb0019e46fff677534c5fa1badfa4/regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e", size = 868336 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/f3/bd5879e41ef8187fec5e678e94b526a93f99e7bbe0437b0f2b47f9101694/regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43", size = 854567 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/57/2b6bbdbd2f24dfed5b028033aa17ad8f7d86bb28f1a892cac8b3bc89d059/regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3", size = 799565 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/ba/a6168f542ba73b151ed81237adf6b869c7b2f7f8d51618111296674e20ee/regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521", size = 274428 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/a0/c84475e14a2829e9b0864ebf77c3f7da909df9d8acfe2bb540ff0072047c/regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741", size = 284140 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/33/6a08ade0eee5b8ba79386869fa6f77afeb835b60510f3525db987e2fffc4/regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed", size = 274497 }, +] + [[package]] name = "requests" version = "2.32.5" @@ -819,6 +928,67 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736 }, ] +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806 } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/b3/2cb7c17b6c4cf8ca983204255d3f1d95eda7213e247e6947a0ee2c747a2c/tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970", size = 1051991 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/0f/df139f1df5f6167194ee5ab24634582ba9a1b62c6b996472b0277ec80f66/tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16", size = 995798 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/5d/26a691f28ab220d5edc09b9b787399b130f24327ef824de15e5d85ef21aa/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030", size = 1129865 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/94/443fab3d4e5ebecac895712abd3849b8da93b7b7dec61c7db5c9c7ebe40c/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134", size = 1152856 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/35/388f941251b2521c70dd4c5958e598ea6d2c88e28445d2fb8189eecc1dfc/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a", size = 1195308 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/00/c6681c7f833dd410576183715a530437a9873fa910265817081f65f9105f/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892", size = 1255697 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/d2/82e795a6a9bafa034bf26a58e68fe9a89eeaaa610d51dbeb22106ba04f0a/tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1", size = 879375 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706 }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667 }, +] + [[package]] name = "tomli" version = "2.3.0"