feat(financial_analysis_agent): add data fetcher, analyzer, manager a…#1133
feat(financial_analysis_agent): add data fetcher, analyzer, manager a…#1133tang1314-dr wants to merge 1 commit intoagentscope-ai:mainfrom
Conversation
…nd universal agent components - Add financial data fetching tools (data_fetcher.py, data_fetcher_fixed.py) - Add financial analysis calculator (analyzer_fixed.py) - Add financial report generator (report_generator.py) - Add tool management system (manager.py, manager_fixed.py) - Add universal agent with config, intent analyzer and model selector - Add unified configuration management system
|
|
Summary of ChangesHello @tang1314-dr, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new financial analysis agent and a universal agent, along with their foundational components. A significant portion of the work involved refactoring the financial analysis agent's tools, skills, and communication modules to ensure full compliance with the AgentScope framework, enhancing its robustness, configurability, and integration capabilities. The changes provide a solid, extensible foundation for advanced agent functionalities. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive financial analysis agent with many advanced features like agent skills, MCP integration, A2A communication, and a unified configuration system. The overall structure is well-thought-out, and the separation of concerns into different modules is good. However, there are several critical issues, particularly regarding class inheritance from AgentBase and inconsistencies in configuration handling, that need to be addressed. I've also found some logic errors and potential runtime issues in different modules. The fixes in the *_fixed.py files are a good step, but some of them introduce new problems or don't fully resolve the original ones.
| def create_toolkit(self, tool_names: List[str] = None) -> Toolkit: | ||
| """创建工具集""" | ||
| toolkit = Toolkit() | ||
|
|
||
| if tool_names is None: | ||
| tool_names = self.registry.list_tools(enabled_only=True) | ||
|
|
||
| for name, tool_func in self.registry.tools.items(): | ||
| if tool_config := self.registry.get_tool(name): | ||
| if tool_config and tool_config.enabled: | ||
| toolkit.register_tool_function(tool_func) | ||
|
|
||
| return toolkit |
There was a problem hiding this comment.
The logic in create_toolkit is flawed. It iterates over self.registry.tools.items(), where the value (tool_func) is a ToolConfig object, not a callable function. toolkit.register_tool_function expects a callable and will fail. The loop should iterate over the correctly filtered list of tools and register the function attribute of the ToolConfig object.
def create_toolkit(self, tool_names: List[str] = None) -> Toolkit:
"""创建工具集"""
toolkit = Toolkit()
if tool_names is None:
tools_to_add = self.registry.list_tools(enabled_only=True)
else:
tools_to_add = {}
for name in tool_names:
tool_config = self.registry.get_tool(name)
if tool_config and tool_config.enabled:
tools_to_add[name] = tool_config
for name, tool_config in tools_to_add.items():
try:
if tool_config.function:
toolkit.register_tool_function(tool_config.function)
except Exception as e:
print(f"注册工具 {name} 到Toolkit失败: {str(e)}")
return toolkit| def __init__(self): | ||
| """初始化财务分析技能""" | ||
| super().__init__( | ||
| name="financial_analysis", | ||
| sys_prompt="你是一个专业的财务分析技能专家,能够获取财务数据、计算财务比率、评估投资风险" | ||
| ) |
There was a problem hiding this comment.
Similar to the issue in a2a/__init___fixed.py, the AgentBase class from agentscope.agent has a parameter-less __init__. Calling super().__init__(name="financial_analysis", sys_prompt=...) will raise a TypeError. The correct pattern is to call super().__init__() and then set the name and sys_prompt attributes on self.
def __init__(self):
"""初始化财务分析技能"""
super().__init__()
self.name = "financial_analysis"
self.sys_prompt = "你是一个专业的财务分析技能专家,能够获取财务数据、计算财务比率、评估投资风险"| def __init__(self, name: str = "A2ARegistry", **kwargs): | ||
| super().__init__( | ||
| name=name, | ||
| sys_prompt=f"""你是A2A智能体注册表,负责: | ||
| 1. 管理智能体注册和注销 | ||
| 2. 维护智能体能力目录 | ||
| 3. 支持智能体发现和匹配 | ||
| 4. 提供智能体组管理功能""", | ||
| **kwargs | ||
| ) |
There was a problem hiding this comment.
The AgentBase class from agentscope.agent has a parameter-less __init__ method. Calling super().__init__(name=name, sys_prompt=...) will raise a TypeError: __init__() got an unexpected keyword argument 'name'. The correct approach is to call super().__init__() and then set the attributes on self. The a2a/__init___completely_fixed.py file demonstrates the correct pattern.
def __init__(self, name: str = "A2ARegistry", **kwargs):
super().__init__()
self.name = name
self.sys_prompt=f"""你是A2A智能体注册表,负责:
1. 管理智能体注册和注销
2. 维护智能体能力目录
3. 支持智能体发现和匹配
4. 提供智能体组管理功能"""| ) | ||
|
|
||
| # 注册模板 | ||
| self.register_template(financial_analysis_prompt, category="analysis") |
There was a problem hiding this comment.
The register_template method is called with two arguments (self.register_template(financial_analysis_prompt, category="analysis")), but its definition only accepts one (def register_template(self, template: PromptTemplate)). This will raise a TypeError. The category should be handled within the _create_default_templates method, for example by adding the template name to self.categories after registration.
| self.register_template(financial_analysis_prompt, category="analysis") | |
| self.register_template(financial_analysis_prompt) | |
| self.categories.setdefault("analysis", []).append(financial_analysis_prompt.name) |
| "type": "http_stateless", | ||
| "url": "https://financial-mcp.example.com/api", | ||
| "api_key": os.environ.get("FINANCIAL_MCP_API_KEY"), | ||
| "tools": ["get_stock_price", "get_financial_statements", "get_market_indices"] |
There was a problem hiding this comment.
The default configuration for tools within _load_config is a list of strings (e.g., ["get_stock_price", ...]). However, the call_tool method processes this list assuming it's a list of dictionaries ([tool.get("name") for tool in ...]). This will cause an AttributeError: 'str' object has no attribute 'get' if the mcp_config.json file is not found and the default config is used. The default config structure should match the expected structure (a list of dictionaries).
| "- **风险评估**: 识别和评估投资风险" | ||
| "- **实时更新**: 实时数据更新", |
There was a problem hiding this comment.
There is a missing comma between the string literals on lines 307 and 308. This causes Python to concatenate them into a single string: "- **风险评估**: 识别和评估投资风险- **实时更新**: 实时数据更新". A comma should be added to separate them into two distinct list items for correct formatting in the system prompt.
| "- **风险评估**: 识别和评估投资风险" | |
| "- **实时更新**: 实时数据更新", | |
| "- **风险评估**: 识别和评估投资风险", | |
| "- **实时更新**: 实时数据更新", |
| ### ✅ 已完成的修复 | ||
|
|
||
| **1. 🛠️ 工具函数标准化** | ||
| - **问题**: 原�始工具使用类而非独立函数 |
| if key in data and isinstance(data[key], dict): | ||
| values = list(data[key].values()) | ||
| # 过滤非数值 | ||
| numeric_values = [v for v in values if isinstance(v, (int, float)) and v > 0] |
There was a problem hiding this comment.
The _get_latest_value function filters for values greater than 0 (v > 0). This can be problematic for financial metrics that can legitimately be negative, such as Net Income. If a company has reported losses for all available periods, this function will return None, preventing the calculation of ratios like ROE and ROA. The filter should probably only check for the numeric type.
| numeric_values = [v for v in values if isinstance(v, (int, float)) and v > 0] | |
| numeric_values = [v for v in values if isinstance(v, (int, float))] |
| content=json.dumps({ | ||
| "a2a_message": message.to_dict() | ||
| }, ensure_ascii=False), |
There was a problem hiding this comment.
The message content is created by dumping a dictionary to a JSON string. This practice of nesting JSON within a message's content can make message handling and parsing on the receiver's side unnecessarily complex and less standardized. It's better to use the fields of the Msg object or dedicated content blocks to structure data, rather than embedding JSON strings.
| complexity_scores[complexity] += 1 | ||
|
|
||
| # Count complexity indicators | ||
| step_count = len(re.findall(r'\b(step|步骤|阶段|部分|部分)\b', message)) |
There was a problem hiding this comment.
There was a problem hiding this comment.
Pull request overview
This pull request adds a comprehensive financial analysis agent system to AgentScope with multiple components including data fetchers, analyzers, report generators, tool management, and a universal agent framework. The PR introduces MCP (Model Context Protocol) integration, Agent-to-Agent communication, skills framework, and prompt management systems.
Changes:
- Add financial data fetching and analysis tools with multiple implementations (base and "fixed" versions)
- Add universal agent with configuration, intent analyzer, and model selector
- Add tool management system with registry and configuration
- Add MCP client integration for external data sources
- Add agent skills framework and A2A communication support
- Add prompt management and report generation capabilities
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 84 comments.
Show a summary per file
| File | Description |
|---|---|
| unified_config_fixed.py | Centralized configuration management system |
| examples/universal_agent/*.py | Universal agent framework with config, model selector, and intent analyzer |
| examples/financial_analysis_agent/tools/*.py | Financial data fetchers, analyzers, and report generators |
| examples/financial_analysis_agent/skills/*.py | Agent skills framework implementation |
| examples/financial_analysis_agent/prompts/*.py | Prompt management system |
| examples/financial_analysis_agent/mcp/*.py | MCP protocol integration |
| examples/financial_analysis_agent/*.py | Main agent implementations and demos |
| examples/financial_analysis_agent/requirements.txt | Project dependencies |
| if "OPENAI_API_KEY" in os.environ: | ||
| if not any(m.name == "openai" for m in data.get("models", [])): | ||
| data.setdefault("models", []).append(ModelConfig( | ||
| name="openai", | ||
| model_type="openai", | ||
| api_key=os.environ.get("OPENAI_API_KEY"), | ||
| model_name="gpt-4", | ||
| capabilities=["text", "vision", "tools"] | ||
| )) | ||
|
|
||
| if "ANTHROPIC_API_KEY" in os.environ: | ||
| if not any(m.name == "anthropic" for m in data.get("models", [])): | ||
| data.setdefault("models", []).append(ModelConfig( | ||
| name="anthropic", | ||
| model_type="anthropic", | ||
| api_key=os.environ.get("ANTHROPIC_API_KEY"), | ||
| model_name="claude-3-5-sonnet-20241022", | ||
| capabilities=["text", "vision", "tools"] | ||
| )) | ||
|
|
||
| if "DASHSCOPE_API_KEY" in os.environ: | ||
| if not any(m.name == "dashscope" for m in data.get("models", [])): | ||
| data.setdefault("models", []).append(ModelConfig( | ||
| name="dashscope", | ||
| model_type="dashscope", | ||
| api_key=os.environ.get("DASHSCOPE_API_KEY"), | ||
| model_name="qwen-max", | ||
| capabilities=["text", "vision", "tools", "tts"] | ||
| )) | ||
|
|
There was a problem hiding this comment.
Hardcoded API keys in environment variable calls without proper handling is a security concern. While using environment variables is correct, the code should validate that these keys exist and provide clear error messages if they're missing, rather than silently using None values which could lead to runtime errors.
| agentscope | ||
| yfinance>=0.2.18 | ||
| pandas>=1.5.0 | ||
| numpy>=1.24.0 | ||
| matplotlib>=3.6.0 | ||
| seaborn>=0.12.0 | ||
| plotly>=5.15.0 | ||
| jinja2>=3.1.0 | ||
| aiohttp>=3.8.0 | ||
| pyyaml>=6.0 No newline at end of file |
There was a problem hiding this comment.
Missing unit tests. According to guidelines, new features must include unit tests. This PR adds multiple new components (data fetchers, analyzers, managers, universal agent) but no test files are included in the changes.
| @@ -0,0 +1,265 @@ | |||
| """ | |||
There was a problem hiding this comment.
The PR title is incomplete (ends with "a…"). According to guidelines, PR titles must follow Conventional Commits format with prefixes like feat/fix/docs/ci/refactor/test and format feat(scope): description. The current title appears truncated and does not provide a clear, complete description.
| #!/usr/bin/env python3 | ||
| # -*- coding: utf-8 -*- | ||
|
|
||
| """ | ||
| Unified Configuration Management | ||
| Centralized configuration system for the financial analysis agent framework | ||
| """ | ||
|
|
||
| import os | ||
| import json | ||
| import logging | ||
| from typing import Dict, Any, Optional, Union | ||
| from dataclasses import dataclass, asdict | ||
| from pathlib import Path | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| @dataclass | ||
| class DatabaseConfig: | ||
| """Database configuration settings.""" | ||
| host: str = "localhost" | ||
| port: int = 5432 | ||
| name: str = "financial_analysis" | ||
| user: str = "postgres" | ||
| password: str = "" | ||
| pool_size: int = 10 | ||
| max_overflow: int = 20 | ||
|
|
||
|
|
||
| @dataclass | ||
| class APIConfig: | ||
| """External API configuration settings.""" | ||
| alpha_vantage_key: str = "" | ||
| finnhub_key: str = "" | ||
| quandl_key: str = "" | ||
| fred_key: str = "" | ||
| base_url: str = "https://api.example.com" | ||
| timeout: int = 30 | ||
| retry_attempts: int = 3 | ||
|
|
||
|
|
||
| @dataclass | ||
| class ToolConfig: | ||
| """Tool system configuration settings.""" | ||
| enabled_tools: list = None | ||
| tool_timeout: int = 60 | ||
| max_concurrent_tools: int = 5 | ||
| tool_cache_size: int = 100 | ||
| fallback_enabled: bool = True | ||
|
|
||
| def __post_init__(self): | ||
| if self.enabled_tools is None: | ||
| self.enabled_tools = ["data_fetcher", "analyzer", "validator"] | ||
|
|
||
|
|
||
| @dataclass | ||
| class AgentConfig: | ||
| """Agent configuration settings.""" | ||
| name: str = "FinancialAnalysisAgent" | ||
| version: str = "2.0.0" | ||
| model_name: str = "gpt-4" | ||
| max_tokens: int = 4096 | ||
| temperature: float = 0.7 | ||
| memory_limit: int = 1000 | ||
| log_level: str = "INFO" | ||
|
|
||
|
|
||
| @dataclass | ||
| class MCPConfig: | ||
| """MCP (Model Context Protocol) configuration settings.""" | ||
| enabled: bool = False | ||
| server_url: str = "ws://localhost:8080" | ||
| api_key: str = "" | ||
| timeout: int = 30 | ||
| max_connections: int = 10 | ||
| heartbeat_interval: int = 60 | ||
|
|
||
|
|
||
| @dataclass | ||
| class A2AConfig: | ||
| """Agent-to-Agent communication configuration settings.""" | ||
| enabled: bool = True | ||
| message_timeout: int = 30 | ||
| max_message_size: int = 1024 * 1024 # 1MB | ||
| retry_attempts: int = 3 | ||
| workflow_timeout: int = 300 # 5 minutes | ||
|
|
||
|
|
||
| @dataclass | ||
| class LoggingConfig: | ||
| """Logging configuration settings.""" | ||
| level: str = "INFO" | ||
| format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" | ||
| file_path: Optional[str] = None | ||
| max_file_size: int = 10 * 1024 * 1024 # 10MB | ||
| backup_count: int = 5 | ||
|
|
||
|
|
||
| @dataclass | ||
| class SecurityConfig: | ||
| """Security configuration settings.""" | ||
| encryption_enabled: bool = False | ||
| api_key_rotation_days: int = 90 | ||
| max_login_attempts: int = 5 | ||
| session_timeout: int = 3600 # 1 hour | ||
| audit_enabled: bool = True | ||
|
|
||
|
|
||
| @dataclass | ||
| class UnifiedConfig: | ||
| """Unified configuration container.""" | ||
| database: DatabaseConfig = None | ||
| api: APIConfig = None | ||
| tools: ToolConfig = None | ||
| agent: AgentConfig = None | ||
| mcp: MCPConfig = None | ||
| a2a: A2AConfig = None | ||
| logging: LoggingConfig = None | ||
| security: SecurityConfig = None | ||
|
|
||
| def __post_init__(self): | ||
| if self.database is None: | ||
| self.database = DatabaseConfig() | ||
| if self.api is None: | ||
| self.api = APIConfig() | ||
| if self.tools is None: | ||
| self.tools = ToolConfig() | ||
| if self.agent is None: | ||
| self.agent = AgentConfig() | ||
| if self.mcp is None: | ||
| self.mcp = MCPConfig() | ||
| if self.a2a is None: | ||
| self.a2a = A2AConfig() | ||
| if self.logging is None: | ||
| self.logging = LoggingConfig() | ||
| if self.security is None: | ||
| self.security = SecurityConfig() | ||
|
|
||
|
|
||
| class ConfigManager: | ||
| """Centralized configuration manager.""" | ||
|
|
||
| def __init__(self, config_dir: str = "config"): | ||
| self.config_dir = Path(config_dir) | ||
| self.config_dir.mkdir(exist_ok=True) | ||
| self._config: Optional[UnifiedConfig] = None | ||
| self._config_file = self.config_dir / "unified_config.json" | ||
|
|
||
| def load_config(self, config_file: Optional[str] = None) -> UnifiedConfig: | ||
| """Load configuration from file or create default.""" | ||
| if config_file: | ||
| self._config_file = Path(config_file) | ||
|
|
||
| try: | ||
| if self._config_file.exists(): | ||
| with open(self._config_file, 'r', encoding='utf-8') as f: | ||
| config_data = json.load(f) | ||
| self._config = self._dict_to_config(config_data) | ||
| logger.info(f"Loaded configuration from {self._config_file}") | ||
| else: | ||
| self._config = UnifiedConfig() | ||
| logger.info("Created default configuration") | ||
| except Exception as e: | ||
| logger.error(f"Failed to load config: {e}. Using defaults.") | ||
| self._config = UnifiedConfig() | ||
|
|
||
| return self._config | ||
|
|
||
| def save_config(self, config: Optional[UnifiedConfig] = None) -> bool: | ||
| """Save configuration to file.""" | ||
| if config is None: | ||
| config = self._config | ||
|
|
||
| if config is None: | ||
| logger.error("No configuration to save") | ||
| return False | ||
|
|
||
| try: | ||
| config_dict = self._config_to_dict(config) | ||
| with open(self._config_file, 'w', encoding='utf-8') as f: | ||
| json.dump(config_dict, f, indent=2, ensure_ascii=False) | ||
| logger.info(f"Saved configuration to {self._config_file}") | ||
| return True | ||
| except Exception as e: | ||
| logger.error(f"Failed to save config: {e}") | ||
| return False | ||
|
|
||
| def get_config(self) -> UnifiedConfig: | ||
| """Get current configuration.""" | ||
| if self._config is None: | ||
| self._config = self.load_config() | ||
| return self._config | ||
|
|
||
| def update_config(self, section: str, updates: Dict[str, Any]) -> bool: | ||
| """Update specific configuration section.""" | ||
| config = self.get_config() | ||
|
|
||
| try: | ||
| if hasattr(config, section): | ||
| section_obj = getattr(config, section) | ||
| for key, value in updates.items(): | ||
| if hasattr(section_obj, key): | ||
| setattr(section_obj, key, value) | ||
| else: | ||
| logger.warning(f"Unknown key {key} in section {section}") | ||
| return True | ||
| else: | ||
| logger.error(f"Unknown configuration section: {section}") | ||
| return False | ||
| except Exception as e: | ||
| logger.error(f"Failed to update config: {e}") | ||
| return False | ||
|
|
||
| def validate_config(self, config: Optional[UnifiedConfig] = None) -> bool: | ||
| """Validate configuration integrity.""" | ||
| if config is None: | ||
| config = self.get_config() | ||
|
|
||
| try: | ||
| # Validate database configuration | ||
| if config.database.port <= 0 or config.database.port > 65535: | ||
| logger.error("Invalid database port") | ||
| return False | ||
|
|
||
| # Validate API configuration | ||
| if config.api.timeout <= 0: | ||
| logger.error("Invalid API timeout") | ||
| return False | ||
|
|
||
| # Validate tool configuration | ||
| if config.tools.tool_timeout <= 0: | ||
| logger.error("Invalid tool timeout") | ||
| return False | ||
|
|
||
| # Validate agent configuration | ||
| if config.agent.max_tokens <= 0: | ||
| logger.error("Invalid max tokens") | ||
| return False | ||
|
|
||
| logger.info("Configuration validation passed") | ||
| return True | ||
| except Exception as e: | ||
| logger.error(f"Configuration validation failed: {e}") | ||
| return False | ||
|
|
||
| def _config_to_dict(self, config: UnifiedConfig) -> Dict[str, Any]: | ||
| """Convert configuration object to dictionary.""" | ||
| return { | ||
| "database": asdict(config.database), | ||
| "api": asdict(config.api), | ||
| "tools": asdict(config.tools), | ||
| "agent": asdict(config.agent), | ||
| "mcp": asdict(config.mcp), | ||
| "a2a": asdict(config.a2a), | ||
| "logging": asdict(config.logging), | ||
| "security": asdict(config.security) | ||
| } | ||
|
|
||
| def _dict_to_config(self, config_dict: Dict[str, Any]) -> UnifiedConfig: | ||
| """Convert dictionary to configuration object.""" | ||
| config = UnifiedConfig() | ||
|
|
||
| if "database" in config_dict: | ||
| config.database = DatabaseConfig(**config_dict["database"]) | ||
| if "api" in config_dict: | ||
| config.api = APIConfig(**config_dict["api"]) | ||
| if "tools" in config_dict: | ||
| config.tools = ToolConfig(**config_dict["tools"]) | ||
| if "agent" in config_dict: | ||
| config.agent = AgentConfig(**config_dict["agent"]) | ||
| if "mcp" in config_dict: | ||
| config.mcp = MCPConfig(**config_dict["mcp"]) | ||
| if "a2a" in config_dict: | ||
| config.a2a = A2AConfig(**config_dict["a2a"]) | ||
| if "logging" in config_dict: | ||
| config.logging = LoggingConfig(**config_dict["logging"]) | ||
| if "security" in config_dict: | ||
| config.security = SecurityConfig(**config_dict["security"]) | ||
|
|
||
| return config | ||
|
|
||
|
|
||
| # Global configuration manager instance | ||
| _config_manager: Optional[ConfigManager] = None | ||
|
|
||
|
|
||
| def get_config_manager(config_dir: str = "config") -> ConfigManager: | ||
| """Get global configuration manager instance.""" | ||
| global _config_manager | ||
| if _config_manager is None: | ||
| _config_manager = ConfigManager(config_dir) | ||
| return _config_manager | ||
|
|
||
|
|
||
| def load_config(config_file: Optional[str] = None) -> UnifiedConfig: | ||
| """Load configuration using global manager.""" | ||
| return get_config_manager().load_config(config_file) | ||
|
|
||
|
|
||
| def save_config(config: Optional[UnifiedConfig] = None) -> bool: | ||
| """Save configuration using global manager.""" | ||
| return get_config_manager().save_config(config) | ||
|
|
||
|
|
||
| def get_config() -> UnifiedConfig: | ||
| """Get current configuration using global manager.""" | ||
| return get_config_manager().get_config() | ||
|
|
||
|
|
||
| def update_config_section(section: str, updates: Dict[str, Any]) -> bool: | ||
| """Update configuration section using global manager.""" | ||
| return get_config_manager().update_config(section, updates) | ||
|
|
||
|
|
||
| # Configuration schema for validation | ||
| CONFIG_SCHEMA = { | ||
| "database": { | ||
| "type": "object", | ||
| "properties": { | ||
| "host": {"type": "string"}, | ||
| "port": {"type": "number"}, | ||
| "name": {"type": "string"}, | ||
| "user": {"type": "string"}, | ||
| "password": {"type": "string"} | ||
| } | ||
| }, | ||
| "api": { | ||
| "type": "object", | ||
| "properties": { | ||
| "alpha_vantage_key": {"type": "string"}, | ||
| "finnhub_key": {"type": "string"}, | ||
| "base_url": {"type": "string"}, | ||
| "timeout": {"type": "number"} | ||
| } | ||
| }, | ||
| "tools": { | ||
| "type": "object", | ||
| "properties": { | ||
| "enabled_tools": {"type": "array"}, | ||
| "tool_timeout": {"type": "number"}, | ||
| "max_concurrent_tools": {"type": "number"} | ||
| } | ||
| }, | ||
| "agent": { | ||
| "type": "object", | ||
| "properties": { | ||
| "name": {"type": "string"}, | ||
| "version": {"type": "string"}, | ||
| "model_name": {"type": "string"}, | ||
| "max_tokens": {"type": "number"} | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| # Test the unified configuration system | ||
| print("Testing Unified Configuration System") | ||
|
|
||
| # Initialize configuration manager | ||
| config_manager = ConfigManager() | ||
|
|
||
| # Load configuration | ||
| config = config_manager.load_config() | ||
| print(f"Agent name: {config.agent.name}") | ||
| print(f"Database host: {config.database.host}") | ||
| print(f"Enabled tools: {config.tools.enabled_tools}") | ||
|
|
||
| # Validate configuration | ||
| is_valid = config_manager.validate_config() | ||
| print(f"Configuration valid: {is_valid}") | ||
|
|
||
| # Update configuration | ||
| success = config_manager.update_config("agent", {"temperature": 0.8}) | ||
| print(f"Update successful: {success}") | ||
|
|
||
| # Save configuration | ||
| saved = config_manager.save_config() | ||
| print(f"Configuration saved: {saved}") No newline at end of file |
There was a problem hiding this comment.
This file violates the AgentScope encapsulation standard. According to the guidelines, all Python files under src/agentscope should be named with an underscore prefix, and exposure should be controlled through __init__.py. This file appears to be at the root level without proper encapsulation. Additionally, classes and functions used internally that don't need to be exposed to users must be named with underscore prefixes.
| from pydantic import BaseModel, Field | ||
| import os |
There was a problem hiding this comment.
Third-party library imports should be done at the point of use (lazy loading), not at the top of the file. Move the import of pydantic to where the BaseModel is first used, or use a factory pattern if this is a base class that will be imported by other modules.
| previous = data[key][years[1]] | ||
| if current and previous and previous > 0: | ||
| return (current - previous) / previous | ||
| except (KeyError, TypeError, ZeroDivisionError): |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| previous = data[key][years[1]] | ||
| if current and previous and previous > 0: | ||
| return (current - previous) / previous | ||
| except (KeyError, TypeError, ZeroDivisionError): |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| if os.path.exists(file_path): | ||
| with open(file_path, 'r', encoding='utf-8') as f: | ||
| return json.load(f) | ||
| except Exception: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| if os.path.exists(file_path): | ||
| with open(file_path, 'r', encoding='utf-8') as f: | ||
| return json.load(f) | ||
| except Exception: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| class A2AAgent: | ||
| def __init__(self, *args, **kwargs): | ||
| self.name = kwargs.get('name', 'A2AAgent') | ||
| pass |
There was a problem hiding this comment.
Unnecessary 'pass' statement.
|
@tang1314-dr Thank you for your interest in contributing to AgentScope and for the excellent work in this pull request! However, this sample may be too complex for the examples in the main AgentScope repository. We have created a separate repository called AgentScope-samples to showcase excellent works and advanced examples built with AgentScope. This sample would be a great fit for that repository instead. Would you be interested in contributing it there? We'd be happy to help you with the process! |
…nd universal agent components
AgentScope Version
[The version of AgentScope you are working on, e.g.
import agentscope; print(agentscope.__version__)]Description
[Please describe the background, purpose, changes made, and how to test this PR]
Checklist
Please check the following items before code is ready to be reviewed.
pre-commit run --all-filescommand