Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Temporary Items
/benchmark_data/*
.claude
.openviking
*.code-workspace

# AI Coding
CLAUDE.md
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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+

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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+

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- **网络连接**:需要稳定的网络连接(用于下载依赖和访问模型服务)
Expand Down
2 changes: 1 addition & 1 deletion docs/design/multi-tenant-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 → 身份 + 角色
Expand Down
10 changes: 8 additions & 2 deletions openviking/server/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {}

Expand Down
3 changes: 2 additions & 1 deletion openviking/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
14 changes: 8 additions & 6 deletions openviking/service/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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]:
Expand Down
13 changes: 3 additions & 10 deletions openviking/storage/transaction/transaction_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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,
)
Expand Down
44 changes: 35 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,18 @@ def build_agfs(self):
else ["make", "build"]
)

subprocess.run(
result = subprocess.run(
build_args,
cwd=str(agfs_server_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')}")

agfs_built_binary = agfs_server_dir / "build" / binary_name
if agfs_built_binary.exists():
Expand All @@ -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:
Expand All @@ -146,14 +151,18 @@ 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,
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')}")

agfs_built_lib = agfs_server_dir / "build" / lib_name
if agfs_built_lib.exists():
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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}")
Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_admin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions tests/server/test_api_key_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion third_party/agfs/agfs-server/go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading