feat: 实现基于Agno框架的SubAgent系统

- 新增SubAgent核心类,支持多LLM提供商
- 实现动态prompt模板构建功能
- 添加JSON结构化输出和零容错解析
- 集成配置管理和模型工厂模式
- 提供完整的错误处理和日志系统
- 支持阿里云、DeepSeek、OpenAI等主流LLM服务
This commit is contained in:
iomgaa 2025-08-25 17:33:11 +08:00
parent 099159dfb7
commit f7a06775ca
9 changed files with 2044 additions and 1 deletions

View File

@ -5,5 +5,10 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"agno>=1.7.12",
"ollama>=0.5.3",
"openai>=1.101.0",
"pydantic",
"pyyaml>=6.0.2",
"requests>=2.32.5",
]

View File

@ -0,0 +1,38 @@
"""
Agent System Module
基于Agno框架的SubAgent系统实现
为MedResearcher项目提供智能Agent功能
"""
__version__ = "0.1.0"
# 导入核心类
from .subagent import SubAgent, create_json_agent
from .config_loader import load_llm_config, get_model_config
from .model_factory import create_agno_model, list_available_models
from .json_processor import parse_json_response, JSONProcessor
# 导入异常类
from .subagent import SubAgentError, ConfigurationError, ModelError
from .json_processor import JSONParseError
__all__ = [
# 核心类
'SubAgent',
'JSONProcessor',
# 便捷函数
'create_json_agent',
'load_llm_config',
'get_model_config',
'create_agno_model',
'list_available_models',
'parse_json_response',
# 异常类
'SubAgentError',
'ConfigurationError',
'ModelError',
'JSONParseError',
]

View File

@ -0,0 +1,292 @@
"""
配置加载模块
负责解析LLM配置文件和环境变量为SubAgent提供统一的配置接口
"""
import os
import yaml
from typing import Dict, Any, Optional
from pathlib import Path
def load_llm_config(config_path: Optional[str] = None) -> Dict[str, Any]:
"""
加载LLM配置文件和环境变量
Args:
config_path: 配置文件路径默认为 src/config/llm_config.yaml
Returns:
完整的LLM配置字典包含所有提供商和环境变量
Raises:
FileNotFoundError: 配置文件不存在
yaml.YAMLError: YAML解析错误
ValueError: 配置格式错误
"""
# 确定配置文件路径
if config_path is None:
# 获取项目根目录
current_dir = Path(__file__).parent
project_root = current_dir.parent.parent
config_path = project_root / "src" / "config" / "llm_config.yaml"
config_path = Path(config_path)
# 检查配置文件是否存在
if not config_path.exists():
raise FileNotFoundError(f"LLM配置文件不存在: {config_path}")
# 读取YAML配置文件
try:
with open(config_path, 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
except yaml.YAMLError as e:
raise yaml.YAMLError(f"YAML配置文件解析失败: {e}")
except Exception as e:
raise ValueError(f"读取配置文件失败: {e}")
# 验证配置结构
if not isinstance(config, dict):
raise ValueError("配置文件格式错误:根元素必须是字典")
# 加载环境变量配置
env_config = load_env_config()
# 处理环境变量替换
config = _resolve_env_variables(config, env_config)
return config
def load_env_config(env_path: Optional[str] = None) -> Dict[str, str]:
"""
加载环境变量配置
Args:
env_path: .env文件路径默认为 src/config/.env
Returns:
环境变量字典
"""
# 确定环境变量文件路径
if env_path is None:
current_dir = Path(__file__).parent
project_root = current_dir.parent.parent
env_path = project_root / "src" / "config" / ".env"
env_config = {}
# 尝试加载.env文件
env_path = Path(env_path)
if env_path.exists():
try:
with open(env_path, 'r', encoding='utf-8') as file:
for line in file:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
env_config[key.strip()] = value.strip()
except Exception as e:
print(f"警告: 读取.env文件失败: {e}")
# 同时从系统环境变量中加载
env_keys = ['DASHSCOPE_API_KEY', 'OPENAI_API_KEY', 'DEEPSEEK_API_KEY']
for key in env_keys:
if key in os.environ:
env_config[key] = os.environ[key]
return env_config
def _resolve_env_variables(config: Dict[str, Any], env_config: Dict[str, str]) -> Dict[str, Any]:
"""
解析配置中的环境变量占位符
Args:
config: 原始配置
env_config: 环境变量配置
Returns:
解析后的配置
"""
def resolve_value(value):
if isinstance(value, str) and value.startswith('${') and value.endswith('}'):
# 提取环境变量名称
env_var = value[2:-1] # 去掉 ${ 和 }
if env_var in env_config:
return env_config[env_var]
elif env_var in os.environ:
return os.environ[env_var]
else:
print(f"警告: 环境变量 {env_var} 未定义,保持原值")
return value
elif isinstance(value, dict):
return {k: resolve_value(v) for k, v in value.items()}
elif isinstance(value, list):
return [resolve_value(item) for item in value]
else:
return value
return resolve_value(config)
def get_provider_config(config: Dict[str, Any], provider: str) -> Dict[str, Any]:
"""
获取特定提供商的配置
Args:
config: 完整的LLM配置
provider: 提供商名称 ( 'aliyun', 'deepseek', 'openai')
Returns:
提供商配置字典
Raises:
ValueError: 提供商不存在
"""
if provider not in config:
available_providers = list(config.keys())
raise ValueError(f"提供商 '{provider}' 不存在,可用提供商: {available_providers}")
return config[provider]
def get_model_config(config: Dict[str, Any], provider: str, model_name: str) -> Dict[str, Any]:
"""
获取特定模型的配置
Args:
config: 完整的LLM配置
provider: 提供商名称
model_name: 模型名称
Returns:
模型配置字典
Raises:
ValueError: 提供商或模型不存在
"""
provider_config = get_provider_config(config, provider)
if 'models' not in provider_config:
raise ValueError(f"提供商 '{provider}' 配置中缺少 models 字段")
models = provider_config['models']
if model_name not in models:
available_models = list(models.keys())
raise ValueError(f"模型 '{model_name}' 在提供商 '{provider}' 中不存在,可用模型: {available_models}")
model_config = models[model_name].copy()
# 添加提供商级别的配置
for key in ['base_url', 'api_key']:
if key in provider_config:
model_config[key] = provider_config[key]
return model_config
def validate_config(config: Dict[str, Any]) -> bool:
"""
验证配置文件的完整性
Args:
config: LLM配置字典
Returns:
验证是否通过
"""
required_providers = ['aliyun', 'deepseek', 'openai']
missing_providers = []
for provider in required_providers:
if provider not in config:
missing_providers.append(provider)
if missing_providers:
print(f"警告: 缺少提供商配置: {missing_providers}")
# 验证每个提供商的配置结构
valid = True
for provider_name, provider_config in config.items():
if not isinstance(provider_config, dict):
print(f"错误: 提供商 '{provider_name}' 配置必须是字典")
valid = False
continue
if 'models' not in provider_config:
print(f"错误: 提供商 '{provider_name}' 缺少 models 字段")
valid = False
continue
models = provider_config['models']
if not isinstance(models, dict) or not models:
print(f"错误: 提供商 '{provider_name}' 的 models 必须是非空字典")
valid = False
continue
# 验证每个模型配置
for model_name, model_config in models.items():
if not isinstance(model_config, dict):
print(f"错误: 模型 '{model_name}' 配置必须是字典")
valid = False
continue
required_fields = ['class', 'params']
for field in required_fields:
if field not in model_config:
print(f"错误: 模型 '{model_name}' 缺少必需字段: {field}")
valid = False
return valid
# 测试函数
def test_config_loading():
"""测试配置加载功能"""
try:
print("正在测试配置加载...")
# 加载配置
config = load_llm_config()
print(f"✅ 配置加载成功,找到 {len(config)} 个提供商")
# 验证配置
is_valid = validate_config(config)
print(f"✅ 配置验证: {'通过' if is_valid else '失败'}")
# 测试提供商配置获取
try:
aliyun_config = get_provider_config(config, 'aliyun')
print(f"✅ 阿里云配置获取成功,包含 {len(aliyun_config.get('models', {}))} 个模型")
except ValueError as e:
print(f"❌ 阿里云配置获取失败: {e}")
# 测试模型配置获取
try:
qwen_config = get_model_config(config, 'aliyun', 'qwen-max')
print(f"✅ qwen-max模型配置获取成功")
print(f" 模型类: {qwen_config.get('class', 'N/A')}")
print(f" 模型ID: {qwen_config.get('params', {}).get('id', 'N/A')}")
except ValueError as e:
print(f"❌ qwen-max模型配置获取失败: {e}")
return True
except Exception as e:
print(f"❌ 配置加载测试失败: {e}")
return False
if __name__ == "__main__":
test_config_loading()

View File

@ -0,0 +1,404 @@
"""
JSON处理器模块
提供强大的JSON解析和验证功能支持零容错解析和Pydantic模型集成
"""
import json
import re
from typing import Any, Dict, List, Optional, Type, Union, get_origin, get_args
from pydantic import BaseModel, ValidationError
class JSONParseError(Exception):
"""JSON解析错误"""
pass
class JSONProcessor:
"""
JSON处理器类
提供多种JSON解析策略确保即使在不完美的JSON输出下也能成功解析
"""
def __init__(self):
"""初始化JSON处理器"""
pass
def parse_json_response(
self,
response: str,
response_model: Optional[Type[BaseModel]] = None
) -> Union[Dict, List, BaseModel]:
"""
解析JSON响应
Args:
response: 响应字符串
response_model: 可选的Pydantic模型类
Returns:
解析后的数据对象或模型实例
Raises:
JSONParseError: 解析失败时抛出
"""
if not response or not response.strip():
raise JSONParseError("响应为空无法解析JSON")
# 清理响应字符串
cleaned_response = self._clean_response(response)
# 尝试多种解析策略
parsed_data = self._try_multiple_parsing_strategies(cleaned_response)
if parsed_data is None:
raise JSONParseError(f"所有解析策略都失败了,响应内容: {response[:200]}...")
# 如果指定了响应模型,尝试创建模型实例
if response_model is not None:
return self._create_model_instance(parsed_data, response_model)
return parsed_data
def _clean_response(self, response: str) -> str:
"""
清理响应字符串
Args:
response: 原始响应
Returns:
清理后的响应
"""
# 去除首尾空白
cleaned = response.strip()
# 移除可能的markdown代码块标记
if cleaned.startswith('```json'):
cleaned = cleaned[7:]
elif cleaned.startswith('```'):
cleaned = cleaned[3:]
if cleaned.endswith('```'):
cleaned = cleaned[:-3]
# 移除可能的额外引号包装
if cleaned.startswith('"') and cleaned.endswith('"'):
try:
# 尝试解析为字符串看是否是被引号包装的JSON
unquoted = json.loads(cleaned)
if isinstance(unquoted, str):
cleaned = unquoted
except json.JSONDecodeError:
pass
return cleaned.strip()
def _try_multiple_parsing_strategies(self, response: str) -> Optional[Union[Dict, List]]:
"""
尝试多种解析策略
Args:
response: 清理后的响应
Returns:
解析成功的数据或None
"""
strategies = [
self._strategy_direct_parse,
self._strategy_extract_json_block,
self._strategy_find_json_structure,
self._strategy_regex_extract,
self._strategy_fix_common_errors,
]
for strategy in strategies:
try:
result = strategy(response)
if result is not None:
return result
except Exception:
continue
return None
def _strategy_direct_parse(self, response: str) -> Optional[Union[Dict, List]]:
"""策略1: 直接解析JSON"""
try:
return json.loads(response)
except json.JSONDecodeError:
return None
def _strategy_extract_json_block(self, response: str) -> Optional[Union[Dict, List]]:
"""策略2: 提取JSON代码块"""
# 查找被代码块包围的JSON
code_block_pattern = r'```(?:json)?\s*([\s\S]*?)\s*```'
matches = re.findall(code_block_pattern, response, re.IGNORECASE)
for match in matches:
try:
return json.loads(match.strip())
except json.JSONDecodeError:
continue
return None
def _strategy_find_json_structure(self, response: str) -> Optional[Union[Dict, List]]:
"""策略3: 查找JSON结构"""
# 查找第一个完整的JSON对象或数组
for start_char, end_char in [("{", "}"), ("[", "]")]:
json_str = self._extract_complete_json_structure(response, start_char, end_char)
if json_str:
try:
return json.loads(json_str)
except json.JSONDecodeError:
continue
return None
def _strategy_regex_extract(self, response: str) -> Optional[Union[Dict, List]]:
"""策略4: 正则表达式提取"""
# 使用正则表达式查找JSON模式
patterns = [
r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', # 嵌套对象
r'\[[^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*\]', # 嵌套数组
]
for pattern in patterns:
matches = re.findall(pattern, response)
for match in matches:
try:
return json.loads(match)
except json.JSONDecodeError:
continue
return None
def _strategy_fix_common_errors(self, response: str) -> Optional[Union[Dict, List]]:
"""策略5: 修复常见JSON错误"""
# 修复常见的JSON格式错误
fixed_response = response
# 修复单引号为双引号
fixed_response = re.sub(r"'([^']*)':", r'"\1":', fixed_response)
fixed_response = re.sub(r":\s*'([^']*)'", r': "\1"', fixed_response)
# 修复末尾逗号
fixed_response = re.sub(r',(\s*[}\]])', r'\1', fixed_response)
# 修复未引用的键
fixed_response = re.sub(r'(\w+):', r'"\1":', fixed_response)
try:
return json.loads(fixed_response)
except json.JSONDecodeError:
return None
def _extract_complete_json_structure(
self,
text: str,
start_char: str,
end_char: str
) -> Optional[str]:
"""
提取完整的JSON结构
Args:
text: 文本内容
start_char: 开始字符 ('{' '[')
end_char: 结束字符 ('}' ']')
Returns:
提取的JSON字符串或None
"""
start_idx = text.find(start_char)
if start_idx == -1:
return None
# 使用计数器匹配嵌套结构
count = 0
in_string = False
escape_next = False
for i, char in enumerate(text[start_idx:], start_idx):
if escape_next:
escape_next = False
continue
if char == '\\' and in_string:
escape_next = True
continue
if char == '"' and not escape_next:
in_string = not in_string
continue
if not in_string:
if char == start_char:
count += 1
elif char == end_char:
count -= 1
if count == 0:
return text[start_idx:i+1]
# 如果没有找到匹配的结束符,返回到字符串末尾
if count > 0:
return text[start_idx:]
return None
def _create_model_instance(
self,
data: Union[Dict, List],
response_model: Type[BaseModel]
) -> BaseModel:
"""
创建Pydantic模型实例
Args:
data: 解析后的数据
response_model: Pydantic模型类
Returns:
模型实例
Raises:
JSONParseError: 模型验证失败
"""
try:
# 如果数据是列表但模型期望对象,尝试包装
if isinstance(data, list) and not self._is_list_model(response_model):
# 尝试将列表作为某个字段的值
field_names = list(response_model.model_fields.keys())
if field_names:
# 使用第一个字段名作为包装器
data = {field_names[0]: data}
# 创建模型实例
return response_model(**data)
except ValidationError as e:
# 详细的验证错误信息
error_msg = f"Pydantic模型验证失败: {e}"
raise JSONParseError(error_msg)
except TypeError as e:
error_msg = f"模型实例化失败: {e}"
raise JSONParseError(error_msg)
def _is_list_model(self, model: Type[BaseModel]) -> bool:
"""
检查模型是否期望列表类型
Args:
model: Pydantic模型类
Returns:
是否为列表模型
"""
# 检查模型的字段类型
for field_info in model.model_fields.values():
annotation = field_info.annotation
origin = get_origin(annotation)
# 如果有字段是List类型认为这是一个列表模型
if origin is list or origin is List:
return True
return False
def validate_json_schema(self, data: Dict, schema: Dict) -> bool:
"""
验证JSON数据是否符合指定的schema
Args:
data: JSON数据
schema: JSON schema
Returns:
是否符合schema
"""
try:
import jsonschema
jsonschema.validate(data, schema)
return True
except ImportError:
# 如果没有安装jsonschema库跳过验证
return True
except Exception:
return False
# 便捷函数
def parse_json_response(
response: str,
response_model: Optional[Type[BaseModel]] = None
) -> Union[Dict, List, BaseModel]:
"""
便捷函数解析JSON响应
Args:
response: 响应字符串
response_model: 可选的Pydantic模型类
Returns:
解析后的数据或模型实例
"""
processor = JSONProcessor()
return processor.parse_json_response(response, response_model)
# 测试函数
def test_json_processor():
"""测试JSON处理器功能"""
print("正在测试JSON处理器...")
processor = JSONProcessor()
# 测试用例
test_cases = [
# 标准JSON
('{"name": "test", "value": 123}', None),
# 带代码块标记的JSON
('```json\n{"name": "test", "value": 123}\n```', None),
# 不完整的JSON
('{"name": "test", "value": 123', None),
# 单引号JSON
("{'name': 'test', 'value': 123}", None),
# 包含额外文本的JSON
('这是一个JSON响应: {"name": "test", "value": 123} 解析完成', None),
# 数组JSON
('[{"name": "test1"}, {"name": "test2"}]', None),
]
success_count = 0
for i, (test_input, model) in enumerate(test_cases, 1):
try:
result = processor.parse_json_response(test_input, model)
print(f"✅ 测试用例 {i}: 解析成功 - {type(result)}")
success_count += 1
except Exception as e:
print(f"❌ 测试用例 {i}: 解析失败 - {e}")
print(f"\n🎯 JSON处理器测试完成: {success_count}/{len(test_cases)} 通过")
return success_count == len(test_cases)
if __name__ == "__main__":
test_json_processor()

View File

@ -0,0 +1,312 @@
"""
模型工厂模块
基于配置创建各种Agno模型实例支持多种LLM提供商
"""
from typing import Dict, Any, Optional, Union
from agno.models.openai import OpenAILike, OpenAIChat
# 尝试导入其他可选模型
try:
from agno.models.ollama import Ollama
OLLAMA_AVAILABLE = True
except ImportError:
OLLAMA_AVAILABLE = False
Ollama = None
from .config_loader import load_llm_config, get_model_config
class ModelFactory:
"""
模型工厂类
负责根据配置创建不同类型的Agno模型实例
"""
# 支持的模型类映射
MODEL_CLASSES = {
"OpenAILike": OpenAILike,
"OpenAIChat": OpenAIChat,
}
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
初始化模型工厂
Args:
config: LLM配置如果为None则自动加载
"""
self.config = config if config is not None else load_llm_config()
# 如果Ollama可用添加到支持列表
if OLLAMA_AVAILABLE:
self.MODEL_CLASSES["Ollama"] = Ollama
def create_model(
self,
provider: str,
model_name: str,
**override_params
) -> Union[OpenAILike, OpenAIChat]:
"""
创建模型实例
Args:
provider: 提供商名称 ( 'aliyun', 'deepseek', 'openai')
model_name: 模型名称 ( 'qwen-max', 'deepseek-v3')
**override_params: 覆盖配置的参数
Returns:
创建的模型实例
Raises:
ValueError: 配置错误或不支持的模型类型
ImportError: 缺少必需的依赖
"""
# 获取模型配置
model_config = get_model_config(self.config, provider, model_name)
# 获取模型类名
model_class_name = model_config.get('class')
if not model_class_name:
raise ValueError(f"模型配置中缺少 'class' 字段: {provider}.{model_name}")
# 检查模型类是否支持
if model_class_name not in self.MODEL_CLASSES:
available_classes = list(self.MODEL_CLASSES.keys())
raise ValueError(f"不支持的模型类 '{model_class_name}',支持的类型: {available_classes}")
# 特殊处理Ollama
if model_class_name == "Ollama" and not OLLAMA_AVAILABLE:
raise ImportError("Ollama模型需要安装ollama包: pip install ollama")
# 获取模型类
model_class = self.MODEL_CLASSES[model_class_name]
# 准备初始化参数
init_params = self._prepare_init_params(model_config, model_class_name, override_params)
# 创建模型实例
try:
model = model_class(**init_params)
return model
except Exception as e:
raise ValueError(f"创建模型实例失败 ({provider}.{model_name}): {e}")
def _prepare_init_params(
self,
model_config: Dict[str, Any],
model_class_name: str,
override_params: Dict[str, Any]
) -> Dict[str, Any]:
"""
准备模型初始化参数
Args:
model_config: 模型配置
model_class_name: 模型类名
override_params: 覆盖参数
Returns:
初始化参数字典
"""
# 基础参数从params字段获取
init_params = model_config.get('params', {}).copy()
# 添加API相关参数
if 'api_key' in model_config:
init_params['api_key'] = model_config['api_key']
if 'base_url' in model_config:
init_params['base_url'] = model_config['base_url']
# 根据模型类型调整参数
if model_class_name == "OpenAILike":
# OpenAILike模型特殊处理
self._adjust_openai_like_params(init_params)
elif model_class_name == "OpenAIChat":
# OpenAIChat模型特殊处理
self._adjust_openai_chat_params(init_params)
elif model_class_name == "Ollama":
# Ollama模型特殊处理
self._adjust_ollama_params(init_params)
# 应用覆盖参数
init_params.update(override_params)
return init_params
def _adjust_openai_like_params(self, params: Dict[str, Any]) -> None:
"""调整OpenAILike模型参数"""
# OpenAILike使用id参数不需要model参数
# id参数已经在params中无需额外处理
pass
def _adjust_openai_chat_params(self, params: Dict[str, Any]) -> None:
"""调整OpenAIChat模型参数"""
# OpenAIChat通常需要id参数作为model参数
if 'id' in params and 'model' not in params:
params['model'] = params['id']
def _adjust_ollama_params(self, params: Dict[str, Any]) -> None:
"""调整Ollama模型参数"""
# Ollama模型通常不需要api_key和base_url
params.pop('api_key', None)
params.pop('base_url', None)
# 使用id作为model参数
if 'id' in params and 'model' not in params:
params['model'] = params['id']
def list_available_models(self) -> Dict[str, list]:
"""
列出所有可用的模型
Returns:
按提供商分组的模型列表
"""
available_models = {}
for provider, provider_config in self.config.items():
if 'models' in provider_config:
models = list(provider_config['models'].keys())
available_models[provider] = models
return available_models
def validate_model_exists(self, provider: str, model_name: str) -> bool:
"""
验证模型是否存在
Args:
provider: 提供商名称
model_name: 模型名称
Returns:
模型是否存在
"""
try:
get_model_config(self.config, provider, model_name)
return True
except ValueError:
return False
# 便捷函数
def create_agno_model(
provider: str,
model_name: str,
config: Optional[Dict[str, Any]] = None,
**override_params
) -> Union[OpenAILike, OpenAIChat]:
"""
便捷函数创建Agno模型实例
Args:
provider: 提供商名称
model_name: 模型名称
config: 可选的配置字典
**override_params: 覆盖参数
Returns:
创建的模型实例
"""
factory = ModelFactory(config)
return factory.create_model(provider, model_name, **override_params)
def list_available_models(config: Optional[Dict[str, Any]] = None) -> Dict[str, list]:
"""
便捷函数列出可用模型
Args:
config: 可选的配置字典
Returns:
按提供商分组的模型列表
"""
factory = ModelFactory(config)
return factory.list_available_models()
# 测试函数
def test_model_creation():
"""测试模型创建功能"""
print("正在测试模型创建...")
try:
# 创建模型工厂
factory = ModelFactory()
# 列出可用模型
available_models = factory.list_available_models()
print(f"✅ 发现可用模型:")
for provider, models in available_models.items():
print(f" {provider}: {models}")
# 测试阿里云qwen-max模型创建
try:
qwen_model = factory.create_model('aliyun', 'qwen-max')
print(f"✅ qwen-max模型创建成功: {type(qwen_model)}")
except Exception as e:
print(f"❌ qwen-max模型创建失败: {e}")
# 测试DeepSeek模型创建
try:
deepseek_model = factory.create_model('deepseek', 'deepseek-v3')
print(f"✅ deepseek-v3模型创建成功: {type(deepseek_model)}")
except Exception as e:
print(f"❌ deepseek-v3模型创建失败: {e}")
# 测试参数覆盖
try:
custom_model = factory.create_model(
'aliyun',
'qwen-plus',
temperature=0.5,
max_tokens=1000
)
print(f"✅ 参数覆盖测试成功: {type(custom_model)}")
except Exception as e:
print(f"❌ 参数覆盖测试失败: {e}")
return True
except Exception as e:
print(f"❌ 模型创建测试失败: {e}")
return False
def test_convenience_functions():
"""测试便捷函数"""
print("\n正在测试便捷函数...")
try:
# 测试便捷创建函数
model = create_agno_model('aliyun', 'qwen-turbo')
print(f"✅ 便捷函数创建模型成功: {type(model)}")
# 测试列出模型函数
models = list_available_models()
total_models = sum(len(model_list) for model_list in models.values())
print(f"✅ 便捷函数列出模型成功,总计 {total_models} 个模型")
return True
except Exception as e:
print(f"❌ 便捷函数测试失败: {e}")
return False
if __name__ == "__main__":
success1 = test_model_creation()
success2 = test_convenience_functions()
if success1 and success2:
print("\n🎉 所有模型工厂测试通过!")
else:
print("\n💥 部分测试失败,请检查配置")

View File

@ -0,0 +1,434 @@
"""
SubAgent核心类
基于Agno框架的智能代理实现提供动态prompt构建JSON解析模型管理等功能
"""
import logging
from typing import Any, Dict, List, Optional, Type, Union
from agno.agent import Agent, RunResponse
from pydantic import BaseModel
from .config_loader import load_llm_config
from .model_factory import ModelFactory
from .json_processor import JSONProcessor, JSONParseError
# 设置日志
logger = logging.getLogger(__name__)
class SubAgentError(Exception):
"""SubAgent相关错误的基类"""
pass
class ConfigurationError(SubAgentError):
"""配置相关错误"""
pass
class ModelError(SubAgentError):
"""模型相关错误"""
pass
class SubAgent:
"""
SubAgent核心类
基于Agno框架构建的智能代理支持:
- 动态prompt模板构建
- 多LLM提供商支持
- JSON结构化输出
- 零容错解析
- 灵活的配置管理
"""
def __init__(
self,
provider: str,
model_name: str,
instructions: Optional[List[str]] = None,
name: Optional[str] = None,
description: Optional[str] = None,
prompt_template: Optional[str] = None,
response_model: Optional[Type[BaseModel]] = None,
config: Optional[Dict[str, Any]] = None,
**agent_kwargs
):
"""
初始化SubAgent
Args:
provider: LLM提供商名称 ( 'aliyun', 'deepseek', 'openai')
model_name: 模型名称 ( 'qwen-max', 'deepseek-v3')
instructions: 指令列表
name: Agent名称
description: Agent描述
prompt_template: 动态prompt模板
response_model: Pydantic响应模型类
config: 自定义LLM配置
**agent_kwargs: 传递给Agno Agent的额外参数
Raises:
ConfigurationError: 配置错误
ModelError: 模型创建错误
"""
# 基础属性设置
self.provider = provider
self.model_name = model_name
self.name = name or f"{provider}_{model_name}_agent"
self.description = description or f"基于{provider}{model_name}模型的智能代理"
self.instructions = instructions or []
self.prompt_template = prompt_template
self.response_model = response_model
# 初始化组件
try:
# 加载配置
self.config = config if config is not None else load_llm_config()
# 创建模型工厂和JSON处理器
self.model_factory = ModelFactory(self.config)
self.json_processor = JSONProcessor()
# 创建Agno模型
self.model = self._create_model()
# 创建Agno Agent
self.agent = self._create_agent(**agent_kwargs)
logger.info(f"SubAgent {self.name} 初始化成功")
except Exception as e:
logger.error(f"SubAgent初始化失败: {e}")
raise ConfigurationError(f"SubAgent初始化失败: {e}")
def _create_model(self):
"""创建Agno模型实例"""
try:
return self.model_factory.create_model(self.provider, self.model_name)
except Exception as e:
raise ModelError(f"模型创建失败: {e}")
def _create_agent(self, **agent_kwargs):
"""创建Agno Agent实例"""
try:
agent_params = {
"model": self.model,
"name": self.name,
"description": self.description,
"instructions": self.instructions,
"markdown": agent_kwargs.pop("markdown", True),
"debug_mode": agent_kwargs.pop("debug_mode", False),
**agent_kwargs
}
return Agent(**agent_params)
except Exception as e:
raise ConfigurationError(f"Agent创建失败: {e}")
def build_prompt(self, template_vars: Optional[Dict[str, Any]] = None) -> str:
"""
构建动态prompt
Args:
template_vars: 模板变量字典用于替换模板中的占位符
Returns:
构建完成的prompt字符串
Raises:
SubAgentError: prompt构建失败
"""
if not self.prompt_template:
raise SubAgentError("未设置prompt模板无法构建动态prompt")
if not template_vars:
template_vars = {}
try:
# 使用str.format()进行模板替换
prompt = self.prompt_template.format(**template_vars)
# 如果需要JSON输出添加JSON格式要求
if self.response_model:
json_instruction = self._build_json_instruction()
prompt = f"{prompt}\n\n{json_instruction}"
return prompt
except KeyError as e:
raise SubAgentError(f"模板变量缺失: {e}")
except Exception as e:
raise SubAgentError(f"prompt构建失败: {e}")
def _build_json_instruction(self) -> str:
"""构建JSON格式指令"""
json_instruction = """
请严格按照以下JSON格式返回结果不要添加任何额外的文字说明
"""
if self.response_model:
# 生成JSON schema示例
try:
schema = self.response_model.model_json_schema()
json_instruction += f"JSON Schema:\n{schema}\n\n"
# 生成示例
example = self._generate_model_example()
if example:
json_instruction += f"示例格式:\n{example}"
except Exception:
# 如果schema生成失败使用通用指令
json_instruction += "请返回有效的JSON格式数据。"
return json_instruction
def _generate_model_example(self) -> Optional[str]:
"""生成Pydantic模型的示例JSON"""
try:
# 创建一个示例实例
field_values = {}
for field_name, field_info in self.response_model.model_fields.items():
# 根据字段类型生成示例值
field_type = field_info.annotation
field_values[field_name] = self._generate_example_value(field_type)
example_instance = self.response_model(**field_values)
return example_instance.model_dump_json(indent=2)
except Exception:
return None
def _generate_example_value(self, field_type: Type) -> Any:
"""根据字段类型生成示例值"""
if field_type == str:
return "示例文本"
elif field_type == int:
return 0
elif field_type == float:
return 0.0
elif field_type == bool:
return True
elif hasattr(field_type, '__origin__'):
origin = field_type.__origin__
if origin == list:
return []
elif origin == dict:
return {}
return "示例值"
def run(
self,
prompt: Optional[str] = None,
template_vars: Optional[Dict[str, Any]] = None,
**run_kwargs
) -> Any:
"""
执行Agent推理
Args:
prompt: 直接prompt文本如果提供将忽略template_vars
template_vars: 模板变量用于动态构建prompt
**run_kwargs: 传递给Agent.run的额外参数
Returns:
如果指定了response_model返回解析后的Pydantic模型实例
否则返回原始响应内容
Raises:
SubAgentError: 执行失败
JSONParseError: JSON解析失败
"""
try:
# 构建最终prompt
if prompt is not None:
final_prompt = prompt
else:
final_prompt = self.build_prompt(template_vars)
logger.debug(f"Agent {self.name} 执行推理prompt长度: {len(final_prompt)}")
# 执行Agent推理
response: RunResponse = self.agent.run(final_prompt, **run_kwargs)
# 获取响应内容
content = response.content
if self.response_model:
# 如果指定了响应模型进行JSON解析
return self._parse_structured_response(content)
else:
# 返回原始内容
return content
except JSONParseError:
# JSON解析错误直接抛出
raise
except Exception as e:
logger.error(f"Agent执行失败: {e}")
raise SubAgentError(f"Agent执行失败: {e}")
def _parse_structured_response(self, content: str) -> BaseModel:
"""解析结构化响应"""
try:
return self.json_processor.parse_json_response(content, self.response_model)
except JSONParseError as e:
logger.error(f"JSON解析失败: {e}")
logger.debug(f"响应内容: {content}")
raise
def get_model_info(self) -> Dict[str, Any]:
"""
获取模型信息
Returns:
包含模型信息的字典
"""
return {
"name": self.name,
"provider": self.provider,
"model_name": self.model_name,
"model_type": type(self.model).__name__,
"has_prompt_template": self.prompt_template is not None,
"has_response_model": self.response_model is not None,
"instructions_count": len(self.instructions),
}
def update_instructions(self, instructions: List[str]) -> None:
"""
更新指令列表
Args:
instructions: 新的指令列表
"""
self.instructions = instructions
# 注意这里不会更新已创建的Agent实例如需更新需要重新创建Agent
logger.info(f"Agent {self.name} 指令已更新")
def update_prompt_template(self, template: str) -> None:
"""
更新prompt模板
Args:
template: 新的prompt模板
"""
self.prompt_template = template
logger.info(f"Agent {self.name} prompt模板已更新")
def __str__(self) -> str:
return f"SubAgent({self.name}, {self.provider}.{self.model_name})"
def __repr__(self) -> str:
return self.__str__()
# 便捷函数
def create_json_agent(
provider: str,
model_name: str,
name: str,
prompt_template: str,
response_model: Union[str, Type[BaseModel]],
instructions: Optional[List[str]] = None,
**kwargs
) -> SubAgent:
"""
便捷函数创建支持JSON输出的SubAgent
Args:
provider: 提供商名称
model_name: 模型名称
name: Agent名称
prompt_template: prompt模板
response_model: 响应模型类或模块路径字符串
instructions: 指令列表
**kwargs: 额外参数
Returns:
配置好的SubAgent实例
"""
# 如果response_model是字符串尝试导入
if isinstance(response_model, str):
try:
# 解析模块路径和类名
if '.' in response_model:
module_path, class_name = response_model.rsplit('.', 1)
else:
# 如果没有模块路径,假设在当前模块
module_path = '__main__'
class_name = response_model
# 动态导入
import importlib
module = importlib.import_module(module_path)
response_model = getattr(module, class_name)
except Exception as e:
raise ValueError(f"无法导入响应模型 {response_model}: {e}")
return SubAgent(
provider=provider,
model_name=model_name,
name=name,
prompt_template=prompt_template,
response_model=response_model,
instructions=instructions,
**kwargs
)
# 测试函数
def test_subagent():
"""测试SubAgent功能"""
print("正在测试SubAgent...")
try:
# 创建基础SubAgent
agent = SubAgent(
provider="aliyun",
model_name="qwen-turbo",
name="test_agent",
instructions=["你是一个测试助手", "请简洁回答问题"]
)
print(f"✅ SubAgent创建成功: {agent}")
print(f" 模型信息: {agent.get_model_info()}")
# 测试基础对话
try:
response = agent.run("请简单介绍一下Python语言")
print(f"✅ 基础对话测试成功,响应长度: {len(str(response))}字符")
except Exception as e:
print(f"❌ 基础对话测试失败: {e}")
# 测试动态prompt构建
agent.update_prompt_template("请回答关于{topic}的问题:{question}")
try:
prompt = agent.build_prompt({
"topic": "编程",
"question": "什么是函数?"
})
print(f"✅ 动态prompt构建成功长度: {len(prompt)}字符")
except Exception as e:
print(f"❌ 动态prompt构建失败: {e}")
return True
except Exception as e:
print(f"❌ SubAgent测试失败: {e}")
return False
if __name__ == "__main__":
test_subagent()

1
src/config/.env Normal file
View File

@ -0,0 +1 @@
DASHSCOPE_API_KEY=sk-5c7f9dc33e0a43738d415a0432452b93

View File

@ -0,0 +1,93 @@
# LLM配置文件
# 定义所有Agent可用的LLM模型配置
# 阿里云通义千问配置
aliyun:
base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key: ${DASHSCOPE_API_KEY} # 从环境变量读取
models:
qwen-max:
class: "OpenAILike"
params:
id: "qwen-max"
temperature: 0.7
max_tokens: 3000
qwen-plus:
class: "OpenAILike"
params:
id: "qwen-plus"
temperature: 0.7
max_tokens: 2000
qwen-turbo:
class: "OpenAILike"
params:
id: "qwen-turbo"
temperature: 0.7
max_tokens: 1500
# DeepSeek配置
deepseek:
base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key: ${DASHSCOPE_API_KEY}
models:
deepseek-v3:
class: "OpenAILike"
params:
id: "deepseek-v3"
temperature: 0.7
max_tokens: 4000
deepseek-r1:
class: "OpenAILike"
params:
id: "deepseek-r1"
temperature: 0.5
max_tokens: 8000
# DeepSeek R1特有的推理模式
reasoning_effort: "high"
# OpenAI配置备用
openai:
base_url: "https://api.openai.com/v1"
api_key: ${OPENAI_API_KEY}
models:
gpt-4:
class: "OpenAIChat"
params:
id: "gpt-4"
temperature: 0.7
max_tokens: 4000
gpt-3.5-turbo:
class: "OpenAIChat"
params:
id: "gpt-3.5-turbo"
temperature: 0.7
max_tokens: 2000
# 本地Ollama配置开发测试用
ollama:
host: "127.0.0.1"
port: 11434
models:
qwen2.5:
class: "Ollama"
params:
id: "qwen2.5:latest"
temperature: 0.7
max_tokens: 2000
llama3:
class: "Ollama"
params:
id: "llama3:latest"
temperature: 0.7
max_tokens: 2000
vllm:
base_url: "http://100.82.33.121:11001/v1"
models:
gpt-oss:
class: "OpenAIChat"
params:
id: "gpt-oss-20b"
temperature: 0.7
max_tokens: 2000

466
uv.lock generated
View File

@ -2,6 +2,52 @@ version = 1
revision = 2
requires-python = ">=3.13"
[[package]]
name = "agno"
version = "1.7.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "docstring-parser" },
{ name = "gitpython" },
{ name = "httpx" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-dotenv" },
{ name = "python-multipart" },
{ name = "pyyaml" },
{ name = "rich" },
{ name = "tomli" },
{ name = "typer" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b7/4c/5a1b7b7bc41c521fe4659e15e2594801ae5356444ec29ffb1e7227f47428/agno-1.7.12.tar.gz", hash = "sha256:df0627cbb66d7d73ec639d23821a549e9a4e6178f812dceaf000697d6246b9d8", size = 747380, upload-time = "2025-08-20T20:47:16.157Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/53/f8d7a8d5e18d9cf1362b9a9c54ba1895625d7b65aa9e9487fc5439aca5b7/agno-1.7.12-py3-none-any.whl", hash = "sha256:288cf2e1cf4df2496bc8c44ab1ae28ea7821a090d00d3eca92f521fc8bbe3c13", size = 988853, upload-time = "2025-08-20T20:47:13.457Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
]
[[package]]
name = "certifi"
version = "2025.8.3"
@ -42,6 +88,106 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
]
[[package]]
name = "click"
version = "8.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "distro"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "docstring-parser"
version = "0.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
]
[[package]]
name = "gitdb"
version = "4.0.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "smmap" },
]
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
]
[[package]]
name = "gitpython"
version = "3.1.45"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gitdb" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.10"
@ -51,16 +197,227 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "jiter"
version = "0.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" },
{ url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" },
{ url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" },
{ url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" },
{ url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" },
{ url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" },
{ url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" },
{ url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" },
{ url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" },
{ url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" },
{ url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" },
{ url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" },
{ url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" },
{ url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" },
{ url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" },
{ url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" },
{ url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" },
{ url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" },
{ url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" },
{ url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" },
{ url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" },
{ url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" },
{ url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" },
{ url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" },
{ url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" },
{ url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" },
{ url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "medresearcher"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "agno" },
{ name = "ollama" },
{ name = "openai" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "requests" },
]
[package.metadata]
requires-dist = [{ name = "requests", specifier = ">=2.32.5" }]
requires-dist = [
{ name = "agno", specifier = ">=1.7.12" },
{ name = "ollama", specifier = ">=0.5.3" },
{ name = "openai", specifier = ">=1.101.0" },
{ name = "pydantic" },
{ name = "pyyaml", specifier = ">=6.0.2" },
{ name = "requests", specifier = ">=2.32.5" },
]
[[package]]
name = "ollama"
version = "0.5.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/91/6d/ae96027416dcc2e98c944c050c492789502d7d7c0b95a740f0bb39268632/ollama-0.5.3.tar.gz", hash = "sha256:40b6dff729df3b24e56d4042fd9d37e231cee8e528677e0d085413a1d6692394", size = 43331, upload-time = "2025-08-07T21:44:10.422Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/f6/2091e50b8b6c3e6901f6eab283d5efd66fb71c86ddb1b4d68766c3eeba0f/ollama-0.5.3-py3-none-any.whl", hash = "sha256:a8303b413d99a9043dbf77ebf11ced672396b59bec27e6d5db67c88f01b279d2", size = 13490, upload-time = "2025-08-07T21:44:09.353Z" },
]
[[package]]
name = "openai"
version = "1.101.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx" },
{ name = "jiter" },
{ name = "pydantic" },
{ name = "sniffio" },
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/00/7c/eaf06b62281f5ca4f774c4cff066e6ddfd6a027e0ac791be16acec3a95e3/openai-1.101.0.tar.gz", hash = "sha256:29f56df2236069686e64aca0e13c24a4ec310545afb25ef7da2ab1a18523f22d", size = 518415, upload-time = "2025-08-21T21:11:01.645Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/a6/0e39baa335bbd1c66c7e0a41dbbec10c5a15ab95c1344e7f7beb28eee65a/openai-1.101.0-py3-none-any.whl", hash = "sha256:6539a446cce154f8d9fb42757acdfd3ed9357ab0d34fcac11096c461da87133b", size = 810772, upload-time = "2025-08-21T21:10:59.215Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pydantic"
version = "2.11.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
]
[[package]]
name = "pydantic-settings"
version = "2.10.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "python-dotenv"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
]
[[package]]
name = "requests"
@ -77,6 +434,113 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "rich"
version = "14.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
name = "smmap"
version = "5.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "tqdm"
version = "4.67.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
]
[[package]]
name = "typer"
version = "0.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/78/d90f616bf5f88f8710ad067c1f8705bf7618059836ca084e5bb2a0855d75/typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614", size = 102836, upload-time = "2025-08-18T19:18:22.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2d/76/06dbe78f39b2203d2a47d5facc5df5102d0561e2807396471b5f7c5a30a1/typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9", size = 46397, upload-time = "2025-08-18T19:18:21.663Z" },
]
[[package]]
name = "typing-extensions"
version = "4.14.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
]
[[package]]
name = "urllib3"
version = "2.5.0"