feat: add Chinese UI, Responses models, and mirrored Windows setup
Localize the web console and persist custom model and capability settings. Use OpenAI Responses exclusively and move runtime limits into environment configuration. Bootstrap uv and the virtual environment on Windows, support optional China mirrors, and package source distributions without environments, build output, or credentials. Update regression tests and repository quality checks for the new behavior.
This commit is contained in:
1 parent
371aa6df56
commit
2ddec53954
183 files changed
+3427
-11857
No files matched your search
@@ -57,14 +57,10 @@ from artemis.config.constants import (
|
||||
ENV_ADB_HOST,
|
||||
ENV_ADB_PORT,
|
||||
ENV_ADB_SERVER_SOCKET,
|
||||
ENV_ANTHROPIC_API_KEY,
|
||||
ENV_ANTIGRAVITY_APP_DIR,
|
||||
ENV_ANTIGRAVITY_LS_ADDRESS,
|
||||
ENV_DATA_ENGINE_DB_PATH,
|
||||
ENV_EVENTS_OUTPUT_PATH,
|
||||
ENV_GCP_API_KEY,
|
||||
ENV_GEMINI_API_KEY,
|
||||
ENV_GOOGLE_API_KEY,
|
||||
ENV_KEEP_VIDEOS,
|
||||
ENV_ARTEMIS_APP_DIR,
|
||||
ENV_ARTEMIS_CLOUD_GATEWAY_URL,
|
||||
@@ -83,11 +79,9 @@ from artemis.config.constants import (
|
||||
ENV_ARTEMIS_TRACES_DIR,
|
||||
ENV_ARTEMIS_USE_FILE_API,
|
||||
ENV_ARTEMIS_USE_USER_DIR,
|
||||
ENV_OPEN_ROUTER_API_KEY,
|
||||
ENV_OPENAI_API_KEY,
|
||||
ENV_OPENAI_BASE_URL,
|
||||
ENV_RESULTS_OUTPUT_PATH,
|
||||
ENV_XAI_API_KEY,
|
||||
IPC_PORT_FILENAME,
|
||||
LLM_CONFIG_FILENAME,
|
||||
LLM_CONFIG_OVERRIDE_FILENAME,
|
||||
@@ -110,7 +104,6 @@ from artemis.config.llm import (
|
||||
initialize_llm_config,
|
||||
load_llm_config_override,
|
||||
parse_llm_config,
|
||||
validate_vertex_ai_credentials,
|
||||
)
|
||||
from artemis.config.output import (
|
||||
OutputConfig,
|
||||
@@ -206,7 +199,6 @@ __all__ = [
|
||||
"get_default_llm_config",
|
||||
"deep_merge_llm_config",
|
||||
"load_llm_config_override",
|
||||
"validate_vertex_ai_credentials",
|
||||
# Agent & Tool Config
|
||||
"AgentGlobalConfig",
|
||||
"CheckerConfig",
|
||||
@@ -290,14 +282,8 @@ __all__ = [
|
||||
"ENV_ADB_HOST",
|
||||
"ENV_ADB_PORT",
|
||||
"ENV_ADB_SERVER_SOCKET",
|
||||
"ENV_GOOGLE_API_KEY",
|
||||
"ENV_GEMINI_API_KEY",
|
||||
"ENV_GCP_API_KEY",
|
||||
"ENV_OPENAI_API_KEY",
|
||||
"ENV_OPENAI_BASE_URL",
|
||||
"ENV_ANTHROPIC_API_KEY",
|
||||
"ENV_OPEN_ROUTER_API_KEY",
|
||||
"ENV_XAI_API_KEY",
|
||||
"ENV_ARTEMIS_EXPLORER_VERSION",
|
||||
"ENV_ARTEMIS_DEFAULT_PROFILE",
|
||||
"ENV_ARTEMIS_DEFAULT_MODEL",
|
||||
|
||||
+14
-38
@@ -26,7 +26,8 @@ from artemis.config.constants import (
|
||||
ExplorerVersion,
|
||||
)
|
||||
from artemis.config.paths import ROOT_DIR, get_config_path
|
||||
from artemis.llm.google import VideoProcessing
|
||||
from artemis.config.settings import settings
|
||||
from artemis.config.core.interpolator import interpolate_config_value
|
||||
from artemis.utils.file import load_jsonc
|
||||
from artemis.utils.logger import get_logger
|
||||
|
||||
@@ -48,7 +49,7 @@ class VideoAnalyzerConfig(BaseModel):
|
||||
description="Whether to enable video action ledger state tracking.",
|
||||
)
|
||||
chunk_size_seconds: float = Field(
|
||||
default=60.0,
|
||||
default_factory=lambda: settings.ARTEMIS_VIDEO_CHUNK_SECONDS,
|
||||
ge=5.0,
|
||||
le=300.0,
|
||||
description="Maximum duration of an initial video-analysis chunk.",
|
||||
@@ -95,44 +96,20 @@ class VideoAnalyzerConfig(BaseModel):
|
||||
le=120,
|
||||
description="Maximum additional action-proximal keyframes per chunk.",
|
||||
)
|
||||
native_max_retries: int = Field(
|
||||
default=1,
|
||||
max_retries: int = Field(
|
||||
default_factory=lambda: settings.ARTEMIS_VIDEO_MAX_RETRIES,
|
||||
ge=0,
|
||||
le=5,
|
||||
description=(
|
||||
"Retries against the native video provider before switching to the "
|
||||
"Retries against the video model before switching to the "
|
||||
"configured universal fallback. A value of 1 means two total attempts."
|
||||
),
|
||||
)
|
||||
model_call_timeout_seconds: float = Field(
|
||||
default=120.0,
|
||||
default_factory=lambda: settings.ARTEMIS_VIDEO_TIMEOUT_SECONDS,
|
||||
ge=15.0,
|
||||
le=600.0,
|
||||
description="Hard timeout for one native or universal video-model response.",
|
||||
)
|
||||
processing: VideoProcessing = Field(
|
||||
default="auto",
|
||||
description=(
|
||||
"How Gemini sub-agents watch a clip. 'agentic' lets the model search,"
|
||||
" scan and re-watch the clip on demand (Gemini Interactions API);"
|
||||
" 'static' samples it at a fixed frame rate; 'auto' picks agentic on"
|
||||
" Gemini 3.6+ Flash models and static everywhere else."
|
||||
),
|
||||
)
|
||||
agentic_chunk_size_seconds: float = Field(
|
||||
default=600.0,
|
||||
ge=30.0,
|
||||
le=1800.0,
|
||||
description=(
|
||||
"Maximum clip length handed to one agentic sub-agent. Static mode keeps"
|
||||
" using chunk_size_seconds."
|
||||
),
|
||||
)
|
||||
agentic_call_timeout_seconds: float = Field(
|
||||
default=300.0,
|
||||
ge=30.0,
|
||||
le=1200.0,
|
||||
description="Hard timeout for one agentic sub-agent response.",
|
||||
description="Hard timeout for one Responses video-model response.",
|
||||
)
|
||||
model_config = {"extra": "allow"}
|
||||
|
||||
@@ -214,12 +191,12 @@ class CheckerConfig(BaseModel):
|
||||
description="Maximum concurrently running checkpoint attempts.",
|
||||
)
|
||||
checkpoint_timeout: float = Field(
|
||||
default=180.0,
|
||||
default_factory=lambda: settings.ARTEMIS_CHECKPOINT_TIMEOUT_SECONDS,
|
||||
gt=0,
|
||||
description="Per-attempt timeout (seconds) for one checkpoint check.",
|
||||
)
|
||||
settlement_timeout: float = Field(
|
||||
default=120.0,
|
||||
default_factory=lambda: settings.ARTEMIS_SETTLEMENT_TIMEOUT_SECONDS,
|
||||
gt=0,
|
||||
description="Aggregate timeout (seconds) for the exit settlement barrier.",
|
||||
)
|
||||
@@ -343,8 +320,7 @@ class ExplorerConfig(BaseModel):
|
||||
caching: bool | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Gemini explicit context caching for multi-turn Explorer tiers. ``None``"
|
||||
" (default) uses the tier's own default: off for pro, on for ultra."
|
||||
"Legacy compatibility field; Responses prompt caching is managed by the endpoint."
|
||||
),
|
||||
)
|
||||
model_config = {"extra": "allow"}
|
||||
@@ -391,7 +367,7 @@ class StepSummarizerConfig(BaseModel):
|
||||
description="Whether to asynchronously summarize historical steps to replace pruned images.",
|
||||
)
|
||||
model: str = Field(
|
||||
default="gemini-2.5-flash-lite",
|
||||
default_factory=lambda: settings.OPENAI_MODEL,
|
||||
description="Lightweight model used for background step state summarization.",
|
||||
)
|
||||
prune_history_xml: bool = Field(
|
||||
@@ -688,7 +664,7 @@ class MemoryChunkingConfig(BaseModel):
|
||||
),
|
||||
)
|
||||
model: str = Field(
|
||||
default="gemini-3.8-flash",
|
||||
default_factory=lambda: settings.OPENAI_MODEL,
|
||||
description="Model used for the chunk-level StepCapsuleLens (bands ①+②).",
|
||||
)
|
||||
max_chunks: int = Field(
|
||||
@@ -1089,7 +1065,7 @@ def load_agent_config(
|
||||
|
||||
try:
|
||||
with open(resolved_path, encoding="utf-8") as f:
|
||||
data = load_jsonc(f)
|
||||
data = interpolate_config_value(load_jsonc(f))
|
||||
if isinstance(data, dict) and "agent" in data:
|
||||
return AgentGlobalConfig.model_validate(data["agent"])
|
||||
return AgentGlobalConfig.model_validate(data)
|
||||
|
||||
@@ -53,17 +53,11 @@ ENV_ARTEMIS_HIERARCHY_BACKEND = "ARTEMIS_HIERARCHY_BACKEND"
|
||||
ENV_ARTEMIS_HELPER_AUTO_INSTALL = "ARTEMIS_HELPER_AUTO_INSTALL"
|
||||
|
||||
# LLM & Vision OCR API Keys
|
||||
ENV_GOOGLE_API_KEY = "GOOGLE_API_KEY"
|
||||
ENV_GEMINI_API_KEY = "GEMINI_API_KEY"
|
||||
ENV_GCP_API_KEY = "GCP_API_KEY"
|
||||
ENV_OCR_API_KEY = "OCR_API_KEY"
|
||||
ENV_VISION_API_KEY = "VISION_API_KEY"
|
||||
ENV_API_KEY = "API_KEY"
|
||||
ENV_OPENAI_API_KEY = "OPENAI_API_KEY"
|
||||
ENV_OPENAI_BASE_URL = "OPENAI_BASE_URL"
|
||||
ENV_ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY"
|
||||
ENV_OPEN_ROUTER_API_KEY = "OPEN_ROUTER_API_KEY"
|
||||
ENV_XAI_API_KEY = "XAI_API_KEY"
|
||||
|
||||
# Explorer & Agent Defaults
|
||||
ENV_ARTEMIS_EXPLORER_VERSION = "ARTEMIS_EXPLORER_VERSION"
|
||||
@@ -110,7 +104,7 @@ DEFAULT_ACTION_TIMEOUT = 30.0
|
||||
DEFAULT_STREAM_PING_INTERVAL = 15.0
|
||||
|
||||
DEFAULT_PROFILE = "pro"
|
||||
DEFAULT_MODEL = "gemini-2.5-flash"
|
||||
DEFAULT_MODEL = "gpt-5-mini"
|
||||
DEFAULT_EXPLORER_VERSION: Literal["flash", "pro", "ultra"] = "flash"
|
||||
|
||||
|
||||
@@ -118,9 +112,7 @@ DEFAULT_EXPLORER_VERSION: Literal["flash", "pro", "ultra"] = "flash"
|
||||
# Literals and Type Aliases
|
||||
# ==============================================================================
|
||||
|
||||
LLMProvider = Literal[
|
||||
"openai", "google", "openrouter", "xai", "vertexai", "anthropic", "ollama", "vllm", "custom"
|
||||
]
|
||||
LLMProvider = Literal["openai"]
|
||||
ExplorerVersion = Literal["flash", "pro", "ultra"]
|
||||
|
||||
LLMUtilsNode = Literal[
|
||||
|
||||
@@ -74,7 +74,7 @@ class CascadingConfigEngine:
|
||||
# 3. Workspace .env
|
||||
ws_env = self.workspace_root / ".env"
|
||||
if ws_env.exists():
|
||||
load_dotenv(dotenv_path=ws_env, override=True, encoding="utf-8")
|
||||
load_dotenv(dotenv_path=ws_env, override=False, encoding="utf-8")
|
||||
|
||||
def load_raw_config(
|
||||
self,
|
||||
|
||||
+19
-46
@@ -15,12 +15,11 @@
|
||||
"""LLM provider, model hierarchy, fallback chaining, and configuration loaders."""
|
||||
|
||||
import os
|
||||
from artemis.config.core.interpolator import interpolate_config_value
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import google.auth
|
||||
from google.auth.exceptions import DefaultCredentialsError
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic import Field, BaseModel, ValidationError
|
||||
|
||||
from artemis.config.constants import (
|
||||
LLM_CONFIG_FILENAME,
|
||||
@@ -36,18 +35,6 @@ from artemis.utils.logger import get_logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def validate_vertex_ai_credentials() -> None:
|
||||
"""Validate Google Application Default Credentials for VertexAI provider."""
|
||||
try:
|
||||
_, project = google.auth.default()
|
||||
if not project:
|
||||
raise Exception("VertexAI requires a Google Cloud project to be set.")
|
||||
except DefaultCredentialsError as e:
|
||||
raise Exception(
|
||||
f"VertexAI requires valid Google Application Default Credentials (ADC): {e}"
|
||||
)
|
||||
|
||||
|
||||
class _CyFunctionDetectorMeta(type):
|
||||
def __instancecheck__(self, instance):
|
||||
name = type(instance).__name__
|
||||
@@ -72,13 +59,14 @@ class LLM(BaseModel):
|
||||
"""Base model representing an LLM model provider and runtime parameters."""
|
||||
|
||||
model_config = {"ignored_types": (CyFunctionDetector,)}
|
||||
provider: LLMProvider
|
||||
model: str
|
||||
provider: LLMProvider = "openai"
|
||||
model: str = Field(default_factory=lambda: settings.OPENAI_MODEL)
|
||||
api_base: str | None = None
|
||||
timeout: float = Field(default_factory=lambda: settings.OPENAI_TIMEOUT_SECONDS, gt=0)
|
||||
max_retries: int = Field(default_factory=lambda: settings.OPENAI_MAX_RETRIES, ge=0)
|
||||
max_tokens: int | None = Field(default=None, gt=0)
|
||||
temperature: float | None = None
|
||||
thinking_budget: int | None = None
|
||||
thinking_level: Literal["minimal", "low", "medium", "high"] | None = None
|
||||
reasoning_effort: Literal["none", "low", "medium", "high"] | None = None
|
||||
include_thoughts: bool | None = None
|
||||
enable_grounding: bool | None = None
|
||||
|
||||
def validate_provider(self, name: str) -> None:
|
||||
@@ -86,20 +74,6 @@ class LLM(BaseModel):
|
||||
if self.provider == "openai":
|
||||
if not settings.OPENAI_API_KEY:
|
||||
raise Exception(f"{name} requires OPENAI_API_KEY in .env")
|
||||
elif self.provider == "google":
|
||||
if not settings.GOOGLE_API_KEY:
|
||||
raise Exception(f"{name} requires GOOGLE_API_KEY in .env")
|
||||
elif self.provider == "vertexai":
|
||||
validate_vertex_ai_credentials()
|
||||
elif self.provider == "anthropic":
|
||||
if not (settings.ANTHROPIC_API_KEY or os.environ.get("ANTHROPIC_API_KEY")):
|
||||
raise Exception(f"{name} requires ANTHROPIC_API_KEY in .env")
|
||||
elif self.provider == "openrouter":
|
||||
if not settings.OPEN_ROUTER_API_KEY:
|
||||
raise Exception(f"{name} requires OPEN_ROUTER_API_KEY in .env")
|
||||
elif self.provider == "xai":
|
||||
if not settings.XAI_API_KEY:
|
||||
raise Exception(f"{name} requires XAI_API_KEY in .env")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.provider}/{self.model}"
|
||||
@@ -110,7 +84,6 @@ class LLMWithFallback(LLM):
|
||||
|
||||
fallback: LLM
|
||||
fix_model: str | None = None
|
||||
timeout: float | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.provider}/{self.model} (fallback: {self.fallback})"
|
||||
@@ -118,14 +91,14 @@ class LLMWithFallback(LLM):
|
||||
|
||||
def lightweight_judge_default() -> "LLMWithFallback":
|
||||
"""Factory default for the lightweight judge nodes (pixel safety net and
|
||||
planner validation): a flash-lite model at temperature 0."""
|
||||
planner validation): the configured Responses model at temperature 0."""
|
||||
return LLMWithFallback(
|
||||
provider="google",
|
||||
model="gemini-3.5-flash-lite",
|
||||
provider="openai",
|
||||
model=settings.OPENAI_MODEL,
|
||||
temperature=0.0,
|
||||
fallback=LLM(
|
||||
provider="google",
|
||||
model="gemini-3.1-flash-lite",
|
||||
provider="openai",
|
||||
model=settings.OPENAI_MODEL,
|
||||
temperature=0.0,
|
||||
),
|
||||
)
|
||||
@@ -236,11 +209,11 @@ def _expand_default_into_nodes(config_dict: dict) -> dict:
|
||||
default_model_cfg = config_dict.get(
|
||||
"default",
|
||||
{
|
||||
"provider": "google",
|
||||
"model": "gemini-3.8-flash",
|
||||
"provider": "openai",
|
||||
"model": settings.OPENAI_MODEL,
|
||||
"fallback": {
|
||||
"provider": "google",
|
||||
"model": "gemini-3.7-flash",
|
||||
"provider": "openai",
|
||||
"model": settings.OPENAI_MODEL,
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -310,7 +283,7 @@ def parse_llm_config() -> LLMConfig:
|
||||
|
||||
try:
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config_dict = load_jsonc(f)
|
||||
config_dict = interpolate_config_value(load_jsonc(f))
|
||||
expanded_dict = _expand_default_into_nodes(config_dict)
|
||||
return LLMConfig.model_validate(expanded_dict)
|
||||
except Exception as e:
|
||||
@@ -361,7 +334,7 @@ def load_llm_config_override(path: Path | str) -> LLMConfig:
|
||||
if resolved_path.exists():
|
||||
logger.info(f"Loading custom LLM config from {resolved_path.resolve()}...")
|
||||
with open(resolved_path, encoding="utf-8") as f:
|
||||
override_config_dict = load_jsonc(f)
|
||||
override_config_dict = interpolate_config_value(load_jsonc(f))
|
||||
else:
|
||||
logger.warning(f"Custom LLM config not found at {path} - using default config")
|
||||
|
||||
|
||||
+21
-71
@@ -26,16 +26,10 @@ from artemis.config.constants import (
|
||||
DEFAULT_ADB_PORT,
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_PROFILE,
|
||||
ENV_ANTHROPIC_API_KEY,
|
||||
ENV_DATA_ENGINE_DB_PATH,
|
||||
ENV_GCP_API_KEY,
|
||||
ENV_GEMINI_API_KEY,
|
||||
ENV_GOOGLE_API_KEY,
|
||||
ENV_OCR_API_KEY,
|
||||
ENV_OPEN_ROUTER_API_KEY,
|
||||
ENV_OPENAI_API_KEY,
|
||||
ENV_VISION_API_KEY,
|
||||
ENV_XAI_API_KEY,
|
||||
)
|
||||
from artemis.config.paths import (
|
||||
GLOBAL_APP_DIR,
|
||||
@@ -94,12 +88,6 @@ class Settings(BaseSettings):
|
||||
|
||||
# LLM Provider Authentication
|
||||
OPENAI_API_KEY: SecretStr | None = None
|
||||
GOOGLE_API_KEY: SecretStr | None = None
|
||||
GEMINI_API_KEY: SecretStr | None = None
|
||||
GCP_API_KEY: SecretStr | None = None
|
||||
ANTHROPIC_API_KEY: SecretStr | None = None
|
||||
XAI_API_KEY: SecretStr | None = None
|
||||
OPEN_ROUTER_API_KEY: SecretStr | None = None
|
||||
|
||||
# Google Cloud Vision OCR Authentication
|
||||
OCR_API_KEY: SecretStr | None = None
|
||||
@@ -108,6 +96,19 @@ class Settings(BaseSettings):
|
||||
|
||||
# Custom Provider Endpoints
|
||||
OPENAI_BASE_URL: str | None = None
|
||||
OPENAI_MODEL: str = Field(default="gpt-5-mini", min_length=1)
|
||||
OPENAI_TIMEOUT_SECONDS: float = Field(default=120.0, gt=0)
|
||||
OPENAI_MAX_RETRIES: int = Field(default=0, ge=0)
|
||||
ARTEMIS_CREDENTIAL_TIMEOUT_SECONDS: float = Field(default=12.0, gt=0)
|
||||
ARTEMIS_MODEL_CACHE_TTL_SECONDS: float = Field(default=10.0, ge=0)
|
||||
ARTEMIS_VIDEO_TIMEOUT_SECONDS: float = Field(default=120.0, gt=0)
|
||||
ARTEMIS_VIDEO_MAX_RETRIES: int = Field(default=1, ge=0)
|
||||
ARTEMIS_VIDEO_CHUNK_SECONDS: float = Field(default=60.0, gt=0)
|
||||
ARTEMIS_CHECKPOINT_TIMEOUT_SECONDS: float = Field(default=180.0, gt=0)
|
||||
ARTEMIS_SETTLEMENT_TIMEOUT_SECONDS: float = Field(default=120.0, gt=0)
|
||||
ARTEMIS_VIDEO_ENABLED: bool = True
|
||||
ARTEMIS_OUTPUTTER_ENABLED: bool = True
|
||||
ARTEMIS_STEP_SUMMARIZER_ENABLED: bool = True
|
||||
|
||||
# Android ADB Connectivity
|
||||
ADB_HOST: str | None = Field(default=DEFAULT_ADB_HOST)
|
||||
@@ -125,16 +126,6 @@ class Settings(BaseSettings):
|
||||
ARTEMIS_DEFAULT_PROFILE: str = Field(default=DEFAULT_PROFILE)
|
||||
ARTEMIS_DEFAULT_MODEL: str = Field(default=DEFAULT_MODEL)
|
||||
|
||||
# Explorer Tool Settings (the tier itself is configured in artemis.jsonc or
|
||||
# via ARTEMIS_EXPLORER_VERSION; see artemis.config.agent.ExplorerConfig)
|
||||
EXPLORER_CACHING: bool | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Environment-level override for Explorer context caching; unset"
|
||||
" defers to the agent configuration and the tier default."
|
||||
),
|
||||
)
|
||||
|
||||
# LLM Reliability
|
||||
LLM_PAUSE_TIMEOUT_SECONDS: float = Field(
|
||||
default=900.0,
|
||||
@@ -154,16 +145,10 @@ class Settings(BaseSettings):
|
||||
|
||||
@model_validator(mode="after")
|
||||
def fallback_api_keys(self) -> "Settings":
|
||||
"""Normalize Google / Gemini / GCP and OCR API keys and filter placeholders."""
|
||||
"""Normalize OpenAI and OCR API keys and filter placeholders."""
|
||||
# Sanitize any placeholder values loaded from environment or .env
|
||||
for attr in (
|
||||
"OPENAI_API_KEY",
|
||||
"GOOGLE_API_KEY",
|
||||
"GEMINI_API_KEY",
|
||||
"GCP_API_KEY",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"XAI_API_KEY",
|
||||
"OPEN_ROUTER_API_KEY",
|
||||
"OCR_API_KEY",
|
||||
"VISION_API_KEY",
|
||||
"API_KEY",
|
||||
@@ -172,12 +157,6 @@ class Settings(BaseSettings):
|
||||
if val and is_placeholder_key(val):
|
||||
setattr(self, attr, None)
|
||||
|
||||
if not self.GOOGLE_API_KEY:
|
||||
if self.GEMINI_API_KEY:
|
||||
self.GOOGLE_API_KEY = self.GEMINI_API_KEY
|
||||
elif self.GCP_API_KEY:
|
||||
self.GOOGLE_API_KEY = self.GCP_API_KEY
|
||||
|
||||
# Fallback for OCR & Vision API keys
|
||||
if not self.OCR_API_KEY and self.VISION_API_KEY:
|
||||
self.OCR_API_KEY = self.VISION_API_KEY
|
||||
@@ -187,27 +166,18 @@ class Settings(BaseSettings):
|
||||
"""Get API key for a specified provider.
|
||||
|
||||
Args:
|
||||
provider: Provider name (e.g., 'google', 'gemini', 'openai', 'ocr', 'vision').
|
||||
provider: Provider name ('openai', 'ocr', or 'vision').
|
||||
|
||||
Returns:
|
||||
SecretStr containing the API key or None if not configured.
|
||||
"""
|
||||
from artemis.llm.google.provider import is_google_family_provider
|
||||
|
||||
provider_lower = provider.lower()
|
||||
key: SecretStr | None = None
|
||||
if is_google_family_provider(provider_lower):
|
||||
key = self.GOOGLE_API_KEY or self.GEMINI_API_KEY or self.GCP_API_KEY
|
||||
elif provider_lower in ("ocr", "vision", "google_vision"):
|
||||
if provider_lower in ("ocr", "vision", "google_vision"):
|
||||
key = self.OCR_API_KEY or self.VISION_API_KEY
|
||||
elif provider_lower == "openai":
|
||||
key = self.OPENAI_API_KEY
|
||||
elif provider_lower in ("anthropic", "claude"):
|
||||
key = self.ANTHROPIC_API_KEY
|
||||
elif provider_lower == "openrouter":
|
||||
key = self.OPEN_ROUTER_API_KEY
|
||||
elif provider_lower in ("xai", "grok"):
|
||||
key = self.XAI_API_KEY
|
||||
|
||||
if key and not is_placeholder_key(key):
|
||||
return key
|
||||
@@ -222,35 +192,14 @@ class Settings(BaseSettings):
|
||||
persist_to_env: Whether to save the key to the app directory's .env file.
|
||||
"""
|
||||
secret = SecretStr(key)
|
||||
from artemis.llm.google.provider import is_google_family_provider
|
||||
|
||||
provider_lower = provider.lower()
|
||||
env_key_name = None
|
||||
|
||||
if is_google_family_provider(provider_lower):
|
||||
self.GOOGLE_API_KEY = secret
|
||||
self.GEMINI_API_KEY = secret
|
||||
self.GCP_API_KEY = secret
|
||||
env_key_name = ENV_GEMINI_API_KEY
|
||||
os.environ[ENV_GOOGLE_API_KEY] = key
|
||||
os.environ[ENV_GEMINI_API_KEY] = key
|
||||
os.environ[ENV_GCP_API_KEY] = key
|
||||
elif provider_lower == "openai":
|
||||
if provider_lower == "openai":
|
||||
self.OPENAI_API_KEY = secret
|
||||
env_key_name = ENV_OPENAI_API_KEY
|
||||
os.environ[ENV_OPENAI_API_KEY] = key
|
||||
elif provider_lower in ("anthropic", "claude"):
|
||||
self.ANTHROPIC_API_KEY = secret
|
||||
env_key_name = ENV_ANTHROPIC_API_KEY
|
||||
os.environ[ENV_ANTHROPIC_API_KEY] = key
|
||||
elif provider_lower == "openrouter":
|
||||
self.OPEN_ROUTER_API_KEY = secret
|
||||
env_key_name = ENV_OPEN_ROUTER_API_KEY
|
||||
os.environ[ENV_OPEN_ROUTER_API_KEY] = key
|
||||
elif provider_lower == "xai":
|
||||
self.XAI_API_KEY = secret
|
||||
env_key_name = ENV_XAI_API_KEY
|
||||
os.environ[ENV_XAI_API_KEY] = key
|
||||
elif provider_lower in ("ocr", "vision", "google_vision"):
|
||||
self.OCR_API_KEY = secret
|
||||
self.VISION_API_KEY = secret
|
||||
@@ -258,6 +207,9 @@ class Settings(BaseSettings):
|
||||
os.environ[ENV_OCR_API_KEY] = key
|
||||
os.environ[ENV_VISION_API_KEY] = key
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported credential provider: {provider}")
|
||||
|
||||
if persist_to_env and env_key_name:
|
||||
target_env_files = [get_env_file()]
|
||||
seen_paths = set()
|
||||
@@ -275,9 +227,7 @@ class Settings(BaseSettings):
|
||||
|
||||
# Determine keys to update
|
||||
keys_to_update = [env_key_name]
|
||||
if is_google_family_provider(provider_lower):
|
||||
keys_to_update = [ENV_GEMINI_API_KEY, ENV_GOOGLE_API_KEY, ENV_GCP_API_KEY]
|
||||
elif provider_lower in ("ocr", "vision", "google_vision"):
|
||||
if provider_lower in ("ocr", "vision", "google_vision"):
|
||||
keys_to_update = [ENV_OCR_API_KEY, ENV_VISION_API_KEY]
|
||||
|
||||
new_lines = []
|
||||
|
||||
Reference in new issue
Block a user