virattt/ai-hedge-fund · error · ValueError
{env_var} not found. Set it in your .env to use {provider} m
Error message
{env_var} not found. Set it in your .env to use {provider} models. What it means
Raised by _require_key (hedge_fund/llm/client.py:184) when the provider's API key environment variable is unset or empty. The error deliberately names the exact variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, DEEPSEEK_API_KEY, GOOGLE_API_KEY, KIMI_API_KEY — Kimi also accepts MOONSHOT_API_KEY) because that is the only actionable item for the user.
Source
Thrown at hedge_fund/llm/client.py:184
if isinstance(content, list):
parts = []
for block in content:
if isinstance(block, str):
parts.append(block)
elif isinstance(block, dict) and block.get("type") == "text":
parts.append(block.get("text", ""))
return sep.join(parts)
return "" if content is None else str(content)
def _require_key(provider: str) -> str:
"""The provider's API key, or a failure that names the variable to set."""
env_var = env_var_for(provider)
# Kimi accepts either name; v1 reads MOONSHOT_API_KEY first.
key = (os.getenv("MOONSHOT_API_KEY") if provider == "Kimi" else None)
key = key or (os.getenv(env_var) if env_var else None)
if not key:
raise ValueError(
f"{env_var} not found. Set it in your .env to use {provider} models."
)
return key
def extract_json(text: str) -> dict:
"""Pull the first JSON object out of an LLM response.
Tries: ```json fence -> whole string -> first balanced {...} block.
Raises LLMParseError if nothing parses.
"""
fence = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
if fence:
try:
return json.loads(fence.group(1))
except json.JSONDecodeError:
pass
View on GitHub (pinned to eff8a7320f)
Solutions
- Add the exact variable named in the message to your .env (e.g. ANTHROPIC_API_KEY=sk-ant-...) and restart the process so it's reloaded.
- If the .env exists but isn't picked up, verify it's on the loader's path (cwd / dotenv config) or export the variable in the shell.
- For Kimi specifically, either KIMI_API_KEY or MOONSHOT_API_KEY works — set whichever you have.
Example fix
# before # .env missing key -> ValueError: ANTHROPIC_API_KEY not found. Set it in your .env to use Anthropic models. # after # .env ANTHROPIC_API_KEY=sk-ant-your-key-here
Defensive patterns
Strategy: validation
Validate before calling
from hedge_fund.llm.registry import PROVIDER_ENV_VARS, provider_for
def key_is_set(model_id: str) -> bool:
provider = provider_for(model_id) or "Anthropic"
env_var = PROVIDER_ENV_VARS[provider]
import os
if provider == "Kimi":
return bool(os.getenv("KIMI_API_KEY") or os.getenv("MOONSHOT_API_KEY"))
return bool(os.getenv(env_var)) Type guard
import os
from hedge_fund.llm.registry import PROVIDER_ENV_VARS
def provider_key_present(provider: str) -> bool:
env_var = PROVIDER_ENV_VARS.get(provider)
if env_var is None:
return False
if provider == "Kimi":
return bool(os.getenv("MOONSHOT_API_KEY") or os.getenv(env_var))
return bool(os.getenv(env_var)) Try / catch
try:
chat = make_llm()
except ValueError as e:
if "not found. Set it in your .env" in str(e):
raise SystemExit(str(e)) from e # message already names the variable
raise Prevention
- At startup, check the key for the configured provider and fail fast with the variable name.
- Copy .env.example to .env on fresh clones before running anything LLM-backed.
- In CI, inject provider keys as secrets and assert make_llm() constructs cleanly in a smoke job.
When it happens
Trigger: Calling make_llm() (directly or via an LLM-agent signal) with no .env loaded: no ANTHROPIC_API_KEY when defaulting to claude-sonnet-5; selecting a DeepSeek or Google model in the TUI without its key set; key present but empty string. Note Kimi has a two-name fallback (MOONSHOT_API_KEY then KIMI_API_KEY); all other providers check exactly one variable.
Common situations: Fresh clone without copying .env.example; .env file exists but the process wasn't started in a directory where it's loaded, or the key line is commented out; CI runner missing the secret; typo in the variable name (e.g. ANTHROPIC_KEY).
Related errors
- No v2 client for {provider} (model {model}). Supported: {',
- {method} {path} returned {resp.status_code}: {resp.text[:200
- no JSON object found in response: {text[:200]!r}
- invalid signal {data.get('signal')!r}
- confidence out of range: {confidence}
AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15).
Data as JSON: /api/errors/46cf29f0d1bff701.
Report an issue: GitHub.