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
9 changes: 9 additions & 0 deletions hindsight-api-slim/hindsight_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ def _parse_boolean_env(env_name: str, default: bool) -> bool:
ENV_RETAIN_LLM_MAX_BACKOFF = "HINDSIGHT_API_RETAIN_LLM_MAX_BACKOFF"
ENV_RETAIN_LLM_TIMEOUT = "HINDSIGHT_API_RETAIN_LLM_TIMEOUT"
ENV_RETAIN_LLM_LITELLMROUTER_CONFIG = "HINDSIGHT_API_RETAIN_LLM_LITELLMROUTER_CONFIG"
ENV_RETAIN_LLM_REASONING_EFFORT = "HINDSIGHT_API_RETAIN_LLM_REASONING_EFFORT"

# Fireworks AI batch inference. Fireworks' batch API is a proprietary
# account-scoped dataset/job REST API on a control-plane host, distinct from the
Expand All @@ -336,6 +337,7 @@ def _parse_boolean_env(env_name: str, default: bool) -> bool:
ENV_REFLECT_LLM_MAX_BACKOFF = "HINDSIGHT_API_REFLECT_LLM_MAX_BACKOFF"
ENV_REFLECT_LLM_TIMEOUT = "HINDSIGHT_API_REFLECT_LLM_TIMEOUT"
ENV_REFLECT_LLM_LITELLMROUTER_CONFIG = "HINDSIGHT_API_REFLECT_LLM_LITELLMROUTER_CONFIG"
ENV_REFLECT_LLM_REASONING_EFFORT = "HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT"

ENV_CONSOLIDATION_LLM_PROVIDER = "HINDSIGHT_API_CONSOLIDATION_LLM_PROVIDER"
ENV_CONSOLIDATION_LLM_API_KEY = "HINDSIGHT_API_CONSOLIDATION_LLM_API_KEY"
Expand All @@ -347,6 +349,7 @@ def _parse_boolean_env(env_name: str, default: bool) -> bool:
ENV_CONSOLIDATION_LLM_MAX_BACKOFF = "HINDSIGHT_API_CONSOLIDATION_LLM_MAX_BACKOFF"
ENV_CONSOLIDATION_LLM_TIMEOUT = "HINDSIGHT_API_CONSOLIDATION_LLM_TIMEOUT"
ENV_CONSOLIDATION_LLM_LITELLMROUTER_CONFIG = "HINDSIGHT_API_CONSOLIDATION_LLM_LITELLMROUTER_CONFIG"
ENV_CONSOLIDATION_LLM_REASONING_EFFORT = "HINDSIGHT_API_CONSOLIDATION_LLM_REASONING_EFFORT"

ENV_EMBEDDINGS_PROVIDER = "HINDSIGHT_API_EMBEDDINGS_PROVIDER"
ENV_EMBEDDINGS_LOCAL_MODEL = "HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL"
Expand Down Expand Up @@ -1859,6 +1862,7 @@ class HindsightConfig:
retain_llm_max_backoff: float | None
retain_llm_timeout: float | None
retain_llm_litellmrouter_config: dict | None
retain_llm_reasoning_effort: str | None

# Fireworks AI batch inference (static, server-level)
fireworks_account_id: str | None
Expand All @@ -1875,6 +1879,7 @@ class HindsightConfig:
reflect_llm_max_backoff: float | None
reflect_llm_timeout: float | None
reflect_llm_litellmrouter_config: dict | None
reflect_llm_reasoning_effort: str | None

consolidation_llm_provider: str | None
consolidation_llm_api_key: str | None
Expand All @@ -1886,6 +1891,7 @@ class HindsightConfig:
consolidation_llm_max_backoff: float | None
consolidation_llm_timeout: float | None
consolidation_llm_litellmrouter_config: dict | None
consolidation_llm_reasoning_effort: str | None

# Embeddings
embeddings_provider: str
Expand Down Expand Up @@ -2681,6 +2687,7 @@ def from_env(cls) -> "HindsightConfig":
else None,
retain_llm_timeout=float(os.getenv(ENV_RETAIN_LLM_TIMEOUT)) if os.getenv(ENV_RETAIN_LLM_TIMEOUT) else None,
retain_llm_litellmrouter_config=_parse_llm_router_config(ENV_RETAIN_LLM_LITELLMROUTER_CONFIG),
retain_llm_reasoning_effort=os.getenv(ENV_RETAIN_LLM_REASONING_EFFORT) or None,
reflect_llm_provider=os.getenv(ENV_REFLECT_LLM_PROVIDER) or None,
reflect_llm_api_key=os.getenv(ENV_REFLECT_LLM_API_KEY) or None,
reflect_llm_model=os.getenv(ENV_REFLECT_LLM_MODEL)
Expand All @@ -2706,6 +2713,7 @@ def from_env(cls) -> "HindsightConfig":
if os.getenv(ENV_REFLECT_LLM_TIMEOUT)
else None,
reflect_llm_litellmrouter_config=_parse_llm_router_config(ENV_REFLECT_LLM_LITELLMROUTER_CONFIG),
reflect_llm_reasoning_effort=os.getenv(ENV_REFLECT_LLM_REASONING_EFFORT) or None,
consolidation_llm_provider=os.getenv(ENV_CONSOLIDATION_LLM_PROVIDER) or None,
consolidation_llm_api_key=os.getenv(ENV_CONSOLIDATION_LLM_API_KEY) or None,
consolidation_llm_model=os.getenv(ENV_CONSOLIDATION_LLM_MODEL)
Expand All @@ -2731,6 +2739,7 @@ def from_env(cls) -> "HindsightConfig":
if os.getenv(ENV_CONSOLIDATION_LLM_TIMEOUT)
else None,
consolidation_llm_litellmrouter_config=_parse_llm_router_config(ENV_CONSOLIDATION_LLM_LITELLMROUTER_CONFIG),
consolidation_llm_reasoning_effort=os.getenv(ENV_CONSOLIDATION_LLM_REASONING_EFFORT) or None,
# Multi-LLM chains (indexed members + routing strategy)
llm_members=_parse_llm_members(""),
llm_strategy=_parse_llm_strategy(os.getenv(ENV_LLM_STRATEGY)),
Expand Down
6 changes: 3 additions & 3 deletions hindsight-api-slim/hindsight_api/engine/memory_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1182,7 +1182,7 @@ def pick(field: str) -> Any:
api_key=retain_api_key,
base_url=retain_base_url,
model=retain_model,
reasoning_effort=config.llm_reasoning_effort,
reasoning_effort=config.retain_llm_reasoning_effort or config.llm_reasoning_effort,
extra_body=config.llm_extra_body,
default_headers=config.llm_default_headers,
ollama_num_ctx=config.llm_ollama_num_ctx,
Expand Down Expand Up @@ -1221,7 +1221,7 @@ def pick(field: str) -> Any:
api_key=reflect_api_key,
base_url=reflect_base_url,
model=reflect_model,
reasoning_effort=config.llm_reasoning_effort,
reasoning_effort=config.reflect_llm_reasoning_effort or config.llm_reasoning_effort,
extra_body=config.llm_extra_body,
default_headers=config.llm_default_headers,
ollama_num_ctx=config.llm_ollama_num_ctx,
Expand Down Expand Up @@ -1260,7 +1260,7 @@ def pick(field: str) -> Any:
api_key=consolidation_api_key,
base_url=consolidation_base_url,
model=consolidation_model,
reasoning_effort=config.llm_reasoning_effort,
reasoning_effort=config.consolidation_llm_reasoning_effort or config.llm_reasoning_effort,
extra_body=config.llm_extra_body,
default_headers=config.llm_default_headers,
ollama_num_ctx=config.llm_ollama_num_ctx,
Expand Down
81 changes: 81 additions & 0 deletions hindsight-api-slim/tests/test_per_operation_llm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,84 @@ def test_per_operation_retry_backoff_fallback_to_global(self):
os.environ.pop("HINDSIGHT_API_LLM_INITIAL_BACKOFF", None)
os.environ.pop("HINDSIGHT_API_LLM_MAX_BACKOFF", None)
clear_config_cache()


class TestPerOperationReasoningEffort:
"""Test the per-operation reasoning_effort override (issue #2998)."""

_REASONING_EFFORT_ENV_VARS = (
"HINDSIGHT_API_LLM_REASONING_EFFORT",
"HINDSIGHT_API_RETAIN_LLM_REASONING_EFFORT",
"HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT",
"HINDSIGHT_API_CONSOLIDATION_LLM_REASONING_EFFORT",
)

def _clear_reasoning_effort_env(self):
for key in self._REASONING_EFFORT_ENV_VARS:
os.environ.pop(key, None)

def test_per_operation_reasoning_effort_from_env(self):
"""Per-operation reasoning_effort overrides are loaded from environment."""
from hindsight_api.config import clear_config_cache, get_config

os.environ["HINDSIGHT_API_LLM_REASONING_EFFORT"] = "low"
os.environ["HINDSIGHT_API_RETAIN_LLM_REASONING_EFFORT"] = "high"
os.environ["HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT"] = "none"
os.environ["HINDSIGHT_API_CONSOLIDATION_LLM_REASONING_EFFORT"] = "medium"

try:
clear_config_cache()
config = get_config()

assert config.retain_llm_reasoning_effort == "high"
assert config.reflect_llm_reasoning_effort == "none"
assert config.consolidation_llm_reasoning_effort == "medium"

# Global stays untouched.
assert config.llm_reasoning_effort == "low"
finally:
self._clear_reasoning_effort_env()
clear_config_cache()

def test_per_operation_reasoning_effort_fallback_to_global(self):
"""Unset per-operation reasoning_effort stays None (falls back at runtime)."""
from hindsight_api.config import clear_config_cache, get_config

os.environ["HINDSIGHT_API_LLM_REASONING_EFFORT"] = "medium"
os.environ.pop("HINDSIGHT_API_RETAIN_LLM_REASONING_EFFORT", None)
os.environ.pop("HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT", None)
os.environ.pop("HINDSIGHT_API_CONSOLIDATION_LLM_REASONING_EFFORT", None)

try:
clear_config_cache()
config = get_config()

assert config.retain_llm_reasoning_effort is None
assert config.reflect_llm_reasoning_effort is None
assert config.consolidation_llm_reasoning_effort is None
assert config.llm_reasoning_effort == "medium"
finally:
self._clear_reasoning_effort_env()
clear_config_cache()

def test_memory_engine_applies_per_operation_reasoning_effort(self):
"""The engine threads each per-operation override into its LLM config,
and unset operations fall back to the global value."""
from hindsight_api import MemoryEngine
from hindsight_api.config import clear_config_cache

os.environ["HINDSIGHT_API_LLM_REASONING_EFFORT"] = "low"
os.environ["HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT"] = "none"
# retain/consolidation intentionally unset -> fall back to global "low".

try:
clear_config_cache()
engine = MemoryEngine(skip_llm_verification=True)

assert engine._llm_config.reasoning_effort == "low"
assert engine._reflect_llm_config.reasoning_effort == "none"
assert engine._retain_llm_config.reasoning_effort == "low"
assert engine._consolidation_llm_config.reasoning_effort == "low"
finally:
self._clear_reasoning_effort_env()
clear_config_cache()
3 changes: 3 additions & 0 deletions hindsight-docs/docs/developer/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ Different memory operations have different requirements. **Retain** (fact extrac
| `HINDSIGHT_API_RETAIN_LLM_INITIAL_BACKOFF` | Initial backoff for retain retries (seconds) | Falls back to `HINDSIGHT_API_LLM_INITIAL_BACKOFF` |
| `HINDSIGHT_API_RETAIN_LLM_MAX_BACKOFF` | Max backoff cap for retain retries (seconds) | Falls back to `HINDSIGHT_API_LLM_MAX_BACKOFF` |
| `HINDSIGHT_API_RETAIN_LLM_TIMEOUT` | Timeout for retain requests (seconds) | Falls back to `HINDSIGHT_API_LLM_TIMEOUT` |
| `HINDSIGHT_API_RETAIN_LLM_REASONING_EFFORT` | Reasoning effort for retain operations | Falls back to `HINDSIGHT_API_LLM_REASONING_EFFORT` |
| `HINDSIGHT_API_REFLECT_LLM_PROVIDER` | LLM provider for reflect operations | Falls back to `HINDSIGHT_API_LLM_PROVIDER` |
| `HINDSIGHT_API_REFLECT_LLM_API_KEY` | API key for reflect LLM | Falls back to `HINDSIGHT_API_LLM_API_KEY` |
| `HINDSIGHT_API_REFLECT_LLM_MODEL` | Model for reflect operations | Falls back to `HINDSIGHT_API_LLM_MODEL` |
Expand All @@ -478,6 +479,7 @@ Different memory operations have different requirements. **Retain** (fact extrac
| `HINDSIGHT_API_REFLECT_LLM_INITIAL_BACKOFF` | Initial backoff for reflect retries (seconds) | Falls back to `HINDSIGHT_API_LLM_INITIAL_BACKOFF` |
| `HINDSIGHT_API_REFLECT_LLM_MAX_BACKOFF` | Max backoff cap for reflect retries (seconds) | Falls back to `HINDSIGHT_API_LLM_MAX_BACKOFF` |
| `HINDSIGHT_API_REFLECT_LLM_TIMEOUT` | Timeout for reflect requests (seconds) | Falls back to `HINDSIGHT_API_LLM_TIMEOUT` |
| `HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT` | Reasoning effort for reflect operations | Falls back to `HINDSIGHT_API_LLM_REASONING_EFFORT` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_PROVIDER` | LLM provider for observation consolidation | Falls back to `HINDSIGHT_API_LLM_PROVIDER` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_API_KEY` | API key for consolidation LLM | Falls back to `HINDSIGHT_API_LLM_API_KEY` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_MODEL` | Model for consolidation operations | Falls back to `HINDSIGHT_API_LLM_MODEL` |
Expand All @@ -487,6 +489,7 @@ Different memory operations have different requirements. **Retain** (fact extrac
| `HINDSIGHT_API_CONSOLIDATION_LLM_INITIAL_BACKOFF` | Initial backoff for consolidation retries (seconds) | Falls back to `HINDSIGHT_API_LLM_INITIAL_BACKOFF` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_MAX_BACKOFF` | Max backoff cap for consolidation retries (seconds) | Falls back to `HINDSIGHT_API_LLM_MAX_BACKOFF` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_TIMEOUT` | Timeout for consolidation requests (seconds) | Falls back to `HINDSIGHT_API_LLM_TIMEOUT` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_REASONING_EFFORT` | Reasoning effort for consolidation operations | Falls back to `HINDSIGHT_API_LLM_REASONING_EFFORT` |

:::tip When to Use Per-Operation Config
- **Retain**: Use models with strong structured output (e.g., GPT-4o, Claude) for accurate fact extraction
Expand Down
3 changes: 3 additions & 0 deletions skills/hindsight-docs/references/developer/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ Different memory operations have different requirements. **Retain** (fact extrac
| `HINDSIGHT_API_RETAIN_LLM_INITIAL_BACKOFF` | Initial backoff for retain retries (seconds) | Falls back to `HINDSIGHT_API_LLM_INITIAL_BACKOFF` |
| `HINDSIGHT_API_RETAIN_LLM_MAX_BACKOFF` | Max backoff cap for retain retries (seconds) | Falls back to `HINDSIGHT_API_LLM_MAX_BACKOFF` |
| `HINDSIGHT_API_RETAIN_LLM_TIMEOUT` | Timeout for retain requests (seconds) | Falls back to `HINDSIGHT_API_LLM_TIMEOUT` |
| `HINDSIGHT_API_RETAIN_LLM_REASONING_EFFORT` | Reasoning effort for retain operations | Falls back to `HINDSIGHT_API_LLM_REASONING_EFFORT` |
| `HINDSIGHT_API_REFLECT_LLM_PROVIDER` | LLM provider for reflect operations | Falls back to `HINDSIGHT_API_LLM_PROVIDER` |
| `HINDSIGHT_API_REFLECT_LLM_API_KEY` | API key for reflect LLM | Falls back to `HINDSIGHT_API_LLM_API_KEY` |
| `HINDSIGHT_API_REFLECT_LLM_MODEL` | Model for reflect operations | Falls back to `HINDSIGHT_API_LLM_MODEL` |
Expand All @@ -478,6 +479,7 @@ Different memory operations have different requirements. **Retain** (fact extrac
| `HINDSIGHT_API_REFLECT_LLM_INITIAL_BACKOFF` | Initial backoff for reflect retries (seconds) | Falls back to `HINDSIGHT_API_LLM_INITIAL_BACKOFF` |
| `HINDSIGHT_API_REFLECT_LLM_MAX_BACKOFF` | Max backoff cap for reflect retries (seconds) | Falls back to `HINDSIGHT_API_LLM_MAX_BACKOFF` |
| `HINDSIGHT_API_REFLECT_LLM_TIMEOUT` | Timeout for reflect requests (seconds) | Falls back to `HINDSIGHT_API_LLM_TIMEOUT` |
| `HINDSIGHT_API_REFLECT_LLM_REASONING_EFFORT` | Reasoning effort for reflect operations | Falls back to `HINDSIGHT_API_LLM_REASONING_EFFORT` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_PROVIDER` | LLM provider for observation consolidation | Falls back to `HINDSIGHT_API_LLM_PROVIDER` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_API_KEY` | API key for consolidation LLM | Falls back to `HINDSIGHT_API_LLM_API_KEY` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_MODEL` | Model for consolidation operations | Falls back to `HINDSIGHT_API_LLM_MODEL` |
Expand All @@ -487,6 +489,7 @@ Different memory operations have different requirements. **Retain** (fact extrac
| `HINDSIGHT_API_CONSOLIDATION_LLM_INITIAL_BACKOFF` | Initial backoff for consolidation retries (seconds) | Falls back to `HINDSIGHT_API_LLM_INITIAL_BACKOFF` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_MAX_BACKOFF` | Max backoff cap for consolidation retries (seconds) | Falls back to `HINDSIGHT_API_LLM_MAX_BACKOFF` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_TIMEOUT` | Timeout for consolidation requests (seconds) | Falls back to `HINDSIGHT_API_LLM_TIMEOUT` |
| `HINDSIGHT_API_CONSOLIDATION_LLM_REASONING_EFFORT` | Reasoning effort for consolidation operations | Falls back to `HINDSIGHT_API_LLM_REASONING_EFFORT` |

:::tip When to Use Per-Operation Config
- **Retain**: Use models with strong structured output (e.g., GPT-4o, Claude) for accurate fact extraction
Expand Down