diff --git a/.gitignore b/.gitignore index 4b018827b..07a04e77f 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ Temporary Items /benchmark_data/* .claude .openviking +*.code-workspace # AI Coding CLAUDE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c3ef541d..571037edf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Thank you for your interest in OpenViking! We welcome contributions of all kinds ### Prerequisites - **Python**: 3.10+ -- **Go**: 1.19.1+ (Required for building AGFS components from source) +- **Go**: 1.22+ (Required for building AGFS components from source) - **C++ Compiler**: GCC 9+ or Clang 11+ (Required for building core extensions, must support C++17) - **CMake**: 3.12+ diff --git a/CONTRIBUTING_CN.md b/CONTRIBUTING_CN.md index ff5854671..2460dbb64 100644 --- a/CONTRIBUTING_CN.md +++ b/CONTRIBUTING_CN.md @@ -14,7 +14,7 @@ ### 前置要求 - **Python**: 3.10+ -- **Go**: 1.19+ (从源码构建 AGFS 组件需要) +- **Go**: 1.22+ (从源码构建 AGFS 组件需要) - **C++ 编译器**: GCC 9+ 或 Clang 11+ (构建核心扩展需要,必须支持 C++17) - **CMake**: 3.12+ diff --git a/README.md b/README.md index 04e975cc2..0fba2724e 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ With OpenViking, developers can build an Agent's brain just like managing local Before starting with OpenViking, please ensure your environment meets the following requirements: - **Python Version**: 3.10 or higher -- **Go Version**: 1.19 or higher (Required for building AGFS components) +- **Go Version**: 1.22 or higher (Required for building AGFS components) - **C++ Compiler**: GCC 9+ or Clang 11+ (Required for building core extensions) - **Operating System**: Linux, macOS, Windows - **Network Connection**: A stable network connection is required (for downloading dependencies and accessing model services) diff --git a/README_CN.md b/README_CN.md index 8ac5f417a..26eaab2e3 100644 --- a/README_CN.md +++ b/README_CN.md @@ -59,7 +59,7 @@ 在开始使用 OpenViking 之前,请确保您的环境满足以下要求: - **Python 版本**:3.10 或更高版本 -- **Go 版本**:1.19 或更高(从源码构建 AGFS 组件需要) +- **Go 版本**:1.22 或更高(从源码构建 AGFS 组件需要) - **C++ 编译器**:GCC 9+ 或 Clang 11+(构建核心扩展需要,必须支持 C++17) - **操作系统**:Linux、macOS、Windows - **网络连接**:需要稳定的网络连接(用于下载依赖和访问模型服务) diff --git a/docs/design/multi-tenant-design.md b/docs/design/multi-tenant-design.md index 0bfd966bd..6c5ad43de 100644 --- a/docs/design/multi-tenant-design.md +++ b/docs/design/multi-tenant-design.md @@ -126,7 +126,7 @@ Request class APIKeyManager: """API Key 生命周期管理与解析""" - def __init__(self, root_key: Optional[str], agfs_url: str) + def __init__(self, root_key: str, agfs_client: AGFSClient) async def load() # 加载所有 account 的 users.json 到内存 async def save_account(account_id: str) # 持久化指定 account 的 users.json def resolve(api_key: str) -> ResolvedIdentity # Key → 身份 + 角色 diff --git a/openviking/server/api_keys.py b/openviking/server/api_keys.py index 28f8e4789..4064baa21 100644 --- a/openviking/server/api_keys.py +++ b/openviking/server/api_keys.py @@ -52,9 +52,15 @@ class APIKeyManager: In-memory index for O(1) key lookup at runtime. """ - def __init__(self, root_key: str, agfs_url: str): + def __init__(self, root_key: str, agfs_client: AGFSClient): + """Initialize APIKeyManager. + + Args: + root_key: Global root API key for administrative access. + agfs_client: AGFS client for persistent storage of user keys. + """ self._root_key = root_key - self._agfs = AGFSClient(agfs_url) + self._agfs = agfs_client self._accounts: Dict[str, AccountInfo] = {} self._user_keys: Dict[str, UserKeyEntry] = {} diff --git a/openviking/server/app.py b/openviking/server/app.py index a99d248bb..d8583b119 100644 --- a/openviking/server/app.py +++ b/openviking/server/app.py @@ -68,7 +68,7 @@ async def lifespan(app: FastAPI): if config.root_api_key: api_key_manager = APIKeyManager( root_key=config.root_api_key, - agfs_url=service._agfs_url, + agfs_client=service._agfs, ) await api_key_manager.load() app.state.api_key_manager = api_key_manager @@ -151,6 +151,7 @@ async def general_error_handler(request: Request, exc: Exception): # Configure Bot API if --with-bot is enabled if config.with_bot: import openviking.server.routers.bot as bot_module + bot_module.set_bot_api_url(config.bot_api_url) logger.info(f"Bot API proxy enabled, forwarding to {config.bot_api_url}") else: diff --git a/openviking/service/core.py b/openviking/service/core.py index 7b9a35c02..5764fed1e 100644 --- a/openviking/service/core.py +++ b/openviking/service/core.py @@ -67,7 +67,6 @@ def __init__( # Infrastructure self._agfs_manager: Optional[AGFSManager] = None - self._agfs_url: Optional[str] = None self._agfs_client: Optional[Any] = None self._queue_manager: Optional[QueueManager] = None self._vikingdb_manager: Optional[VikingDBManager] = None @@ -115,10 +114,8 @@ def _init_storage( if mode == "http-client": self._agfs_manager = AGFSManager(config=config.agfs) self._agfs_manager.start() - self._agfs_url = self._agfs_manager.url - config.agfs.url = self._agfs_url - else: - self._agfs_url = config.agfs.url + agfs_url = self._agfs_manager.url + config.agfs.url = agfs_url # Create AGFS client using utility self._agfs_client = create_agfs_client(config.agfs) @@ -144,7 +141,12 @@ def _init_storage( self._queue_manager.setup_standard_queues(self._vikingdb_manager) # Initialize TransactionManager - self._transaction_manager = init_transaction_manager(agfs_config=config.agfs) + self._transaction_manager = init_transaction_manager(agfs=self._agfs_client) + + @property + def _agfs(self) -> Any: + """Internal access to AGFS client for APIKeyManager.""" + return self._agfs_client @property def viking_fs(self) -> Optional[VikingFS]: diff --git a/openviking/storage/transaction/transaction_manager.py b/openviking/storage/transaction/transaction_manager.py index 32a24c449..80f261619 100644 --- a/openviking/storage/transaction/transaction_manager.py +++ b/openviking/storage/transaction/transaction_manager.py @@ -334,14 +334,14 @@ def get_transaction_count(self) -> int: def init_transaction_manager( - agfs_config: Any, + agfs: AGFSClient, tx_timeout: int = 3600, max_parallel_locks: int = 8, ) -> TransactionManager: """Initialize transaction manager singleton. Args: - agfs_config: AGFS configuration (url, timeout, etc.) + agfs: AGFS client instance tx_timeout: Transaction timeout in seconds (default: 3600) max_parallel_locks: Maximum number of parallel lock operations (default: 8) @@ -355,16 +355,9 @@ def init_transaction_manager( logger.debug("TransactionManager already initialized") return _transaction_manager - # Get AGFS URL from config - agfs_url = getattr(agfs_config, "url", "http://localhost:8080") - agfs_timeout = getattr(agfs_config, "timeout", 10) - - # Create AGFS client - agfs_client = AGFSClient(api_base_url=agfs_url, timeout=agfs_timeout) - # Create transaction manager _transaction_manager = TransactionManager( - agfs_client=agfs_client, + agfs_client=agfs, timeout=tx_timeout, max_parallel_locks=max_parallel_locks, ) diff --git a/setup.py b/setup.py index 98dd96fc3..17e1e5846 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ def build_agfs(self): else ["make", "build"] ) - subprocess.run( + result = subprocess.run( build_args, cwd=str(agfs_server_dir), env=env, @@ -115,6 +115,10 @@ def build_agfs(self): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + if result.stdout: + print(f"Build stdout: {result.stdout.decode('utf-8', errors='replace')}") + if result.stderr: + print(f"Build stderr: {result.stderr.decode('utf-8', errors='replace')}") agfs_built_binary = agfs_server_dir / "build" / binary_name if agfs_built_binary.exists(): @@ -135,7 +139,8 @@ def build_agfs(self): error_msg += ( f"\nBuild stderr:\n{e.stderr.decode('utf-8', errors='replace')}" ) - print(f"[Warning] {error_msg}") + print(f"[Error] {error_msg}") + raise RuntimeError(error_msg) # Build binding library try: @@ -146,7 +151,7 @@ def build_agfs(self): lib_build_args = ["make", "build-lib"] - subprocess.run( + result = subprocess.run( lib_build_args, cwd=str(agfs_server_dir), env=env, @@ -154,6 +159,10 @@ def build_agfs(self): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + if result.stdout: + print(f"Build stdout: {result.stdout.decode('utf-8', errors='replace')}") + if result.stderr: + print(f"Build stderr: {result.stderr.decode('utf-8', errors='replace')}") agfs_built_lib = agfs_server_dir / "build" / lib_name if agfs_built_lib.exists(): @@ -162,16 +171,20 @@ def build_agfs(self): else: print(f"[Warning] Binding library not found at {agfs_built_lib}") except Exception as e: - print(f"[Warning] Failed to build AGFS binding library: {e}") + error_msg = f"Failed to build AGFS binding library: {e}" if isinstance(e, subprocess.CalledProcessError): if e.stdout: - print(f"Build stdout: {e.stdout.decode('utf-8', errors='replace')}") + error_msg += f"\nBuild stdout: {e.stdout.decode('utf-8', errors='replace')}" if e.stderr: - print(f"Build stderr: {e.stderr.decode('utf-8', errors='replace')}") + error_msg += f"\nBuild stderr: {e.stderr.decode('utf-8', errors='replace')}" + print(f"[Error] {error_msg}") + raise RuntimeError(error_msg) else: if agfs_target_binary.exists(): - print(f"[Info] Go compiler not found, but AGFS binary exists at {agfs_target_binary}. Skipping build.") + print( + f"[Info] Go compiler not found, but AGFS binary exists at {agfs_target_binary}. Skipping build." + ) elif not agfs_server_dir.exists(): print(f"[Warning] AGFS source directory not found at {agfs_server_dir}") else: @@ -219,12 +232,18 @@ def build_ov(self): print(f"Cross-compiling with CARGO_BUILD_TARGET={target}") build_args.extend(["--target", target]) - subprocess.run( + result = subprocess.run( build_args, cwd=str(ov_cli_dir), env=env, check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ) + if result.stdout: + print(f"Build stdout: {result.stdout.decode('utf-8', errors='replace')}") + if result.stderr: + print(f"Build stderr: {result.stderr.decode('utf-8', errors='replace')}") # Find built binary if target: @@ -238,7 +257,14 @@ def build_ov(self): else: print(f"[Warning] Built ov binary not found at {built_bin}") except Exception as e: - print(f"[Warning] Failed to build ov CLI from source: {e}") + error_msg = f"Failed to build ov CLI from source: {e}" + if isinstance(e, subprocess.CalledProcessError): + if e.stdout: + error_msg += f"\nBuild stdout: {e.stdout.decode('utf-8', errors='replace')}" + if e.stderr: + error_msg += f"\nBuild stderr: {e.stderr.decode('utf-8', errors='replace')}" + print(f"[Error] {error_msg}") + raise RuntimeError(error_msg) else: if not ov_cli_dir.exists(): print(f"[Warning] ov CLI source directory not found at {ov_cli_dir}") diff --git a/tests/server/test_admin_api.py b/tests/server/test_admin_api.py index a819be19d..80e8d5e3b 100644 --- a/tests/server/test_admin_api.py +++ b/tests/server/test_admin_api.py @@ -39,7 +39,7 @@ async def admin_app(admin_service): app = create_app(config=config, service=admin_service) set_service(admin_service) - manager = APIKeyManager(root_key=ROOT_KEY, agfs_url=admin_service._agfs_url) + manager = APIKeyManager(root_key=ROOT_KEY, agfs_client=admin_service._agfs) await manager.load() app.state.api_key_manager = manager diff --git a/tests/server/test_api_key_manager.py b/tests/server/test_api_key_manager.py index 9b5e2e235..364ebb2a1 100644 --- a/tests/server/test_api_key_manager.py +++ b/tests/server/test_api_key_manager.py @@ -37,7 +37,7 @@ async def manager_service(temp_dir): @pytest_asyncio.fixture(scope="function") async def manager(manager_service): """Fresh APIKeyManager instance, loaded.""" - mgr = APIKeyManager(root_key=ROOT_KEY, agfs_url=manager_service._agfs_url) + mgr = APIKeyManager(root_key=ROOT_KEY, agfs_client=manager_service._agfs) await mgr.load() return mgr @@ -207,14 +207,14 @@ async def test_get_users(manager: APIKeyManager): async def test_persistence_across_reload(manager_service): """Keys should survive manager reload from AGFS.""" - mgr1 = APIKeyManager(root_key=ROOT_KEY, agfs_url=manager_service._agfs_url) + mgr1 = APIKeyManager(root_key=ROOT_KEY, agfs_client=manager_service._agfs) await mgr1.load() acct = _uid() key = await mgr1.create_account(acct, "alice") # Create new manager instance and reload - mgr2 = APIKeyManager(root_key=ROOT_KEY, agfs_url=manager_service._agfs_url) + mgr2 = APIKeyManager(root_key=ROOT_KEY, agfs_client=manager_service._agfs) await mgr2.load() identity = mgr2.resolve(key) diff --git a/tests/server/test_auth.py b/tests/server/test_auth.py index 849bc2a36..70dbc6e8f 100644 --- a/tests/server/test_auth.py +++ b/tests/server/test_auth.py @@ -44,7 +44,7 @@ async def auth_app(auth_service): set_service(auth_service) # Manually initialize APIKeyManager (lifespan not triggered in ASGI tests) - manager = APIKeyManager(root_key=ROOT_KEY, agfs_url=auth_service._agfs_url) + manager = APIKeyManager(root_key=ROOT_KEY, agfs_client=auth_service._agfs) await manager.load() app.state.api_key_manager = manager diff --git a/third_party/agfs/agfs-server/go.mod b/third_party/agfs/agfs-server/go.mod index 623b91618..77d2f121f 100644 --- a/third_party/agfs/agfs-server/go.mod +++ b/third_party/agfs/agfs-server/go.mod @@ -1,6 +1,6 @@ module github.com/c4pt0r/agfs/agfs-server -go 1.19 +go 1.22.0 require ( github.com/aws/aws-sdk-go-v2 v1.39.2