From ff974eb1988ff1c9d5ae8183b22f07875e392703 Mon Sep 17 00:00:00 2001 From: zhonghao lu Date: Tue, 16 Sep 2025 13:44:37 +0800 Subject: [PATCH 1/6] refactor database model for agent management --- python/scripts/init_database.py | 16 + python/valuecell/server/db/README.md | 201 ++++++++++++ python/valuecell/server/db/__init__.py | 19 ++ python/valuecell/server/db/connection.py | 103 ++++++ python/valuecell/server/db/init_db.py | 309 ++++++++++++++++++ python/valuecell/server/db/models/__init__.py | 17 +- python/valuecell/server/db/models/agent.py | 153 +++++++++ 7 files changed, 816 insertions(+), 2 deletions(-) create mode 100644 python/scripts/init_database.py create mode 100644 python/valuecell/server/db/README.md create mode 100644 python/valuecell/server/db/connection.py create mode 100644 python/valuecell/server/db/init_db.py create mode 100644 python/valuecell/server/db/models/agent.py diff --git a/python/scripts/init_database.py b/python/scripts/init_database.py new file mode 100644 index 000000000..986311864 --- /dev/null +++ b/python/scripts/init_database.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Standalone database initialization script for ValueCell.""" + +import sys +import os +from pathlib import Path + +# Add the project root to Python path +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +# Now import from valuecell +from valuecell.server.db.init_db import main + +if __name__ == "__main__": + main() diff --git a/python/valuecell/server/db/README.md b/python/valuecell/server/db/README.md new file mode 100644 index 000000000..f923edaca --- /dev/null +++ b/python/valuecell/server/db/README.md @@ -0,0 +1,201 @@ +# ValueCell 数据库初始化 + +这个目录包含了 ValueCell Server 的数据库相关代码,包括模型定义、连接管理和初始化脚本。 + +## 目录结构 + +``` +db/ +├── __init__.py # 数据库包初始化 +├── connection.py # 数据库连接和会话管理 +├── init_db.py # 数据库初始化脚本 +├── models/ # 数据库模型 +│ ├── __init__.py +│ ├── base.py # 基础模型类 +│ └── agent.py # Agent模型 +└── README.md # 本文档 +``` + +## 数据库配置 + +数据库配置在 `valuecell/server/config/settings.py` 中定义: + +- **DATABASE_URL**: 数据库连接URL,默认为 `sqlite:///./valuecell.db` +- **DB_ECHO**: 是否输出SQL日志,默认为 `false` + +## 数据库模型 + +### Agent模型 (Agent) +Agent模型存储了ValueCell系统中所有可用AI代理的信息: + +**基本信息**: +- `name`: 唯一的代理标识符 +- `display_name`: 人类可读的显示名称 +- `description`: 代理功能和用途的详细描述 +- `url`: 代理服务的基础URL +- `version`: 代理版本号 + +**状态管理**: +- `enabled`: 代理是否启用 +- `is_active`: 代理是否活跃可用 + +**功能描述**: +- `capabilities`: JSON格式的功能描述(如流式传输、推送通知等) +- `agent_metadata`: 额外的元数据(作者、标签、支持的功能等) +- `config`: 代理特定的配置参数 + +**性能跟踪**: +- `last_health_check`: 最后一次健康检查时间 +- `total_requests`: 处理的总请求数 +- `success_rate`: 成功率百分比 + +**时间戳**: +- `created_at`: 创建时间 +- `updated_at`: 更新时间 + +## 数据库初始化 + +### 使用方法 + +1. **基本初始化**: + ```bash + cd /path/to/valuecell/python + python3 -m valuecell.server.db.init_db + ``` + +2. **强制重新初始化**: + ```bash + python3 -m valuecell.server.db.init_db --force + ``` + +3. **详细日志输出**: + ```bash + python3 -m valuecell.server.db.init_db --verbose + ``` + +4. **使用独立脚本**: + ```bash + python3 scripts/init_database.py + ``` + +### 初始化过程 + +1. **检查数据库文件**:验证SQLite数据库文件是否存在 +2. **创建数据库文件**:如果不存在则创建新的数据库文件 +3. **创建表结构**:根据模型定义创建agents表 +4. **初始化Agent数据**: + - 从 `configs/agent_cards/` 目录加载所有JSON配置文件 + - 为每个配置文件创建对应的Agent记录 + - 支持更新现有Agent的配置信息 +5. **验证初始化**:确认数据库连接和表结构正确 + +### Agent配置文件 + +初始化脚本会自动加载 `configs/agent_cards/` 目录下的所有JSON配置文件: + +**配置文件示例**: +```json +{ + "name": "TradingAgentsAdapter", + "url": "http://localhost:10002", + "description": "TradingAgents - Multi-agent trading analysis system", + "capabilities": { + "streaming": true, + "push_notifications": false + }, + "metadata": { + "version": "1.0.0", + "author": "ValueCell Team", + "tags": ["trading", "analysis", "multi-agent"], + "supported_tickers": ["AAPL", "GOOGL", "MSFT"], + "supported_analysts": ["market", "social", "news"] + } +} +``` + +**当前支持的Agent**: +- `AIHedgeFundAgent`: AI对冲基金代理 +- `Sec13FundAgent`: SEC 13F基金分析代理 +- `TradingAgentsAdapter`: 多代理交易分析系统 + +## 在代码中使用 + +### 获取数据库会话 + +```python +from valuecell.server.db import get_db, Agent + +# 在FastAPI路由中使用依赖注入 +@app.get("/api/agents") +def get_agents(db: Session = Depends(get_db)): + return db.query(Agent).filter(Agent.enabled == True).all() +``` + +### 直接使用数据库管理器 + +```python +from valuecell.server.db import get_database_manager, Agent + +db_manager = get_database_manager() +session = db_manager.get_session() + +try: + # 获取所有启用的代理 + agents = session.query(Agent).filter(Agent.enabled == True).all() + + # 获取特定代理 + trading_agent = session.query(Agent).filter(Agent.name == "TradingAgentsAdapter").first() + + # 更新代理状态 + if trading_agent: + trading_agent.is_active = True + session.commit() +finally: + session.close() +``` + +### 程序化初始化 + +```python +from valuecell.server.db import init_database + +# 初始化数据库 +success = init_database(force=False) +if success: + print("数据库初始化成功") +else: + print("数据库初始化失败") +``` + +## 注意事项 + +1. **密码安全**:默认管理员用户的密码是占位符,在生产环境中需要替换为正确的哈希密码 +2. **数据库备份**:SQLite数据库文件应该定期备份 +3. **权限管理**:确保数据库文件有适当的文件系统权限 +4. **环境变量**:可以通过环境变量 `DATABASE_URL` 自定义数据库连接 + +## 故障排除 + +### 常见问题 + +1. **权限错误**:确保对数据库文件目录有写权限 +2. **模块导入错误**:确保在正确的Python环境中运行 +3. **数据库锁定**:确保没有其他进程正在使用数据库文件 + +### 重置数据库 + +如果需要完全重置数据库: + +```bash +# 删除现有数据库文件 +rm valuecell.db + +# 重新初始化 +python3 -m valuecell.server.db.init_db +``` + +或者使用强制重新初始化: + +```bash +python3 -m valuecell.server.db.init_db --force +``` \ No newline at end of file diff --git a/python/valuecell/server/db/__init__.py b/python/valuecell/server/db/__init__.py index e69de29bb..b940294b5 100644 --- a/python/valuecell/server/db/__init__.py +++ b/python/valuecell/server/db/__init__.py @@ -0,0 +1,19 @@ +"""Database package for ValueCell Server.""" + +from .connection import ( + DatabaseManager, + get_database_manager, + get_db, +) +from .init_db import DatabaseInitializer, init_database +from .models import * + +__all__ = [ + # Connection management + "DatabaseManager", + "get_database_manager", + "get_db", + # Database initialization + "DatabaseInitializer", + "init_database", +] diff --git a/python/valuecell/server/db/connection.py b/python/valuecell/server/db/connection.py new file mode 100644 index 000000000..1611e6a67 --- /dev/null +++ b/python/valuecell/server/db/connection.py @@ -0,0 +1,103 @@ +"""Database connection and session management for ValueCell Server.""" + +import os +from typing import Generator +from sqlalchemy import create_engine, Engine +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.pool import StaticPool + +from ..config.settings import get_settings +from .models.base import Base + + +class DatabaseManager: + """Database connection and session manager.""" + + def __init__(self): + """Initialize database manager.""" + self.settings = get_settings() + self.engine: Engine = None + self.SessionLocal = None + self._initialize_engine() + + def _initialize_engine(self) -> None: + """Initialize database engine.""" + database_config = self.settings.get_database_config() + + # SQLite specific configuration + connect_args = {} + if database_config["url"].startswith("sqlite"): + connect_args = { + "check_same_thread": False, + "timeout": 20, + } + + self.engine = create_engine( + database_config["url"], + echo=database_config["echo"], + connect_args=connect_args, + poolclass=StaticPool + if database_config["url"].startswith("sqlite") + else None, + ) + + self.SessionLocal = sessionmaker( + autocommit=False, autoflush=False, bind=self.engine + ) + + def get_engine(self) -> Engine: + """Get database engine.""" + return self.engine + + def create_tables(self) -> None: + """Create all tables defined in models.""" + Base.metadata.create_all(bind=self.engine) + + def drop_tables(self) -> None: + """Drop all tables.""" + Base.metadata.drop_all(bind=self.engine) + + def get_session(self) -> Session: + """Get a new database session.""" + return self.SessionLocal() + + def get_db_session(self) -> Generator[Session, None, None]: + """Get database session for dependency injection.""" + db = self.SessionLocal() + try: + yield db + finally: + db.close() + + +# Global database manager instance +_db_manager: DatabaseManager = None + + +def get_database_manager() -> DatabaseManager: + """Get global database manager instance.""" + global _db_manager + if _db_manager is None: + _db_manager = DatabaseManager() + return _db_manager + + +def get_db() -> Generator[Session, None, None]: + """Get database session for FastAPI dependency injection.""" + db_manager = get_database_manager() + yield from db_manager.get_db_session() + + +def get_engine() -> Engine: + """Get database engine.""" + return get_database_manager().get_engine() + + +def create_tables() -> None: + """Create all database tables.""" + get_database_manager().create_tables() + + +def drop_tables() -> None: + """Drop all database tables.""" + get_database_manager().drop_tables() diff --git a/python/valuecell/server/db/init_db.py b/python/valuecell/server/db/init_db.py new file mode 100644 index 000000000..8329f727c --- /dev/null +++ b/python/valuecell/server/db/init_db.py @@ -0,0 +1,309 @@ +"""Database initialization script for ValueCell Server.""" + +import logging +import sys +from pathlib import Path +from typing import Optional + +from sqlalchemy import text, inspect +from sqlalchemy.exc import SQLAlchemyError + +from .connection import get_database_manager, DatabaseManager +from .models.base import Base +from ..config.settings import get_settings + +# Configure logging +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + + +class DatabaseInitializer: + """Database initialization manager.""" + + def __init__(self, db_manager: Optional[DatabaseManager] = None): + """Initialize database initializer.""" + self.db_manager = db_manager or get_database_manager() + self.settings = get_settings() + self.engine = self.db_manager.get_engine() + + def check_database_exists(self) -> bool: + """Check if database file exists (for SQLite).""" + database_url = self.settings.DATABASE_URL + + if database_url.startswith("sqlite:///"): + # Extract file path from SQLite URL + db_path = database_url.replace("sqlite:///", "") + if db_path.startswith("./"): + # Relative path + db_path = Path.cwd() / db_path[2:] + else: + db_path = Path(db_path) + + return db_path.exists() + + # For other databases, try to connect + try: + with self.engine.connect() as conn: + conn.execute(text("SELECT 1")) + return True + except SQLAlchemyError: + return False + + def check_tables_exist(self) -> bool: + """Check if tables exist in database.""" + try: + inspector = inspect(self.engine) + existing_tables = inspector.get_table_names() + + # Get all table names from metadata + expected_tables = list(Base.metadata.tables.keys()) + + if not expected_tables: + logger.info("No tables defined in models") + return True + + # Check if all expected tables exist + missing_tables = set(expected_tables) - set(existing_tables) + if missing_tables: + logger.info(f"Missing tables: {missing_tables}") + return False + + logger.info(f"All tables exist: {existing_tables}") + return True + + except SQLAlchemyError as e: + logger.error(f"Error checking tables: {e}") + return False + + def create_database_file(self) -> bool: + """Create database file (for SQLite).""" + database_url = self.settings.DATABASE_URL + + if database_url.startswith("sqlite:///"): + # Extract file path from SQLite URL + db_path = database_url.replace("sqlite:///", "") + if db_path.startswith("./"): + # Relative path + db_path = Path.cwd() / db_path[2:] + else: + db_path = Path(db_path) + + try: + # Create parent directories if they don't exist + db_path.parent.mkdir(parents=True, exist_ok=True) + + # Create empty database file + db_path.touch() + logger.info(f"Created database file: {db_path}") + return True + + except Exception as e: + logger.error(f"Error creating database file: {e}") + return False + + logger.info("Database file creation not needed for non-SQLite databases") + return True + + def create_tables(self) -> bool: + """Create all tables.""" + try: + logger.info("Creating database tables...") + Base.metadata.create_all(bind=self.engine) + logger.info("Database tables created successfully") + return True + + except SQLAlchemyError as e: + logger.error(f"Error creating tables: {e}") + return False + + def initialize_basic_data(self) -> bool: + """Initialize agent data from configuration files.""" + try: + logger.info("Initializing agent data...") + + # Get a database session + session = self.db_manager.get_session() + + try: + # Import models here to avoid circular imports + from .models.agent import Agent + import json + import os + from pathlib import Path + + # Get the project root directory + project_root = Path(__file__).parent.parent.parent.parent + agent_configs_dir = project_root / "configs" / "agent_cards" + + if not agent_configs_dir.exists(): + logger.warning( + f"Agent configs directory not found: {agent_configs_dir}" + ) + return True + + # Load agent configurations from JSON files + for config_file in agent_configs_dir.glob("*.json"): + try: + with open(config_file, "r", encoding="utf-8") as f: + config_data = json.load(f) + + agent_name = config_data.get("name") + if not agent_name: + logger.warning( + f"Agent config missing 'name' field: {config_file}" + ) + continue + + # Check if agent already exists + existing_agent = ( + session.query(Agent).filter_by(name=agent_name).first() + ) + if not existing_agent: + # Create new agent from config + agent = Agent.from_config(config_data) + session.add(agent) + logger.info(f"Added agent: {agent_name}") + else: + # Update existing agent with new config data + existing_agent.display_name = config_data.get( + "display_name", existing_agent.display_name + ) + existing_agent.description = config_data.get( + "description", existing_agent.description + ) + existing_agent.url = config_data.get( + "url", existing_agent.url + ) + existing_agent.version = config_data.get( + "version", existing_agent.version + ) + existing_agent.enabled = config_data.get( + "enabled", existing_agent.enabled + ) + existing_agent.capabilities = config_data.get( + "capabilities", existing_agent.capabilities + ) + existing_agent.agent_metadata = config_data.get( + "metadata", existing_agent.agent_metadata + ) + existing_agent.config = config_data.get( + "config", existing_agent.config + ) + logger.info(f"Updated agent: {agent_name}") + + except Exception as e: + logger.error(f"Error loading agent config {config_file}: {e}") + continue + + session.commit() + logger.info("Agent data initialization completed") + return True + + except Exception as e: + session.rollback() + logger.error(f"Error initializing agent data: {e}") + return False + finally: + session.close() + + except Exception as e: + logger.error(f"Error getting database session: {e}") + return False + + def verify_initialization(self) -> bool: + """Verify database initialization.""" + try: + # Test database connection + with self.engine.connect() as conn: + result = conn.execute(text("SELECT 1")) + result.fetchone() + + # Check if tables exist + if not self.check_tables_exist(): + logger.error("Table verification failed") + return False + + logger.info("Database initialization verified successfully") + return True + + except SQLAlchemyError as e: + logger.error(f"Database verification failed: {e}") + return False + + def initialize(self, force: bool = False) -> bool: + """Initialize database completely.""" + logger.info("Starting database initialization...") + + # Check if database already exists and is properly initialized + if not force and self.check_database_exists() and self.check_tables_exist(): + logger.info("Database already exists and is properly initialized") + return True + + # Step 1: Create database file (for SQLite) + if not self.create_database_file(): + logger.error("Failed to create database file") + return False + + # Step 2: Create tables + if not self.create_tables(): + logger.error("Failed to create tables") + return False + + # Step 3: Initialize basic data + if not self.initialize_basic_data(): + logger.error("Failed to initialize basic data") + return False + + # Step 4: Verify initialization + if not self.verify_initialization(): + logger.error("Database initialization verification failed") + return False + + logger.info("Database initialization completed successfully") + return True + + +def init_database(force: bool = False) -> bool: + """Initialize database with all tables and basic data.""" + try: + initializer = DatabaseInitializer() + return initializer.initialize(force=force) + except Exception as e: + logger.error(f"Database initialization failed: {e}") + return False + + +def main(): + """Main entry point for database initialization script.""" + import argparse + + parser = argparse.ArgumentParser(description="Initialize ValueCell database") + parser.add_argument( + "--force", + action="store_true", + help="Force re-initialization even if database exists", + ) + parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") + + args = parser.parse_args() + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + logger.info("ValueCell Database Initialization") + logger.info("=" * 50) + + success = init_database(force=args.force) + + if success: + logger.info("Database initialization completed successfully!") + sys.exit(0) + else: + logger.error("Database initialization failed!") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/python/valuecell/server/db/models/__init__.py b/python/valuecell/server/db/models/__init__.py index 7fde07179..0cd9fc9c3 100644 --- a/python/valuecell/server/db/models/__init__.py +++ b/python/valuecell/server/db/models/__init__.py @@ -1,5 +1,18 @@ -"""Database models for ValueCell Server.""" +""" +ValueCell Server - Database Models +This package contains all database models for the ValueCell server. +All models are automatically imported to ensure they are registered with SQLAlchemy. +""" + +# Import base model from .base import Base -__all__ = ["Base"] +# Import all models to ensure they are registered with SQLAlchemy +from .agent import Agent + +# Export all models +__all__ = [ + "Base", + "Agent", +] diff --git a/python/valuecell/server/db/models/agent.py b/python/valuecell/server/db/models/agent.py new file mode 100644 index 000000000..7aebcc288 --- /dev/null +++ b/python/valuecell/server/db/models/agent.py @@ -0,0 +1,153 @@ +""" +ValueCell Server - Agent Models + +This module defines the database models for agents in the ValueCell system. +""" + +from datetime import datetime +from typing import Dict, List, Optional, Any +from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON +from sqlalchemy.sql import func + +from .base import Base + + +class Agent(Base): + """ + Agent model representing an AI agent in the ValueCell system. + + This table stores information about available agents, their capabilities, + configuration, and metadata. + """ + + __tablename__ = "agents" + + # Primary key + id = Column(Integer, primary_key=True, index=True) + + # Basic agent information + name = Column( + String(100), + unique=True, + nullable=False, + index=True, + comment="Unique agent name/identifier", + ) + display_name = Column( + String(200), nullable=True, comment="Human-readable display name" + ) + description = Column( + Text, + nullable=True, + comment="Detailed description of the agent's purpose and capabilities", + ) + + # Agent configuration + url = Column( + String(500), + nullable=False, + comment="Base URL where the agent service is hosted", + ) + version = Column( + String(50), nullable=True, default="1.0.0", comment="Agent version" + ) + + # Status and availability + enabled = Column( + Boolean, + default=True, + nullable=False, + comment="Whether the agent is currently enabled", + ) + is_active = Column( + Boolean, + default=True, + nullable=False, + comment="Whether the agent is active and available", + ) + + # Capabilities and metadata + capabilities = Column( + JSON, + nullable=True, + comment="JSON object describing agent capabilities (streaming, notifications, etc.)", + ) + agent_metadata = Column( + JSON, + nullable=True, + comment="Additional metadata (author, tags, supported features, etc.)", + ) + + # Configuration + config = Column( + JSON, nullable=True, comment="Agent-specific configuration parameters" + ) + + # Performance and usage tracking + last_health_check = Column( + DateTime(timezone=True), + nullable=True, + comment="Timestamp of last successful health check", + ) + total_requests = Column( + Integer, + default=0, + nullable=False, + comment="Total number of requests processed by this agent", + ) + success_rate = Column( + String(10), nullable=True, comment="Success rate percentage (e.g., '95.5%')" + ) + + # Timestamps + created_at = Column( + DateTime(timezone=True), server_default=func.now(), nullable=False + ) + updated_at = Column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + + def __repr__(self): + return f"" + + def to_dict(self) -> Dict[str, Any]: + """Convert agent to dictionary representation.""" + return { + "id": self.id, + "name": self.name, + "display_name": self.display_name, + "description": self.description, + "url": self.url, + "version": self.version, + "enabled": self.enabled, + "is_active": self.is_active, + "capabilities": self.capabilities, + "metadata": self.agent_metadata, + "config": self.config, + "last_health_check": self.last_health_check.isoformat() + if self.last_health_check + else None, + "total_requests": self.total_requests, + "success_rate": self.success_rate, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + @classmethod + def from_config(cls, config_data: Dict[str, Any]) -> "Agent": + """Create an Agent instance from configuration data.""" + return cls( + name=config_data.get("name"), + display_name=config_data.get("display_name", config_data.get("name")), + description=config_data.get("description"), + url=config_data.get("url"), + version=config_data.get("version", "1.0.0"), + enabled=config_data.get("enabled", True), + is_active=config_data.get("is_active", True), + capabilities=config_data.get("capabilities"), + agent_metadata=config_data.get("metadata"), + config=config_data.get("config"), + ) From 5a8c744204c010f1d08d95ea219253878d246bcd Mon Sep 17 00:00:00 2001 From: zhonghao lu Date: Tue, 16 Sep 2025 14:23:11 +0800 Subject: [PATCH 2/6] refactor simplify agent model fields --- python/valuecell/server/db/models/agent.py | 32 ++-------------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/python/valuecell/server/db/models/agent.py b/python/valuecell/server/db/models/agent.py index 7aebcc288..cf9e37dac 100644 --- a/python/valuecell/server/db/models/agent.py +++ b/python/valuecell/server/db/models/agent.py @@ -43,11 +43,6 @@ class Agent(Base): ) # Agent configuration - url = Column( - String(500), - nullable=False, - comment="Base URL where the agent service is hosted", - ) version = Column( String(50), nullable=True, default="1.0.0", comment="Agent version" ) @@ -83,22 +78,6 @@ class Agent(Base): JSON, nullable=True, comment="Agent-specific configuration parameters" ) - # Performance and usage tracking - last_health_check = Column( - DateTime(timezone=True), - nullable=True, - comment="Timestamp of last successful health check", - ) - total_requests = Column( - Integer, - default=0, - nullable=False, - comment="Total number of requests processed by this agent", - ) - success_rate = Column( - String(10), nullable=True, comment="Success rate percentage (e.g., '95.5%')" - ) - # Timestamps created_at = Column( DateTime(timezone=True), server_default=func.now(), nullable=False @@ -120,18 +99,12 @@ def to_dict(self) -> Dict[str, Any]: "name": self.name, "display_name": self.display_name, "description": self.description, - "url": self.url, "version": self.version, "enabled": self.enabled, "is_active": self.is_active, "capabilities": self.capabilities, - "metadata": self.agent_metadata, + "agent_metadata": self.agent_metadata, "config": self.config, - "last_health_check": self.last_health_check.isoformat() - if self.last_health_check - else None, - "total_requests": self.total_requests, - "success_rate": self.success_rate, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, } @@ -143,11 +116,10 @@ def from_config(cls, config_data: Dict[str, Any]) -> "Agent": name=config_data.get("name"), display_name=config_data.get("display_name", config_data.get("name")), description=config_data.get("description"), - url=config_data.get("url"), version=config_data.get("version", "1.0.0"), enabled=config_data.get("enabled", True), is_active=config_data.get("is_active", True), capabilities=config_data.get("capabilities"), agent_metadata=config_data.get("metadata"), config=config_data.get("config"), - ) + ) \ No newline at end of file From 3a81886bb09e1bcbc16dd223fcadc197f9e176f0 Mon Sep 17 00:00:00 2001 From: zhonghao lu Date: Tue, 16 Sep 2025 14:28:14 +0800 Subject: [PATCH 3/6] refine db README --- python/valuecell/server/db/README.md | 167 +++++++++++++-------------- 1 file changed, 80 insertions(+), 87 deletions(-) diff --git a/python/valuecell/server/db/README.md b/python/valuecell/server/db/README.md index f923edaca..a3f7f2831 100644 --- a/python/valuecell/server/db/README.md +++ b/python/valuecell/server/db/README.md @@ -1,103 +1,96 @@ -# ValueCell 数据库初始化 +# ValueCell Database Initialization -这个目录包含了 ValueCell Server 的数据库相关代码,包括模型定义、连接管理和初始化脚本。 +This directory contains database-related code for ValueCell Server, including model definitions, connection management, and initialization scripts. -## 目录结构 +## Directory Structure ``` db/ -├── __init__.py # 数据库包初始化 -├── connection.py # 数据库连接和会话管理 -├── init_db.py # 数据库初始化脚本 -├── models/ # 数据库模型 +├── __init__.py # Database package initialization +├── connection.py # Database connection and session management +├── init_db.py # Database initialization script +├── models/ # Database models │ ├── __init__.py -│ ├── base.py # 基础模型类 -│ └── agent.py # Agent模型 -└── README.md # 本文档 +│ ├── base.py # Base model class +│ └── agent.py # Agent model +└── README.md # This document ``` -## 数据库配置 +## Database Configuration -数据库配置在 `valuecell/server/config/settings.py` 中定义: +Database configuration is defined in `valuecell/server/config/settings.py`: -- **DATABASE_URL**: 数据库连接URL,默认为 `sqlite:///./valuecell.db` -- **DB_ECHO**: 是否输出SQL日志,默认为 `false` +- **DATABASE_URL**: Database connection URL, defaults to `sqlite:///./valuecell.db` +- **DB_ECHO**: Whether to output SQL logs, defaults to `false` -## 数据库模型 +## Database Models -### Agent模型 (Agent) -Agent模型存储了ValueCell系统中所有可用AI代理的信息: +### Agent Model (Agent) +The Agent model stores information about all available AI agents in the ValueCell system: -**基本信息**: -- `name`: 唯一的代理标识符 -- `display_name`: 人类可读的显示名称 -- `description`: 代理功能和用途的详细描述 -- `url`: 代理服务的基础URL -- `version`: 代理版本号 +**Basic Information**: +- `name`: Unique agent identifier +- `display_name`: Human-readable display name +- `description`: Detailed description of agent functionality and purpose +- `version`: Agent version number -**状态管理**: -- `enabled`: 代理是否启用 -- `is_active`: 代理是否活跃可用 +**State Management**: +- `enabled`: Whether the agent is enabled +- `is_active`: Whether the agent is active and available -**功能描述**: -- `capabilities`: JSON格式的功能描述(如流式传输、推送通知等) -- `agent_metadata`: 额外的元数据(作者、标签、支持的功能等) -- `config`: 代理特定的配置参数 +**Functionality**: +- `capabilities`: JSON format capability description (e.g., streaming, push notifications) +- `agent_metadata`: Additional metadata (author, tags, supported features, etc.) +- `config`: Agent-specific configuration parameters -**性能跟踪**: -- `last_health_check`: 最后一次健康检查时间 -- `total_requests`: 处理的总请求数 -- `success_rate`: 成功率百分比 +**Timestamps**: +- `created_at`: Creation time +- `updated_at`: Update time -**时间戳**: -- `created_at`: 创建时间 -- `updated_at`: 更新时间 +## Database Initialization -## 数据库初始化 +### Usage -### 使用方法 - -1. **基本初始化**: +1. **Basic initialization**: ```bash cd /path/to/valuecell/python python3 -m valuecell.server.db.init_db ``` -2. **强制重新初始化**: +2. **Force re-initialization**: ```bash python3 -m valuecell.server.db.init_db --force ``` -3. **详细日志输出**: +3. **Verbose logging**: ```bash python3 -m valuecell.server.db.init_db --verbose ``` -4. **使用独立脚本**: +4. **Using standalone script**: ```bash python3 scripts/init_database.py ``` -### 初始化过程 +### Initialization Process -1. **检查数据库文件**:验证SQLite数据库文件是否存在 -2. **创建数据库文件**:如果不存在则创建新的数据库文件 -3. **创建表结构**:根据模型定义创建agents表 -4. **初始化Agent数据**: - - 从 `configs/agent_cards/` 目录加载所有JSON配置文件 - - 为每个配置文件创建对应的Agent记录 - - 支持更新现有Agent的配置信息 -5. **验证初始化**:确认数据库连接和表结构正确 +1. **Check database file**: Verify if SQLite database file exists +2. **Create database file**: Create new database file if it doesn't exist +3. **Create table structure**: Create agents table based on model definitions +4. **Initialize Agent data**: + - Load all JSON configuration files from `configs/agent_cards/` directory + - Create corresponding Agent records for each configuration file + - Support updating existing Agent configuration information +5. **Verify initialization**: Confirm database connection and table structure are correct -### Agent配置文件 +### Agent Configuration Files -初始化脚本会自动加载 `configs/agent_cards/` 目录下的所有JSON配置文件: +The initialization script automatically loads all JSON configuration files from the `configs/agent_cards/` directory: -**配置文件示例**: +**Configuration file example**: ```json { "name": "TradingAgentsAdapter", - "url": "http://localhost:10002", "description": "TradingAgents - Multi-agent trading analysis system", "capabilities": { "streaming": true, @@ -113,25 +106,25 @@ Agent模型存储了ValueCell系统中所有可用AI代理的信息: } ``` -**当前支持的Agent**: -- `AIHedgeFundAgent`: AI对冲基金代理 -- `Sec13FundAgent`: SEC 13F基金分析代理 -- `TradingAgentsAdapter`: 多代理交易分析系统 +**Currently supported Agents**: +- `AIHedgeFundAgent`: AI hedge fund agent +- `Sec13FundAgent`: SEC 13F fund analysis agent +- `TradingAgentsAdapter`: Multi-agent trading analysis system -## 在代码中使用 +## Usage in Code -### 获取数据库会话 +### Getting Database Session ```python from valuecell.server.db import get_db, Agent -# 在FastAPI路由中使用依赖注入 +# Using dependency injection in FastAPI routes @app.get("/api/agents") def get_agents(db: Session = Depends(get_db)): return db.query(Agent).filter(Agent.enabled == True).all() ``` -### 直接使用数据库管理器 +### Direct Database Manager Usage ```python from valuecell.server.db import get_database_manager, Agent @@ -140,13 +133,13 @@ db_manager = get_database_manager() session = db_manager.get_session() try: - # 获取所有启用的代理 + # Get all enabled agents agents = session.query(Agent).filter(Agent.enabled == True).all() - # 获取特定代理 + # Get specific agent trading_agent = session.query(Agent).filter(Agent.name == "TradingAgentsAdapter").first() - # 更新代理状态 + # Update agent status if trading_agent: trading_agent.is_active = True session.commit() @@ -154,47 +147,47 @@ finally: session.close() ``` -### 程序化初始化 +### Programmatic Initialization ```python from valuecell.server.db import init_database -# 初始化数据库 +# Initialize database success = init_database(force=False) if success: - print("数据库初始化成功") + print("Database initialization successful") else: - print("数据库初始化失败") + print("Database initialization failed") ``` -## 注意事项 +## Important Notes -1. **密码安全**:默认管理员用户的密码是占位符,在生产环境中需要替换为正确的哈希密码 -2. **数据库备份**:SQLite数据库文件应该定期备份 -3. **权限管理**:确保数据库文件有适当的文件系统权限 -4. **环境变量**:可以通过环境变量 `DATABASE_URL` 自定义数据库连接 +1. **Password Security**: Default admin user password is a placeholder and should be replaced with proper hashed password in production environment +2. **Database Backup**: SQLite database file should be backed up regularly +3. **Permission Management**: Ensure database file has appropriate filesystem permissions +4. **Environment Variables**: Database connection can be customized through `DATABASE_URL` environment variable -## 故障排除 +## Troubleshooting -### 常见问题 +### Common Issues -1. **权限错误**:确保对数据库文件目录有写权限 -2. **模块导入错误**:确保在正确的Python环境中运行 -3. **数据库锁定**:确保没有其他进程正在使用数据库文件 +1. **Permission Error**: Ensure write permissions to database file directory +2. **Module Import Error**: Ensure running in correct Python environment +3. **Database Lock**: Ensure no other processes are using the database file -### 重置数据库 +### Reset Database -如果需要完全重置数据库: +If you need to completely reset the database: ```bash -# 删除现有数据库文件 +# Delete existing database file rm valuecell.db -# 重新初始化 +# Re-initialize python3 -m valuecell.server.db.init_db ``` -或者使用强制重新初始化: +Or use force re-initialization: ```bash python3 -m valuecell.server.db.init_db --force From 1588f293e3799f1b87f6f3141d26376fbf1b51a9 Mon Sep 17 00:00:00 2001 From: zhonghao lu Date: Tue, 16 Sep 2025 14:43:05 +0800 Subject: [PATCH 4/6] lint --- python/scripts/init_database.py | 20 ++- python/valuecell/server/db/README.md | 31 ++-- python/valuecell/server/db/__init__.py | 5 +- python/valuecell/server/db/connection.py | 1 - python/valuecell/server/db/init_db.py | 196 ++++++++++++++------- python/valuecell/server/db/models/agent.py | 5 +- 6 files changed, 165 insertions(+), 93 deletions(-) diff --git a/python/scripts/init_database.py b/python/scripts/init_database.py index 986311864..a38f25ced 100644 --- a/python/scripts/init_database.py +++ b/python/scripts/init_database.py @@ -2,15 +2,21 @@ """Standalone database initialization script for ValueCell.""" import sys -import os from pathlib import Path -# Add the project root to Python path -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) -# Now import from valuecell -from valuecell.server.db.init_db import main +def setup_path_and_run(): + """Setup Python path and run the database initialization.""" + # Add the project root to Python path + project_root = Path(__file__).parent.parent + sys.path.insert(0, str(project_root)) + + # Import after path setup to avoid import errors + import valuecell.server.db.init_db as init_db_module + + # Run the main function + init_db_module.main() + if __name__ == "__main__": - main() + setup_path_and_run() diff --git a/python/valuecell/server/db/README.md b/python/valuecell/server/db/README.md index a3f7f2831..f058f207c 100644 --- a/python/valuecell/server/db/README.md +++ b/python/valuecell/server/db/README.md @@ -78,23 +78,33 @@ The Agent model stores information about all available AI agents in the ValueCel 2. **Create database file**: Create new database file if it doesn't exist 3. **Create table structure**: Create agents table based on model definitions 4. **Initialize Agent data**: - - Load all JSON configuration files from `configs/agent_cards/` directory - - Create corresponding Agent records for each configuration file + - Insert default Agent records directly from code + - Create three default agents: AIHedgeFundAgent, Sec13FundAgent, and TradingAgentsAdapter - Support updating existing Agent configuration information 5. **Verify initialization**: Confirm database connection and table structure are correct -### Agent Configuration Files +### Default Agent Records -The initialization script automatically loads all JSON configuration files from the `configs/agent_cards/` directory: +The initialization script automatically creates default Agent records directly in the code: -**Configuration file example**: -```json +**Default agents created**: + +1. **AIHedgeFundAgent**: AI-powered hedge fund analysis and trading agent +2. **Sec13FundAgent**: SEC 13F fund analysis and tracking agent +3. **TradingAgentsAdapter**: Multi-agent trading analysis system with market, sentiment, news and fundamentals analysis + +**Agent data structure example**: +```python { "name": "TradingAgentsAdapter", + "display_name": "Trading Agents Adapter", "description": "TradingAgents - Multi-agent trading analysis system", + "version": "1.0.0", + "enabled": True, + "is_active": True, "capabilities": { - "streaming": true, - "push_notifications": false + "streaming": True, + "push_notifications": False }, "metadata": { "version": "1.0.0", @@ -106,11 +116,6 @@ The initialization script automatically loads all JSON configuration files from } ``` -**Currently supported Agents**: -- `AIHedgeFundAgent`: AI hedge fund agent -- `Sec13FundAgent`: SEC 13F fund analysis agent -- `TradingAgentsAdapter`: Multi-agent trading analysis system - ## Usage in Code ### Getting Database Session diff --git a/python/valuecell/server/db/__init__.py b/python/valuecell/server/db/__init__.py index b940294b5..a6fe1ae3b 100644 --- a/python/valuecell/server/db/__init__.py +++ b/python/valuecell/server/db/__init__.py @@ -6,7 +6,7 @@ get_db, ) from .init_db import DatabaseInitializer, init_database -from .models import * +from .models import Base, Agent __all__ = [ # Connection management @@ -16,4 +16,7 @@ # Database initialization "DatabaseInitializer", "init_database", + # Models + "Base", + "Agent", ] diff --git a/python/valuecell/server/db/connection.py b/python/valuecell/server/db/connection.py index 1611e6a67..f70c4efcf 100644 --- a/python/valuecell/server/db/connection.py +++ b/python/valuecell/server/db/connection.py @@ -1,6 +1,5 @@ """Database connection and session management for ValueCell Server.""" -import os from typing import Generator from sqlalchemy import create_engine, Engine from sqlalchemy.orm import sessionmaker, Session diff --git a/python/valuecell/server/db/init_db.py b/python/valuecell/server/db/init_db.py index 8329f727c..cdfad05ef 100644 --- a/python/valuecell/server/db/init_db.py +++ b/python/valuecell/server/db/init_db.py @@ -119,9 +119,9 @@ def create_tables(self) -> bool: return False def initialize_basic_data(self) -> bool: - """Initialize agent data from configuration files.""" + """Initialize default agent data.""" try: - logger.info("Initializing agent data...") + logger.info("Initializing default agent data...") # Get a database session session = self.db_manager.get_session() @@ -129,81 +129,141 @@ def initialize_basic_data(self) -> bool: try: # Import models here to avoid circular imports from .models.agent import Agent - import json - import os - from pathlib import Path - # Get the project root directory - project_root = Path(__file__).parent.parent.parent.parent - agent_configs_dir = project_root / "configs" / "agent_cards" - - if not agent_configs_dir.exists(): - logger.warning( - f"Agent configs directory not found: {agent_configs_dir}" + # Define default agents + default_agents = [ + { + "name": "AIHedgeFundAgent", + "display_name": "AI Hedge Fund Agent", + "description": "AI-powered hedge fund analysis and trading agent", + "version": "1.0.0", + "enabled": True, + "is_active": True, + "capabilities": { + "streaming": False, + "push_notifications": False, + }, + "metadata": { + "version": "1.0.0", + "author": "ValueCell Team", + "tags": ["hedge-fund", "ai", "trading"], + }, + }, + { + "name": "Sec13FundAgent", + "display_name": "SEC 13F Fund Agent", + "description": "SEC 13F fund analysis and tracking agent", + "version": "1.0.0", + "enabled": True, + "is_active": True, + "capabilities": { + "streaming": False, + "push_notifications": False, + }, + "metadata": { + "version": "1.0.0", + "author": "ValueCell Team", + "tags": ["sec", "13f", "fund-analysis"], + }, + }, + { + "name": "TradingAgentsAdapter", + "display_name": "Trading Agents Adapter", + "description": "TradingAgents - Multi-agent trading analysis system with market, sentiment, news and fundamentals analysis", + "version": "1.0.0", + "enabled": True, + "is_active": True, + "capabilities": { + "streaming": True, + "push_notifications": False, + }, + "metadata": { + "version": "1.0.0", + "author": "ValueCell Team", + "tags": [ + "trading", + "analysis", + "multi-agent", + "stocks", + "finance", + ], + "supported_tickers": [ + "AAPL", + "GOOGL", + "MSFT", + "NVDA", + "TSLA", + "AMZN", + "META", + "NFLX", + "SPY", + ], + "supported_analysts": [ + "market", + "social", + "news", + "fundamentals", + ], + "supported_llm_providers": [ + "openai", + "anthropic", + "google", + "ollama", + "openrouter", + ], + }, + }, + ] + + # Insert default agents + for agent_data in default_agents: + agent_name = agent_data["name"] + + # Check if agent already exists + existing_agent = ( + session.query(Agent).filter_by(name=agent_name).first() ) - return True - - # Load agent configurations from JSON files - for config_file in agent_configs_dir.glob("*.json"): - try: - with open(config_file, "r", encoding="utf-8") as f: - config_data = json.load(f) - - agent_name = config_data.get("name") - if not agent_name: - logger.warning( - f"Agent config missing 'name' field: {config_file}" - ) - continue - - # Check if agent already exists - existing_agent = ( - session.query(Agent).filter_by(name=agent_name).first() + + if not existing_agent: + # Create new agent + agent = Agent.from_config(agent_data) + session.add(agent) + logger.info(f"Added default agent: {agent_name}") + else: + # Update existing agent with default data + existing_agent.display_name = agent_data.get( + "display_name", existing_agent.display_name + ) + existing_agent.description = agent_data.get( + "description", existing_agent.description + ) + existing_agent.version = agent_data.get( + "version", existing_agent.version + ) + existing_agent.enabled = agent_data.get( + "enabled", existing_agent.enabled + ) + existing_agent.is_active = agent_data.get( + "is_active", existing_agent.is_active + ) + existing_agent.capabilities = agent_data.get( + "capabilities", existing_agent.capabilities + ) + existing_agent.agent_metadata = agent_data.get( + "metadata", existing_agent.agent_metadata + ) + existing_agent.config = agent_data.get( + "config", existing_agent.config ) - if not existing_agent: - # Create new agent from config - agent = Agent.from_config(config_data) - session.add(agent) - logger.info(f"Added agent: {agent_name}") - else: - # Update existing agent with new config data - existing_agent.display_name = config_data.get( - "display_name", existing_agent.display_name - ) - existing_agent.description = config_data.get( - "description", existing_agent.description - ) - existing_agent.url = config_data.get( - "url", existing_agent.url - ) - existing_agent.version = config_data.get( - "version", existing_agent.version - ) - existing_agent.enabled = config_data.get( - "enabled", existing_agent.enabled - ) - existing_agent.capabilities = config_data.get( - "capabilities", existing_agent.capabilities - ) - existing_agent.agent_metadata = config_data.get( - "metadata", existing_agent.agent_metadata - ) - existing_agent.config = config_data.get( - "config", existing_agent.config - ) - logger.info(f"Updated agent: {agent_name}") - - except Exception as e: - logger.error(f"Error loading agent config {config_file}: {e}") - continue + logger.info(f"Updated default agent: {agent_name}") session.commit() - logger.info("Agent data initialization completed") + logger.info("Default agent data initialization completed") return True except Exception as e: session.rollback() - logger.error(f"Error initializing agent data: {e}") + logger.error(f"Error initializing default agent data: {e}") return False finally: session.close() diff --git a/python/valuecell/server/db/models/agent.py b/python/valuecell/server/db/models/agent.py index cf9e37dac..b528e0eec 100644 --- a/python/valuecell/server/db/models/agent.py +++ b/python/valuecell/server/db/models/agent.py @@ -4,8 +4,7 @@ This module defines the database models for agents in the ValueCell system. """ -from datetime import datetime -from typing import Dict, List, Optional, Any +from typing import Dict, Any from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON from sqlalchemy.sql import func @@ -122,4 +121,4 @@ def from_config(cls, config_data: Dict[str, Any]) -> "Agent": capabilities=config_data.get("capabilities"), agent_metadata=config_data.get("metadata"), config=config_data.get("config"), - ) \ No newline at end of file + ) From 28b24d0d4453b1efaeecfd21e759b0f27a9185a1 Mon Sep 17 00:00:00 2001 From: zhonghao lu Date: Tue, 16 Sep 2025 14:55:18 +0800 Subject: [PATCH 5/6] add asset model --- python/valuecell/server/db/README.md | 72 ++++++++-- python/valuecell/server/db/__init__.py | 5 +- python/valuecell/server/db/init_db.py | 95 ++++++++++++- python/valuecell/server/db/models/__init__.py | 2 + python/valuecell/server/db/models/asset.py | 128 ++++++++++++++++++ 5 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 python/valuecell/server/db/models/asset.py diff --git a/python/valuecell/server/db/README.md b/python/valuecell/server/db/README.md index f058f207c..f75aff3a9 100644 --- a/python/valuecell/server/db/README.md +++ b/python/valuecell/server/db/README.md @@ -12,7 +12,8 @@ db/ ├── models/ # Database models │ ├── __init__.py │ ├── base.py # Base model class -│ └── agent.py # Agent model +│ ├── agent.py # Agent model +│ └── asset.py # Asset model └── README.md # This document ``` @@ -47,6 +48,23 @@ The Agent model stores information about all available AI agents in the ValueCel - `created_at`: Creation time - `updated_at`: Update time +### Asset Model (Asset) +The Asset model represents financial assets in the ValueCell system, including stocks, bonds, cryptocurrencies, and other investment instruments. + +**Key fields**: +- `id`: Primary key (auto-increment) +- `symbol`: Unique asset symbol/ticker (e.g., "AAPL", "BTC-USD") +- `name`: Full name of the asset +- `description`: Detailed description of the asset +- `asset_type`: Type of asset (stock, bond, crypto, commodity, etc.) +- `sector`: Industry sector (for stocks) +- `current_price`: Current market price +- `is_active`: Whether the asset is active +- `asset_metadata`: Additional metadata (fundamental data, tags, etc.) +- `config`: Asset-specific configuration parameters +- `created_at`: Timestamp when the asset was created +- `updated_at`: Timestamp when the asset was last updated + ## Database Initialization ### Usage @@ -76,16 +94,21 @@ The Agent model stores information about all available AI agents in the ValueCel 1. **Check database file**: Verify if SQLite database file exists 2. **Create database file**: Create new database file if it doesn't exist -3. **Create table structure**: Create agents table based on model definitions -4. **Initialize Agent data**: - - Insert default Agent records directly from code - - Create three default agents: AIHedgeFundAgent, Sec13FundAgent, and TradingAgentsAdapter - - Support updating existing Agent configuration information +3. **Create table structure**: Create agents and assets tables based on model definitions +4. **Initialize default data**: + - **Agent data**: Insert default Agent records directly from code + - Create three default agents: AIHedgeFundAgent, Sec13FundAgent, and TradingAgentsAdapter + - Support updating existing Agent configuration information + - **Asset data**: Insert default Asset records for common financial instruments + - Create default assets: AAPL, GOOGL, MSFT, SPY, BTC-USD + - Support updating existing Asset information 5. **Verify initialization**: Confirm database connection and table structure are correct -### Default Agent Records +### Default Records + +The initialization script automatically creates default records directly in the code: -The initialization script automatically creates default Agent records directly in the code: +#### Default Agents **Default agents created**: @@ -93,6 +116,16 @@ The initialization script automatically creates default Agent records directly i 2. **Sec13FundAgent**: SEC 13F fund analysis and tracking agent 3. **TradingAgentsAdapter**: Multi-agent trading analysis system with market, sentiment, news and fundamentals analysis +#### Default Assets + +**Default assets created**: + +1. **AAPL**: Apple Inc. (Technology stock) +2. **GOOGL**: Alphabet Inc. Class A (Technology stock) +3. **MSFT**: Microsoft Corporation (Technology stock) +4. **SPY**: SPDR S&P 500 ETF Trust (Index ETF) +5. **BTC-USD**: Bitcoin (Cryptocurrency) + **Agent data structure example**: ```python { @@ -116,17 +149,38 @@ The initialization script automatically creates default Agent records directly i } ``` +**Asset data structure example**: +```python +{ + "symbol": "AAPL", + "name": "Apple Inc.", + "asset_type": "stock", + "sector": "Technology", + "is_active": True, + "metadata": { + "market_cap": "large", + "dividend_yield": 0.5, + "beta": 1.2, + "tags": ["blue-chip", "dividend", "growth"] + } +} +``` + ## Usage in Code ### Getting Database Session ```python -from valuecell.server.db import get_db, Agent +from valuecell.server.db import get_db, Agent, Asset # Using dependency injection in FastAPI routes @app.get("/api/agents") def get_agents(db: Session = Depends(get_db)): return db.query(Agent).filter(Agent.enabled == True).all() + +@app.get("/api/assets") +def get_assets(db: Session = Depends(get_db)): + return db.query(Asset).filter(Asset.is_active == True).all() ``` ### Direct Database Manager Usage diff --git a/python/valuecell/server/db/__init__.py b/python/valuecell/server/db/__init__.py index a6fe1ae3b..f0c6e3e99 100644 --- a/python/valuecell/server/db/__init__.py +++ b/python/valuecell/server/db/__init__.py @@ -6,7 +6,7 @@ get_db, ) from .init_db import DatabaseInitializer, init_database -from .models import Base, Agent +from .models import Base, Agent, Asset __all__ = [ # Connection management @@ -19,4 +19,5 @@ # Models "Base", "Agent", -] + "Asset", +] \ No newline at end of file diff --git a/python/valuecell/server/db/init_db.py b/python/valuecell/server/db/init_db.py index cdfad05ef..8de22b6fe 100644 --- a/python/valuecell/server/db/init_db.py +++ b/python/valuecell/server/db/init_db.py @@ -129,6 +129,7 @@ def initialize_basic_data(self) -> bool: try: # Import models here to avoid circular imports from .models.agent import Agent + from .models.asset import Asset # Define default agents default_agents = [ @@ -257,8 +258,98 @@ def initialize_basic_data(self) -> bool: ) logger.info(f"Updated default agent: {agent_name}") + # Define default assets + default_assets = [ + { + "symbol": "AAPL", + "name": "Apple Inc.", + "asset_type": "stock", + "sector": "Technology", + "is_active": True, + "metadata": { + "market_cap": "large", + "dividend_yield": 0.5, + "beta": 1.2, + "tags": ["blue-chip", "dividend", "growth"] + } + }, + { + "symbol": "GOOGL", + "name": "Alphabet Inc. Class A", + "asset_type": "stock", + "sector": "Technology", + "is_active": True, + "metadata": { + "market_cap": "large", + "dividend_yield": 0.0, + "beta": 1.1, + "tags": ["growth", "tech-giant", "ai"] + } + }, + { + "symbol": "MSFT", + "name": "Microsoft Corporation", + "asset_type": "stock", + "sector": "Technology", + "is_active": True, + "metadata": { + "market_cap": "large", + "dividend_yield": 0.7, + "beta": 0.9, + "tags": ["blue-chip", "dividend", "cloud", "ai"] + } + }, + { + "symbol": "SPY", + "name": "SPDR S&P 500 ETF Trust", + "asset_type": "etf", + "sector": "Diversified", + "is_active": True, + "metadata": { + "expense_ratio": 0.0945, + "aum": "400B+", + "tags": ["index", "diversified", "low-cost"] + } + }, + { + "symbol": "BTC-USD", + "name": "Bitcoin", + "asset_type": "cryptocurrency", + "sector": "Cryptocurrency", + "is_active": True, + "metadata": { + "market_cap": "large", + "volatility": "high", + "tags": ["crypto", "store-of-value", "digital-gold"] + } + } + ] + + # Insert default assets + for asset_data in default_assets: + asset_symbol = asset_data["symbol"] + + # Check if asset already exists + existing_asset = ( + session.query(Asset).filter_by(symbol=asset_symbol).first() + ) + + if not existing_asset: + # Create new asset + asset = Asset.from_config(asset_data) + session.add(asset) + logger.info(f"Added default asset: {asset_symbol}") + else: + # Update existing asset with default data + existing_asset.name = asset_data.get("name", existing_asset.name) + existing_asset.asset_type = asset_data.get("asset_type", existing_asset.asset_type) + existing_asset.sector = asset_data.get("sector", existing_asset.sector) + existing_asset.is_active = asset_data.get("is_active", existing_asset.is_active) + existing_asset.asset_metadata = asset_data.get("metadata", existing_asset.asset_metadata) + logger.info(f"Updated default asset: {asset_symbol}") + session.commit() - logger.info("Default agent data initialization completed") + logger.info("Default agent and asset data initialization completed") return True except Exception as e: @@ -366,4 +457,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/python/valuecell/server/db/models/__init__.py b/python/valuecell/server/db/models/__init__.py index 0cd9fc9c3..57f157ba8 100644 --- a/python/valuecell/server/db/models/__init__.py +++ b/python/valuecell/server/db/models/__init__.py @@ -10,9 +10,11 @@ # Import all models to ensure they are registered with SQLAlchemy from .agent import Agent +from .asset import Asset # Export all models __all__ = [ "Base", "Agent", + "Asset", ] diff --git a/python/valuecell/server/db/models/asset.py b/python/valuecell/server/db/models/asset.py new file mode 100644 index 000000000..4c5253d32 --- /dev/null +++ b/python/valuecell/server/db/models/asset.py @@ -0,0 +1,128 @@ +""" +ValueCell Server - Asset Models + +This module defines the database models for assets in the ValueCell system. +""" + +from typing import Dict, Any +from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, JSON, Numeric +from sqlalchemy.sql import func + +from .base import Base + + +class Asset(Base): + """ + Asset model representing financial assets in the ValueCell system. + + This table stores information about financial assets including stocks, bonds, + cryptocurrencies, and other investment instruments. + """ + + __tablename__ = "assets" + + # Primary key + id = Column(Integer, primary_key=True, index=True) + + # Basic asset information + symbol = Column( + String(50), + unique=True, + nullable=False, + index=True, + comment="Asset symbol/ticker (e.g., AAPL, BTC, etc.)", + ) + name = Column( + String(200), nullable=False, comment="Full name of the asset" + ) + description = Column( + Text, + nullable=True, + comment="Detailed description of the asset", + ) + + # Asset classification + asset_type = Column( + String(50), + nullable=False, + index=True, + comment="Type of asset (stock, bond, crypto, commodity, etc.)", + ) + sector = Column( + String(100), + nullable=True, + comment="Industry sector (for stocks)", + ) + + # Market data + current_price = Column( + Numeric(precision=20, scale=8), + nullable=True, + comment="Current market price", + ) + + # Status and availability + is_active = Column( + Boolean, + default=True, + nullable=False, + comment="Whether the asset is active", + ) + + # Metadata and configuration + asset_metadata = Column( + JSON, + nullable=True, + comment="Additional metadata (ISIN, CUSIP, fundamental data, etc.)", + ) + config = Column( + JSON, + nullable=True, + comment="Asset-specific configuration parameters", + ) + + # Timestamps + created_at = Column( + DateTime(timezone=True), server_default=func.now(), nullable=False + ) + updated_at = Column( + DateTime(timezone=True), + server_default=func.now(), + onupdate=func.now(), + nullable=False, + ) + + def __repr__(self): + return f"" + + def to_dict(self) -> Dict[str, Any]: + """Convert asset to dictionary representation.""" + return { + "id": self.id, + "symbol": self.symbol, + "name": self.name, + "description": self.description, + "asset_type": self.asset_type, + "sector": self.sector, + "current_price": float(self.current_price) if self.current_price else None, + "is_active": self.is_active, + "metadata": self.asset_metadata, + "config": self.config, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + @classmethod + def from_config(cls, config_data: Dict[str, Any]) -> "Asset": + """Create an Asset instance from configuration data.""" + return cls( + symbol=config_data.get("symbol"), + name=config_data.get("name"), + description=config_data.get("description"), + asset_type=config_data.get("asset_type", "stock"), + sector=config_data.get("sector"), + current_price=config_data.get("current_price"), + is_active=config_data.get("is_active", True), + asset_metadata=config_data.get("metadata"), + config=config_data.get("config"), + ) \ No newline at end of file From 7654caeac9ae5d1b6e030cedafdfe2ccab226348 Mon Sep 17 00:00:00 2001 From: zhonghao lu Date: Tue, 16 Sep 2025 15:08:02 +0800 Subject: [PATCH 6/6] format --- python/valuecell/server/db/__init__.py | 2 +- python/valuecell/server/db/init_db.py | 44 +++++++++++++--------- python/valuecell/server/db/models/asset.py | 6 +-- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/python/valuecell/server/db/__init__.py b/python/valuecell/server/db/__init__.py index f0c6e3e99..a18580123 100644 --- a/python/valuecell/server/db/__init__.py +++ b/python/valuecell/server/db/__init__.py @@ -20,4 +20,4 @@ "Base", "Agent", "Asset", -] \ No newline at end of file +] diff --git a/python/valuecell/server/db/init_db.py b/python/valuecell/server/db/init_db.py index 8de22b6fe..9861ac077 100644 --- a/python/valuecell/server/db/init_db.py +++ b/python/valuecell/server/db/init_db.py @@ -270,8 +270,8 @@ def initialize_basic_data(self) -> bool: "market_cap": "large", "dividend_yield": 0.5, "beta": 1.2, - "tags": ["blue-chip", "dividend", "growth"] - } + "tags": ["blue-chip", "dividend", "growth"], + }, }, { "symbol": "GOOGL", @@ -283,8 +283,8 @@ def initialize_basic_data(self) -> bool: "market_cap": "large", "dividend_yield": 0.0, "beta": 1.1, - "tags": ["growth", "tech-giant", "ai"] - } + "tags": ["growth", "tech-giant", "ai"], + }, }, { "symbol": "MSFT", @@ -296,8 +296,8 @@ def initialize_basic_data(self) -> bool: "market_cap": "large", "dividend_yield": 0.7, "beta": 0.9, - "tags": ["blue-chip", "dividend", "cloud", "ai"] - } + "tags": ["blue-chip", "dividend", "cloud", "ai"], + }, }, { "symbol": "SPY", @@ -308,8 +308,8 @@ def initialize_basic_data(self) -> bool: "metadata": { "expense_ratio": 0.0945, "aum": "400B+", - "tags": ["index", "diversified", "low-cost"] - } + "tags": ["index", "diversified", "low-cost"], + }, }, { "symbol": "BTC-USD", @@ -320,9 +320,9 @@ def initialize_basic_data(self) -> bool: "metadata": { "market_cap": "large", "volatility": "high", - "tags": ["crypto", "store-of-value", "digital-gold"] - } - } + "tags": ["crypto", "store-of-value", "digital-gold"], + }, + }, ] # Insert default assets @@ -341,11 +341,21 @@ def initialize_basic_data(self) -> bool: logger.info(f"Added default asset: {asset_symbol}") else: # Update existing asset with default data - existing_asset.name = asset_data.get("name", existing_asset.name) - existing_asset.asset_type = asset_data.get("asset_type", existing_asset.asset_type) - existing_asset.sector = asset_data.get("sector", existing_asset.sector) - existing_asset.is_active = asset_data.get("is_active", existing_asset.is_active) - existing_asset.asset_metadata = asset_data.get("metadata", existing_asset.asset_metadata) + existing_asset.name = asset_data.get( + "name", existing_asset.name + ) + existing_asset.asset_type = asset_data.get( + "asset_type", existing_asset.asset_type + ) + existing_asset.sector = asset_data.get( + "sector", existing_asset.sector + ) + existing_asset.is_active = asset_data.get( + "is_active", existing_asset.is_active + ) + existing_asset.asset_metadata = asset_data.get( + "metadata", existing_asset.asset_metadata + ) logger.info(f"Updated default asset: {asset_symbol}") session.commit() @@ -457,4 +467,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/python/valuecell/server/db/models/asset.py b/python/valuecell/server/db/models/asset.py index 4c5253d32..2b25926d6 100644 --- a/python/valuecell/server/db/models/asset.py +++ b/python/valuecell/server/db/models/asset.py @@ -32,9 +32,7 @@ class Asset(Base): index=True, comment="Asset symbol/ticker (e.g., AAPL, BTC, etc.)", ) - name = Column( - String(200), nullable=False, comment="Full name of the asset" - ) + name = Column(String(200), nullable=False, comment="Full name of the asset") description = Column( Text, nullable=True, @@ -125,4 +123,4 @@ def from_config(cls, config_data: Dict[str, Any]) -> "Asset": is_active=config_data.get("is_active", True), asset_metadata=config_data.get("metadata"), config=config_data.get("config"), - ) \ No newline at end of file + )