Skip to content

feat(financial_analysis_agent): add data fetcher, analyzer, manager a…#1133

Open
tang1314-dr wants to merge 1 commit intoagentscope-ai:mainfrom
tang1314-dr:feature/financial-analysis-enhancements
Open

feat(financial_analysis_agent): add data fetcher, analyzer, manager a…#1133
tang1314-dr wants to merge 1 commit intoagentscope-ai:mainfrom
tang1314-dr:feature/financial-analysis-enhancements

Conversation

@tang1314-dr
Copy link
Copy Markdown

…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

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.

  • Code has been formatted with pre-commit run --all-files command
  • All tests are passing
  • Docstrings are in Google style
  • Related documentation has been updated (e.g. links, examples, etc.)
  • Code is ready for review

…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
Copilot AI review requested due to automatic review settings January 18, 2026 12:17
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Jan 18, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @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

  • AgentScope Framework Compliance: The financial analysis agent has achieved 100% compliance with the AgentScope framework, addressing 7 key compatibility issues.
  • Tool System Reformatted: Class-based tools have been converted to function-based tools utilizing @tool decorators, ensuring proper AgentScope tool registration and execution.
  • AgentBase Skills Implemented: Skills have been refactored to correctly extend AgentBase, enabling framework-compliant agent lifecycle management.
  • MCP Integration Fixed: Robust MCP (Model Context Protocol) integration has been implemented, including fallback mechanisms and proper API usage for external data sources.
  • Message Handling Standardized: Message handling now adheres to the proper AgentScope Msg format, facilitating clean message routing and processing.
  • A2A AgentBase Inheritance: All Agent-to-Agent (A2A) management classes (A2ARegistry, A2ACommunicationManager, FinancialAnalysisWorkflow) have been refactored to properly inherit from AgentBase.
  • Unified Configuration System: A comprehensive, centralized configuration management system has been introduced, featuring dataclass-based configuration, JSON schema validation, and environment variable integration.
  • New Universal Agent Components: Core components for a universal agent have been added, including a flexible configuration system, an intent analyzer, and a model selector.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +420 to +432
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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

Comment on lines +43 to +48
def __init__(self):
"""初始化财务分析技能"""
super().__init__(
name="financial_analysis",
sys_prompt="你是一个专业的财务分析技能专家,能够获取财务数据、计算财务比率、评估投资风险"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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 = "你是一个专业的财务分析技能专家,能够获取财务数据、计算财务比率、评估投资风险"

Comment on lines +99 to +108
def __init__(self, name: str = "A2ARegistry", **kwargs):
super().__init__(
name=name,
sys_prompt=f"""你是A2A智能体注册表,负责:
1. 管理智能体注册和注销
2. 维护智能体能力目录
3. 支持智能体发现和匹配
4. 提供智能体组管理功能""",
**kwargs
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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.

Suggested change
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"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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).

Comment on lines +307 to +308
"- **风险评估**: 识别和评估投资风险"
"- **实时更新**: 实时数据更新",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
"- **风险评估**: 识别和评估投资风险"
"- **实时更新**: 实时数据更新",
"- **风险评估**: 识别和评估投资风险",
"- **实时更新**: 实时数据更新",

### ✅ 已完成的修复

**1. 🛠️ 工具函数标准化**
- **问题**: 原�始工具使用类而非独立函数
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a mojibake character `` in the Chinese text. It should be . This seems to be an encoding issue.

Suggested change
- **问题**: 原�始工具使用类而非独立函数
- **问题**: 原始工具使用类而非独立函数

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]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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))]

Comment on lines +231 to +233
content=json.dumps({
"a2a_message": message.to_dict()
}, ensure_ascii=False),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The regex pattern r'\b(step|步骤|阶段|部分|部分)\b' contains the keyword 部分 twice. This is redundant and should be cleaned up.

Suggested change
step_count = len(re.findall(r'\b(step|步骤|阶段|部分|部分)\b', message))
step_count = len(re.findall(r'\b(step|步骤|阶段|部分)\b', message))

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +128 to +157
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"]
))

Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +10
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
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
@@ -0,0 +1,265 @@
"""
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
Comment thread unified_config_fixed.py
Comment on lines +1 to +380
#!/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
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +5 to +6
from pydantic import BaseModel, Field
import os
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
previous = data[key][years[1]]
if current and previous and previous > 0:
return (current - previous) / previous
except (KeyError, TypeError, ZeroDivisionError):
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
previous = data[key][years[1]]
if current and previous and previous > 0:
return (current - previous) / previous
except (KeyError, TypeError, ZeroDivisionError):
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
class A2AAgent:
def __init__(self, *args, **kwargs):
self.name = kwargs.get('name', 'A2AAgent')
pass
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary 'pass' statement.

Copilot uses AI. Check for mistakes.
@DavdGao
Copy link
Copy Markdown
Member

DavdGao commented Jan 19, 2026

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants