Compare commits

...
Author SHA1 Message Date
Chris BartholomewandNicolò Boschi 2128e28ded feat(recall): make budget mapping configurable per bank (#1106) (#1127)
* feat(recall): make budget mapping configurable per bank

The Budget enum (low/mid/high) used to map to hardcoded thinking_budget
values (100/300/1000) regardless of the request's max_tokens. This adds
a configurable mapping function:

- "fixed" (default, preserves legacy behavior): per-level integer
  read from recall_budget_fixed_<level>.
- "adaptive": round(max_tokens * recall_budget_adaptive_<level>),
  clamped to [recall_budget_min, recall_budget_max] so retrieval
  breadth scales with the requested output size.

All 9 knobs (function selector, 3 fixed values, 3 adaptive ratios,
min/max clamps) are hierarchical config fields — overridable via env
vars and per bank through the existing bank-config API. Validation in
ConfigResolver rejects invalid functions, non-positive values, and
min > max.

* docs(recall-budget): expose new fields in bank template + import API

Adds the 9 recall_budget_* fields to BankTemplateConfig so they can be
set via POST /v1/default/banks/{id}/import (the bank-template manifest
flow), and documents them in the memory-banks API page alongside the
other configurable bank fields.

- Extends BankTemplateConfig in api/http.py with the 9 fields.
- Adds them to the round-trip parametrized test in
  test_bank_template_configurable_fields.py.
- Adds a "Recall budget" subsection to memory-banks.mdx covering the
  function selector and per-level / clamp fields, with cross-link to
  the env-var reference in configuration.md.
- Regenerates openapi.json, bank-template-schema.json, and the
  Python/TypeScript/Go client models.

* fix(recall-budget): bump field-count cap and regen docs-skill refs

- test_config_get_bank_config_no_static_or_credential_fields_leak asserts
  the resolved-config dict size; cap was 30, now 34 fields fit (added 9).
  Bump to 50 to leave headroom for future configurable fields.
- Run scripts/generate-docs-skill.sh so the mirrored docs in
  skills/hindsight-docs/references/ pick up the new memory-banks /
  configuration entries and openapi schema.

Co-authored-by: Nicolò Boschi <[email protected]>
2026-04-17 09:52:55 -04:00
Chris Bartholomew 3af232f7c0 feat: add enable_reranking flag for per-bank RAG mode
Add HINDSIGHT_API_ENABLE_RERANKING config flag. When disabled, recall
skips the cross-encoder scoring step and uses RRF fusion scores
directly. On a 618-node bank this reduces reranking from ~600ms to 0ms.

- Added to configurable fields, BankTemplateConfig, CreateBankRequest
- RAG mode template updated to include enable_reranking: false
- Banks can now be created in full RAG mode via a single PUT
2026-04-15 20:29:08 -04:00
Chris Bartholomew 9be7bb0d57 feat: per-bank RAG mode via bank config API
Add enable_temporal_extraction and enable_graph_retrieval to the
configurable fields set, enabling per-bank RAG mode via PATCH
/v1/{tenant}/banks/{bank_id}/config.

- Recall resolves bank-specific config instead of global config
- Always load query analyzer at startup (any bank may need it)
- Add fields to BankTemplateConfig for template import/export
- Add RAG mode template to docs template registry
- Add "retrieval" category to docs template page
2026-04-14 16:12:28 -04:00
Chris Bartholomew 6524bbbc71 feat: add RAG mode config flags to reduce recall latency
Add HINDSIGHT_API_ENABLE_TEMPORAL_EXTRACTION (default: true) and
HINDSIGHT_API_ENABLE_GRAPH_RETRIEVAL (default: true) config flags.

When disabled, recall skips dateparser temporal extraction (~120ms)
and entity/link graph traversal, reducing retrieval to semantic + BM25
only. This enables a low-latency RAG mode for chunks-based use cases.
2026-04-11 07:56:57 -04:00
22 changed files with 4719 additions and 60 deletions
@@ -1058,6 +1058,19 @@ class CreateBankRequest(BaseModel):
default=None,
description="Controls what gets synthesised into observations. Replaces built-in consolidation rules entirely.",
)
# RAG mode retrieval settings
enable_temporal_extraction: bool | None = Field(
default=None,
description="Toggle temporal extraction during recall. Disable for lower latency.",
)
enable_graph_retrieval: bool | None = Field(
default=None,
description="Toggle entity/link graph traversal during recall. Disable for lower latency.",
)
enable_reranking: bool | None = Field(
default=None,
description="Toggle cross-encoder reranking during recall. Disable for lower latency.",
)
def get_config_updates(self) -> dict[str, Any]:
"""Return only the config fields that were explicitly set.
@@ -1090,6 +1103,9 @@ class CreateBankRequest(BaseModel):
"retain_chunk_size",
"enable_observations",
"observations_mission",
"enable_temporal_extraction",
"enable_graph_retrieval",
"enable_reranking",
):
value = getattr(self, field_name)
if value is not None:
@@ -1662,6 +1678,15 @@ class BankTemplateConfig(BaseModel):
default=None, description="Custom extraction prompt (when mode='custom')"
)
retain_chunk_size: int | None = Field(default=None, description="Max token size for each content chunk")
enable_temporal_extraction: bool | None = Field(
default=None, description="Toggle dateparser temporal extraction during recall"
)
enable_graph_retrieval: bool | None = Field(
default=None, description="Toggle entity/link graph traversal during recall"
)
enable_reranking: bool | None = Field(
default=None, description="Toggle cross-encoder reranking during recall"
)
enable_observations: bool | None = Field(default=None, description="Toggle observation consolidation")
observations_mission: str | None = Field(default=None, description="Controls what gets synthesised")
disposition_skepticism: int | None = Field(default=None, ge=1, le=5, description="Skepticism trait (1-5)")
@@ -1673,6 +1698,61 @@ class BankTemplateConfig(BaseModel):
entities_allow_free_form: bool | None = Field(
default=None, description="Allow entities outside the label vocabulary"
)
retain_default_strategy: str | None = Field(
default=None, description="Name of the default retain strategy (key into retain_strategies map)"
)
retain_strategies: dict | None = Field(
default=None, description="Map of retain strategy name to per-strategy config dict"
)
retain_chunk_batch_size: int | None = Field(
default=None, description="Max chunks per streaming batch (0 disables batching)"
)
mcp_enabled_tools: list[str] | None = Field(
default=None, description="MCP tool allowlist for this bank (None = all tools)"
)
consolidation_llm_batch_size: int | None = Field(
default=None, description="LLM batch size for observation consolidation"
)
consolidation_source_facts_max_tokens: int | None = Field(
default=None, description="Max tokens of source facts per consolidation batch"
)
consolidation_source_facts_max_tokens_per_observation: int | None = Field(
default=None, description="Max tokens of source facts per observation"
)
max_observations_per_scope: int | None = Field(
default=None, description="Max observations to retain per consolidation scope"
)
reflect_source_facts_max_tokens: int | None = Field(
default=None, description="Max tokens of source facts per reflect call"
)
llm_gemini_safety_settings: list | None = Field(
default=None, description="Per-bank Gemini/VertexAI safety filter settings"
)
recall_budget_function: str | None = Field(
default=None, description="Recall budget mapping function: 'fixed' or 'adaptive'"
)
recall_budget_fixed_low: int | None = Field(
default=None, description="Fixed thinking_budget for budget=low (function='fixed')"
)
recall_budget_fixed_mid: int | None = Field(
default=None, description="Fixed thinking_budget for budget=mid (function='fixed')"
)
recall_budget_fixed_high: int | None = Field(
default=None, description="Fixed thinking_budget for budget=high (function='fixed')"
)
recall_budget_adaptive_low: float | None = Field(
default=None, description="Ratio of max_tokens for budget=low (function='adaptive')"
)
recall_budget_adaptive_mid: float | None = Field(
default=None, description="Ratio of max_tokens for budget=mid (function='adaptive')"
)
recall_budget_adaptive_high: float | None = Field(
default=None, description="Ratio of max_tokens for budget=high (function='adaptive')"
)
recall_budget_min: int | None = Field(default=None, description="Floor for the adaptive function (after clamping)")
recall_budget_max: int | None = Field(
default=None, description="Ceiling for the adaptive function (after clamping)"
)
def get_config_updates(self) -> dict[str, Any]:
"""Return only the fields that were explicitly set (non-None)."""
+134
View File
@@ -325,6 +325,21 @@ ENV_FILE_CONVERSION_MAX_BATCH_SIZE = "HINDSIGHT_API_FILE_CONVERSION_MAX_BATCH_SI
ENV_ENABLE_FILE_UPLOAD_API = "HINDSIGHT_API_ENABLE_FILE_UPLOAD_API"
ENV_FILE_DELETE_AFTER_RETAIN = "HINDSIGHT_API_FILE_DELETE_AFTER_RETAIN"
# Temporal extraction — dateparser-based query analysis for date-aware recall.
# Adds ~120ms per recall. Disable to reduce recall latency when temporal
# filtering is not needed.
ENV_ENABLE_TEMPORAL_EXTRACTION = "HINDSIGHT_API_ENABLE_TEMPORAL_EXTRACTION"
# Graph retrieval — entity/link-based graph traversal during recall.
# Disable to reduce recall latency when only semantic + BM25 retrieval is needed
# (e.g. pure RAG / chunks mode).
ENV_ENABLE_GRAPH_RETRIEVAL = "HINDSIGHT_API_ENABLE_GRAPH_RETRIEVAL"
# Reranking — cross-encoder reranking of candidates during recall.
# Disable to skip the cross-encoder scoring step and use RRF-merged scores
# directly. Significantly reduces recall latency on large banks.
ENV_ENABLE_RERANKING = "HINDSIGHT_API_ENABLE_RERANKING"
# Observations settings (consolidated knowledge from facts)
ENV_ENABLE_OBSERVATIONS = "HINDSIGHT_API_ENABLE_OBSERVATIONS"
ENV_CONSOLIDATION_BATCH_SIZE = "HINDSIGHT_API_CONSOLIDATION_BATCH_SIZE"
@@ -383,6 +398,17 @@ ENV_REFLECT_WALL_TIMEOUT = "HINDSIGHT_API_REFLECT_WALL_TIMEOUT"
ENV_REFLECT_MISSION = "HINDSIGHT_API_REFLECT_MISSION"
ENV_REFLECT_SOURCE_FACTS_MAX_TOKENS = "HINDSIGHT_API_REFLECT_SOURCE_FACTS_MAX_TOKENS"
# Recall budget mapping (budget enum -> thinking_budget integer)
ENV_RECALL_BUDGET_FUNCTION = "HINDSIGHT_API_RECALL_BUDGET_FUNCTION"
ENV_RECALL_BUDGET_FIXED_LOW = "HINDSIGHT_API_RECALL_BUDGET_FIXED_LOW"
ENV_RECALL_BUDGET_FIXED_MID = "HINDSIGHT_API_RECALL_BUDGET_FIXED_MID"
ENV_RECALL_BUDGET_FIXED_HIGH = "HINDSIGHT_API_RECALL_BUDGET_FIXED_HIGH"
ENV_RECALL_BUDGET_ADAPTIVE_LOW = "HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_LOW"
ENV_RECALL_BUDGET_ADAPTIVE_MID = "HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_MID"
ENV_RECALL_BUDGET_ADAPTIVE_HIGH = "HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_HIGH"
ENV_RECALL_BUDGET_MIN = "HINDSIGHT_API_RECALL_BUDGET_MIN"
ENV_RECALL_BUDGET_MAX = "HINDSIGHT_API_RECALL_BUDGET_MAX"
# Audit log settings
ENV_AUDIT_LOG_ENABLED = "HINDSIGHT_API_AUDIT_LOG_ENABLED"
ENV_AUDIT_LOG_ACTIONS = "HINDSIGHT_API_AUDIT_LOG_ACTIONS"
@@ -540,6 +566,10 @@ DEFAULT_ENABLE_FILE_UPLOAD_API = True # Enable file upload endpoint
DEFAULT_FILE_DELETE_AFTER_RETAIN = True # Delete file bytes after retain (saves storage)
# Observations defaults (consolidated knowledge from facts)
DEFAULT_ENABLE_TEMPORAL_EXTRACTION = True # Temporal extraction enabled by default
DEFAULT_ENABLE_GRAPH_RETRIEVAL = True # Graph retrieval enabled by default
DEFAULT_ENABLE_RERANKING = True # Cross-encoder reranking enabled by default
DEFAULT_ENABLE_OBSERVATIONS = True # Observations enabled by default
DEFAULT_ENABLE_OBSERVATION_HISTORY = True # Observation history tracking enabled by default
DEFAULT_ENABLE_MENTAL_MODEL_HISTORY = True # Mental model history tracking enabled by default
@@ -580,6 +610,22 @@ DEFAULT_REFLECT_MAX_CONTEXT_TOKENS = 100_000 # Max accumulated context tokens b
DEFAULT_REFLECT_WALL_TIMEOUT = 300 # Wall-clock timeout in seconds for the entire reflect operation (5 minutes)
DEFAULT_REFLECT_SOURCE_FACTS_MAX_TOKENS = -1 # Token budget for source facts in search_observations (-1 = disabled)
# Recall budget mapping
# "fixed": thinking_budget = recall_budget_fixed_<level> (preserves legacy behavior)
# "adaptive": thinking_budget = round(max_tokens * recall_budget_adaptive_<level>),
# clamped to [recall_budget_min, recall_budget_max]
RECALL_BUDGET_FUNCTIONS = ("fixed", "adaptive")
DEFAULT_RECALL_BUDGET_FUNCTION = "fixed"
DEFAULT_RECALL_BUDGET_FIXED_LOW = 100
DEFAULT_RECALL_BUDGET_FIXED_MID = 300
DEFAULT_RECALL_BUDGET_FIXED_HIGH = 1000
# Adaptive defaults chosen to roughly match fixed defaults at max_tokens=4096
DEFAULT_RECALL_BUDGET_ADAPTIVE_LOW = 0.025
DEFAULT_RECALL_BUDGET_ADAPTIVE_MID = 0.075
DEFAULT_RECALL_BUDGET_ADAPTIVE_HIGH = 0.25
DEFAULT_RECALL_BUDGET_MIN = 20 # Floor for the adaptive function
DEFAULT_RECALL_BUDGET_MAX = 2000 # Ceiling for the adaptive function
# Disposition defaults (None = not set, fall back to bank DB value or 3)
DEFAULT_DISPOSITION_SKEPTICISM = None
DEFAULT_DISPOSITION_LITERALISM = None
@@ -673,6 +719,18 @@ def _validate_extraction_mode(mode: str) -> str:
return mode_lower
def _validate_recall_budget_function(function: str) -> str:
"""Validate and normalize recall budget function."""
function_lower = function.lower()
if function_lower not in RECALL_BUDGET_FUNCTIONS:
logger.warning(
f"Invalid recall budget function '{function}', must be one of {RECALL_BUDGET_FUNCTIONS}. "
f"Defaulting to '{DEFAULT_RECALL_BUDGET_FUNCTION}'."
)
return DEFAULT_RECALL_BUDGET_FUNCTION
return function_lower
def _get_default_model_for_provider(provider: str) -> str:
"""Get the default model for a given provider."""
return PROVIDER_DEFAULT_MODELS.get(provider.lower(), DEFAULT_LLM_MODEL)
@@ -892,6 +950,9 @@ class HindsightConfig:
file_delete_after_retain: bool
# Observations settings (consolidated knowledge from facts)
enable_temporal_extraction: bool
enable_graph_retrieval: bool
enable_reranking: bool
enable_observations: bool
enable_observation_history: bool
enable_mental_model_history: bool
@@ -914,6 +975,25 @@ class HindsightConfig:
reflect_mission: str | None
reflect_source_facts_max_tokens: int
# Recall settings (used by internal recall, e.g. during mental model refresh)
recall_include_chunks: bool
recall_max_tokens: int
recall_chunks_max_tokens: int
# Recall budget mapping: how the Budget enum (LOW/MID/HIGH) maps to thinking_budget integer.
# function="fixed": use the recall_budget_fixed_* values directly (legacy behavior).
# function="adaptive": compute round(max_tokens * recall_budget_adaptive_*),
# clamped to [recall_budget_min, recall_budget_max].
recall_budget_function: str
recall_budget_fixed_low: int
recall_budget_fixed_mid: int
recall_budget_fixed_high: int
recall_budget_adaptive_low: float
recall_budget_adaptive_mid: float
recall_budget_adaptive_high: float
recall_budget_min: int
recall_budget_max: int
# Disposition settings (hierarchical - can be overridden per bank; None = fall back to DB)
disposition_skepticism: int | None
disposition_literalism: int | None
@@ -1016,6 +1096,10 @@ class HindsightConfig:
# Entity labels (controlled vocabulary for entity classification)
"entity_labels",
"entities_allow_free_form",
# RAG mode — per-bank retrieval pipeline control
"enable_temporal_extraction",
"enable_graph_retrieval",
"enable_reranking",
# Consolidation settings
"enable_observations",
"consolidation_llm_batch_size",
@@ -1026,6 +1110,20 @@ class HindsightConfig:
# Reflect settings
"reflect_mission",
"reflect_source_facts_max_tokens",
# Recall settings (used by internal recall, e.g. mental model refresh)
"recall_include_chunks",
"recall_max_tokens",
"recall_chunks_max_tokens",
# Recall budget mapping (Budget enum -> thinking_budget integer)
"recall_budget_function",
"recall_budget_fixed_low",
"recall_budget_fixed_mid",
"recall_budget_fixed_high",
"recall_budget_adaptive_low",
"recall_budget_adaptive_mid",
"recall_budget_adaptive_high",
"recall_budget_min",
"recall_budget_max",
# Disposition settings
"disposition_skepticism",
"disposition_literalism",
@@ -1443,6 +1541,17 @@ class HindsightConfig:
ENV_FILE_DELETE_AFTER_RETAIN, str(DEFAULT_FILE_DELETE_AFTER_RETAIN)
).lower()
== "true",
# Temporal extraction (dateparser query analysis for date-aware recall)
enable_temporal_extraction=os.getenv(
ENV_ENABLE_TEMPORAL_EXTRACTION, str(DEFAULT_ENABLE_TEMPORAL_EXTRACTION)
).lower()
== "true",
# Graph retrieval (entity/link traversal during recall)
enable_graph_retrieval=os.getenv(ENV_ENABLE_GRAPH_RETRIEVAL, str(DEFAULT_ENABLE_GRAPH_RETRIEVAL)).lower()
== "true",
# Reranking (cross-encoder scoring during recall)
enable_reranking=os.getenv(ENV_ENABLE_RERANKING, str(DEFAULT_ENABLE_RERANKING)).lower()
== "true",
# Observations settings (consolidated knowledge from facts)
enable_observations=os.getenv(ENV_ENABLE_OBSERVATIONS, str(DEFAULT_ENABLE_OBSERVATIONS)).lower() == "true",
enable_observation_history=os.getenv(
@@ -1505,6 +1614,31 @@ class HindsightConfig:
reflect_source_facts_max_tokens=int(
os.getenv(ENV_REFLECT_SOURCE_FACTS_MAX_TOKENS, str(DEFAULT_REFLECT_SOURCE_FACTS_MAX_TOKENS))
),
recall_include_chunks=os.getenv(ENV_RECALL_INCLUDE_CHUNKS, str(DEFAULT_RECALL_INCLUDE_CHUNKS)).lower()
in ("true", "1", "yes"),
recall_max_tokens=int(os.getenv(ENV_RECALL_MAX_TOKENS, str(DEFAULT_RECALL_MAX_TOKENS))),
recall_chunks_max_tokens=int(
os.getenv(ENV_RECALL_CHUNKS_MAX_TOKENS, str(DEFAULT_RECALL_CHUNKS_MAX_TOKENS))
),
recall_budget_function=_validate_recall_budget_function(
os.getenv(ENV_RECALL_BUDGET_FUNCTION, DEFAULT_RECALL_BUDGET_FUNCTION)
),
recall_budget_fixed_low=int(os.getenv(ENV_RECALL_BUDGET_FIXED_LOW, str(DEFAULT_RECALL_BUDGET_FIXED_LOW))),
recall_budget_fixed_mid=int(os.getenv(ENV_RECALL_BUDGET_FIXED_MID, str(DEFAULT_RECALL_BUDGET_FIXED_MID))),
recall_budget_fixed_high=int(
os.getenv(ENV_RECALL_BUDGET_FIXED_HIGH, str(DEFAULT_RECALL_BUDGET_FIXED_HIGH))
),
recall_budget_adaptive_low=float(
os.getenv(ENV_RECALL_BUDGET_ADAPTIVE_LOW, str(DEFAULT_RECALL_BUDGET_ADAPTIVE_LOW))
),
recall_budget_adaptive_mid=float(
os.getenv(ENV_RECALL_BUDGET_ADAPTIVE_MID, str(DEFAULT_RECALL_BUDGET_ADAPTIVE_MID))
),
recall_budget_adaptive_high=float(
os.getenv(ENV_RECALL_BUDGET_ADAPTIVE_HIGH, str(DEFAULT_RECALL_BUDGET_ADAPTIVE_HIGH))
),
recall_budget_min=int(os.getenv(ENV_RECALL_BUDGET_MIN, str(DEFAULT_RECALL_BUDGET_MIN))),
recall_budget_max=int(os.getenv(ENV_RECALL_BUDGET_MAX, str(DEFAULT_RECALL_BUDGET_MAX))),
# Disposition settings (None = fall back to DB value)
disposition_skepticism=int(os.getenv(ENV_DISPOSITION_SKEPTICISM))
if os.getenv(ENV_DISPOSITION_SKEPTICISM)
@@ -15,7 +15,12 @@ from typing import Any
import asyncpg
from hindsight_api.config import HindsightConfig, _get_raw_config, normalize_config_dict
from hindsight_api.config import (
RECALL_BUDGET_FUNCTIONS,
HindsightConfig,
_get_raw_config,
normalize_config_dict,
)
from hindsight_api.engine.memory_engine import fq_table
from hindsight_api.extensions.tenant import TenantExtension
from hindsight_api.models import RequestContext
@@ -256,6 +261,9 @@ class ConfigResolver:
"Strategy names must not be empty strings. Remove entries with empty names before saving."
)
# Validate recall budget fields
_validate_recall_budget_updates(normalized_updates)
# Merge with existing config (JSONB || operator)
async with self.pool.acquire() as conn:
await conn.execute(
@@ -292,6 +300,53 @@ class ConfigResolver:
logger.info(f"Reset bank config for {bank_id} to defaults")
_RECALL_BUDGET_FIXED_KEYS = (
"recall_budget_fixed_low",
"recall_budget_fixed_mid",
"recall_budget_fixed_high",
)
_RECALL_BUDGET_ADAPTIVE_KEYS = (
"recall_budget_adaptive_low",
"recall_budget_adaptive_mid",
"recall_budget_adaptive_high",
)
def _validate_recall_budget_updates(updates: dict[str, Any]) -> None:
"""Validate recall budget config updates. Raises ValueError on invalid input."""
if "recall_budget_function" in updates:
function = updates["recall_budget_function"]
if not isinstance(function, str) or function.lower() not in RECALL_BUDGET_FUNCTIONS:
raise ValueError(
f"recall_budget_function must be one of {sorted(RECALL_BUDGET_FUNCTIONS)}, got {function!r}"
)
for key in _RECALL_BUDGET_FIXED_KEYS:
if key in updates:
value = updates[key]
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
raise ValueError(f"{key} must be a positive integer, got {value!r}")
for key in _RECALL_BUDGET_ADAPTIVE_KEYS:
if key in updates:
value = updates[key]
if isinstance(value, bool) or not isinstance(value, (int, float)) or value <= 0:
raise ValueError(f"{key} must be a positive number, got {value!r}")
for key in ("recall_budget_min", "recall_budget_max"):
if key in updates:
value = updates[key]
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
raise ValueError(f"{key} must be a positive integer, got {value!r}")
if "recall_budget_min" in updates and "recall_budget_max" in updates:
if updates["recall_budget_min"] > updates["recall_budget_max"]:
raise ValueError(
f"recall_budget_min ({updates['recall_budget_min']}) must be <= "
f"recall_budget_max ({updates['recall_budget_max']})"
)
def apply_strategy(config: HindsightConfig, strategy_name: str) -> HindsightConfig:
"""
Apply a named retain strategy's overrides on top of a resolved config.
@@ -205,6 +205,39 @@ class Budget(str, Enum):
HIGH = "high"
def _resolve_thinking_budget(config_dict: dict, budget: "Budget | None", max_tokens: int) -> int:
"""
Map a Budget enum level to the integer thinking_budget passed to retrieval.
Reads the bank-resolved config to decide between two functions:
- "fixed": returns recall_budget_fixed_<level> directly (legacy default).
- "adaptive": returns round(max_tokens * recall_budget_adaptive_<level>),
clamped to [recall_budget_min, recall_budget_max].
A None budget falls back to MID (preserves legacy default).
"""
effective_budget = budget if budget is not None else Budget.MID
function = config_dict.get("recall_budget_function", "fixed")
if function == "adaptive":
ratios = {
Budget.LOW: config_dict.get("recall_budget_adaptive_low", 0.025),
Budget.MID: config_dict.get("recall_budget_adaptive_mid", 0.075),
Budget.HIGH: config_dict.get("recall_budget_adaptive_high", 0.25),
}
raw = round(max_tokens * float(ratios[effective_budget]))
floor = int(config_dict.get("recall_budget_min", 20))
ceiling = int(config_dict.get("recall_budget_max", 2000))
return max(floor, min(ceiling, raw))
fixed = {
Budget.LOW: config_dict.get("recall_budget_fixed_low", 100),
Budget.MID: config_dict.get("recall_budget_fixed_mid", 300),
Budget.HIGH: config_dict.get("recall_budget_fixed_high", 1000),
}
return int(fixed[effective_budget])
def utcnow():
"""Get current UTC time with timezone info."""
return datetime.now(UTC)
@@ -2520,10 +2553,11 @@ class MemoryEngine(MemoryEngineInterface):
if result.tag_groups is not None:
tag_groups = result.tag_groups
# Map budget enum to thinking_budget number (default to MID if None)
budget_mapping = {Budget.LOW: 100, Budget.MID: 300, Budget.HIGH: 1000}
effective_budget = budget if budget is not None else Budget.MID
thinking_budget = budget_mapping[effective_budget]
# Map budget enum to thinking_budget number using bank-resolved config.
# Function "fixed" preserves legacy {LOW: 100, MID: 300, HIGH: 1000}; function "adaptive"
# derives from max_tokens and clamps to [recall_budget_min, recall_budget_max].
budget_config_dict = await self._config_resolver.get_bank_config(bank_id, request_context)
thinking_budget = _resolve_thinking_budget(budget_config_dict, budget, max_tokens)
# Log recall start with tags if present (skip if quiet mode for internal operations)
if not _quiet:
@@ -2797,7 +2831,8 @@ class MemoryEngine(MemoryEngineInterface):
try:
# Run optimized retrieval with connection budget
config = get_config()
# Resolve bank-specific config (supports per-bank RAG mode)
config = await self._config_resolver.resolve_full_config(bank_id, request_context)
effective_connection_budget = (
connection_budget if connection_budget is not None else config.recall_connection_budget
)
@@ -2807,6 +2842,12 @@ class MemoryEngine(MemoryEngineInterface):
) as op:
budgeted_pool = op.wrap_pool(pool)
parallel_start = time.time()
# Pass False to skip temporal extraction entirely when
# disabled via config. This avoids the ~120ms dateparser
# overhead per recall.
analyzer = self.query_analyzer if config.enable_temporal_extraction else False
# Pass False to skip graph retrieval when disabled via config.
graph = None if config.enable_graph_retrieval else False
multi_result = await retrieve_all_fact_types_parallel(
budgeted_pool,
query,
@@ -2815,7 +2856,8 @@ class MemoryEngine(MemoryEngineInterface):
fact_type, # Pass all fact types at once
thinking_budget,
question_date,
self.query_analyzer,
analyzer,
graph_retriever=graph,
tags=tags,
tags_match=tags_match,
tag_groups=tag_groups,
@@ -3049,47 +3091,62 @@ class MemoryEngine(MemoryEngineInterface):
# Step 4: Rerank using cross-encoder (MergedCandidate -> ScoredResult)
step_start = time.time()
reranker_instance = self._cross_encoder_reranker
rerank_span = tracer_otel.start_span("hindsight.recall_rerank")
rerank_span.set_attribute("hindsight.bank_id", bank_id)
rerank_span.set_attribute("hindsight.candidates_count", len(merged_candidates))
scored_results: list = []
pre_filtered_count = 0
try:
# Ensure reranker is initialized (for lazy initialization mode)
await reranker_instance.ensure_initialized()
# Pre-filter candidates to reduce reranking cost (RRF already provides good ranking)
# This is especially important for remote rerankers with network latency
reranker_max_candidates = get_config().reranker_max_candidates
if len(merged_candidates) > reranker_max_candidates:
# Sort by RRF score and take top candidates
merged_candidates.sort(key=lambda mc: mc.rrf_score, reverse=True)
pre_filtered_count = len(merged_candidates) - reranker_max_candidates
merged_candidates = merged_candidates[:reranker_max_candidates]
if config.enable_reranking:
reranker_instance = self._cross_encoder_reranker
# Rerank using cross-encoder
scored_results = await reranker_instance.rerank(query, merged_candidates)
rerank_span = tracer_otel.start_span("hindsight.recall_rerank")
rerank_span.set_attribute("hindsight.bank_id", bank_id)
rerank_span.set_attribute("hindsight.candidates_count", len(merged_candidates))
try:
# Ensure reranker is initialized (for lazy initialization mode)
await reranker_instance.ensure_initialized()
# Pre-filter candidates to reduce reranking cost (RRF already provides good ranking)
# This is especially important for remote rerankers with network latency
reranker_max_candidates = get_config().reranker_max_candidates
if len(merged_candidates) > reranker_max_candidates:
# Sort by RRF score and take top candidates
merged_candidates.sort(key=lambda mc: mc.rrf_score, reverse=True)
pre_filtered_count = len(merged_candidates) - reranker_max_candidates
merged_candidates = merged_candidates[:reranker_max_candidates]
# Rerank using cross-encoder
scored_results = await reranker_instance.rerank(query, merged_candidates)
step_duration = time.time() - step_start
pre_filter_note = f" (pre-filtered {pre_filtered_count})" if pre_filtered_count > 0 else ""
log_buffer.append(
f" [4] Reranking: {len(scored_results)} candidates scored in {step_duration:.3f}s{pre_filter_note}"
)
finally:
rerank_span.set_attribute("hindsight.scored_count", len(scored_results))
if pre_filtered_count > 0:
rerank_span.set_attribute("hindsight.pre_filtered_count", pre_filtered_count)
rerank_span.end()
# Step 4.5: Combine cross-encoder score with retrieval signals via multiplicative boosts.
# See apply_combined_scoring for the full rationale and formula.
if scored_results:
apply_combined_scoring(scored_results, now=utcnow())
scored_results.sort(key=lambda x: x.weight, reverse=True)
log_buffer.append(" [4.6] Combined scoring: ce * recency_boost(0.2) * temporal_boost(0.2)")
else:
# Reranking disabled — use RRF scores directly
from .search.types import ScoredResult
merged_candidates.sort(key=lambda mc: mc.rrf_score, reverse=True)
scored_results = [
ScoredResult(candidate=mc, weight=mc.rrf_score)
for mc in merged_candidates
]
step_duration = time.time() - step_start
pre_filter_note = f" (pre-filtered {pre_filtered_count})" if pre_filtered_count > 0 else ""
log_buffer.append(
f" [4] Reranking: {len(scored_results)} candidates scored in {step_duration:.3f}s{pre_filter_note}"
f" [4] Reranking: skipped ({len(scored_results)} candidates ranked by RRF in {step_duration:.3f}s)"
)
finally:
rerank_span.set_attribute("hindsight.scored_count", len(scored_results))
if pre_filtered_count > 0:
rerank_span.set_attribute("hindsight.pre_filtered_count", pre_filtered_count)
rerank_span.end()
# Step 4.5: Combine cross-encoder score with retrieval signals via multiplicative boosts.
# See apply_combined_scoring for the full rationale and formula.
if scored_results:
apply_combined_scoring(scored_results, now=utcnow())
scored_results.sort(key=lambda x: x.weight, reverse=True)
log_buffer.append(" [4.6] Combined scoring: ce * recency_boost(0.2) * temporal_boost(0.2)")
# Add reranked results to tracer AFTER combined scoring (so normalized values are included)
if tracer:
@@ -561,16 +561,23 @@ async def retrieve_all_fact_types_parallel(
"""
import time
retriever = graph_retriever or get_default_graph_retriever()
skip_graph = graph_retriever is False
retriever = None if skip_graph else (graph_retriever or get_default_graph_retriever())
start_time = time.time()
timings: dict[str, float] = {}
# Step 1: Extract temporal constraint first (CPU work, no DB)
# Do this before DB queries so we know if we need temporal retrieval
# Do this before DB queries so we know if we need temporal retrieval.
# Skip entirely when query_analyzer is False (temporal extraction disabled
# via HINDSIGHT_API_ENABLE_TEMPORAL_EXTRACTION=false). This saves ~120ms
# per recall by avoiding the dateparser library.
temporal_extraction_start = time.time()
from .temporal_extraction import extract_temporal_constraint
if query_analyzer is not False:
from .temporal_extraction import extract_temporal_constraint
temporal_constraint = extract_temporal_constraint(query_text, reference_date=question_date, analyzer=query_analyzer)
temporal_constraint = extract_temporal_constraint(query_text, reference_date=question_date, analyzer=query_analyzer)
else:
temporal_constraint = None
temporal_extraction_time = time.time() - temporal_extraction_start
timings["temporal_extraction"] = temporal_extraction_time
@@ -639,9 +646,12 @@ async def retrieve_all_fact_types_parallel(
)
return ft, results, time.time() - graph_start, graph_timing
# Run graph for all fact types in parallel
graph_tasks = [run_graph_for_fact_type(ft) for ft in fact_types]
graph_results_list = await asyncio.gather(*graph_tasks)
# Run graph for all fact types in parallel (skip when disabled)
if skip_graph:
graph_results_list = []
else:
graph_tasks = [run_graph_for_fact_type(ft) for ft in fact_types]
graph_results_list = await asyncio.gather(*graph_tasks)
# Organize results by fact type
results_by_fact_type: dict[str, ParallelRetrievalResult] = {}
@@ -0,0 +1,115 @@
"""Verify that BankTemplateConfig exposes every hierarchical field that
_CONFIGURABLE_FIELDS already accepts at the engine layer.
This test guards the fix for the gap described in the upstream PR title
"fix(bank-template): align BankTemplateConfig with _CONFIGURABLE_FIELDS".
Each new field is POSTed through /v1/default/banks/{id}/import and then
read back via the bank-config endpoint; assertion is that the applied
value round-trips through the engine.
Runs via: uv run pytest tests/test_bank_template_configurable_fields.py -v
The api_client fixture (shared with tests/test_bank_templates.py) wraps
create_app(memory, initialize_memory=False) in an httpx.ASGITransport
with base_url http://test — in-process, no network, no tenant extension.
Copy the fixture inline here so the test file does not depend on a
conftest we do not ship in the patch.
"""
from __future__ import annotations
from datetime import datetime
import httpx
import pytest
import pytest_asyncio
from hindsight_api.api import create_app
from hindsight_api.api.http import BankTemplateConfig
# Each tuple is (field_name, applied_value). Values chosen to differ
# visibly from defaults so round-trip bugs surface.
NEW_FIELDS: list[tuple[str, object]] = [
("retain_default_strategy", "strategy-a"),
("retain_strategies", {"strategy-a": {"mode": "concise", "max_tokens": 512}}),
("retain_chunk_batch_size", 7),
("mcp_enabled_tools", ["list_banks", "get_bank_profile"]),
("consolidation_llm_batch_size", 11),
("consolidation_source_facts_max_tokens", 2048),
("consolidation_source_facts_max_tokens_per_observation", 256),
("max_observations_per_scope", 13),
("reflect_source_facts_max_tokens", 4096),
("llm_gemini_safety_settings", [{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"}]),
("recall_budget_function", "adaptive"),
("recall_budget_fixed_low", 50),
("recall_budget_fixed_mid", 250),
("recall_budget_fixed_high", 800),
("recall_budget_adaptive_low", 0.05),
("recall_budget_adaptive_mid", 0.1),
("recall_budget_adaptive_high", 0.4),
("recall_budget_min", 30),
("recall_budget_max", 1500),
]
@pytest_asyncio.fixture
async def api_client(memory):
"""Matches the fixture in tests/test_bank_templates.py — in-process
ASGI test client, no tenant extension, no auth."""
app = create_app(memory, initialize_memory=False)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
yield client
@pytest.fixture
def bank_id():
return f"tmpl_config_{datetime.now().timestamp()}"
def test_bank_template_config_declares_every_configurable_field():
"""Pydantic-level guard: every field in NEW_FIELDS must be a declared
attribute of BankTemplateConfig so get_config_updates() picks it up."""
declared = set(BankTemplateConfig.model_fields.keys())
missing = [name for name, _ in NEW_FIELDS if name not in declared]
assert not missing, f"BankTemplateConfig missing fields: {missing}"
@pytest.mark.asyncio
@pytest.mark.parametrize("field_name,applied_value", NEW_FIELDS, ids=[n for n, _ in NEW_FIELDS])
async def test_new_field_round_trips_through_import(
api_client: httpx.AsyncClient,
bank_id: str,
field_name: str,
applied_value: object,
):
"""POST a minimal manifest with one new field set, then read bank
config back and assert the value made it through.
Bank config response shape per upstream's test_import_applies_config:
top-level keys are resolved hierarchical config; per-bank overrides
live under config["overrides"][<field>]. Assert on the override slot.
"""
unique_bank_id = f"{bank_id}_{field_name}"
manifest = {
"version": "1",
"bank": {field_name: applied_value},
}
resp = await api_client.post(
f"/v1/default/banks/{unique_bank_id}/import",
json=manifest,
)
assert resp.status_code == 200, resp.text
# Read bank config back — field must reflect the applied value
# under the "overrides" slot, matching upstream's own test shape.
read = await api_client.get(f"/v1/default/banks/{unique_bank_id}/config")
assert read.status_code == 200, read.text
config = read.json()
overrides = config.get("overrides", {})
assert overrides.get(field_name) == applied_value, (
f"round-trip mismatch for {field_name}: "
f"sent {applied_value!r}, got {overrides.get(field_name)!r} "
f"(full overrides: {overrides!r})"
)
@@ -96,9 +96,12 @@ async def test_hierarchical_fields_categorization():
assert "llm_gemini_safety_settings" in configurable
assert "mcp_enabled_tools" in configurable
assert "retain_chunk_batch_size" in configurable
assert "enable_temporal_extraction" in configurable
assert "enable_graph_retrieval" in configurable
assert "enable_reranking" in configurable
# Verify count is correct
assert len(configurable) == 22
assert len(configurable) == 34
# Verify credential fields (NEVER exposed)
assert "llm_api_key" in credentials
@@ -411,7 +414,7 @@ async def test_config_get_bank_config_no_static_or_credential_fields_leak(memory
assert field in config, f"Expected configurable field '{field}' missing from config"
# Should have a small number of configurable fields (not hundreds)
assert len(config) < 25, f"Too many fields returned: {len(config)}"
assert len(config) < 50, f"Too many fields returned: {len(config)}"
finally:
await memory.delete_bank(bank_id, request_context=request_context)
@@ -0,0 +1,254 @@
"""
Tests for the configurable recall-budget mapping (Budget enum -> thinking_budget int).
Two functions are supported:
- "fixed": returns the recall_budget_fixed_<level> integer directly (legacy default).
- "adaptive": returns round(max_tokens * recall_budget_adaptive_<level>),
clamped to [recall_budget_min, recall_budget_max].
Both the function selector and the per-level numbers are hierarchical config
fields (global env -> tenant -> bank), so they can be overridden per bank.
"""
import dataclasses
import pytest
from hindsight_api.config import (
DEFAULT_RECALL_BUDGET_ADAPTIVE_HIGH,
DEFAULT_RECALL_BUDGET_ADAPTIVE_LOW,
DEFAULT_RECALL_BUDGET_ADAPTIVE_MID,
DEFAULT_RECALL_BUDGET_FIXED_HIGH,
DEFAULT_RECALL_BUDGET_FIXED_LOW,
DEFAULT_RECALL_BUDGET_FIXED_MID,
DEFAULT_RECALL_BUDGET_MAX,
DEFAULT_RECALL_BUDGET_MIN,
DEFAULT_RECALL_BUDGET_FUNCTION,
ENV_RECALL_BUDGET_ADAPTIVE_LOW,
ENV_RECALL_BUDGET_ADAPTIVE_MID,
ENV_RECALL_BUDGET_FIXED_HIGH,
ENV_RECALL_BUDGET_FIXED_LOW,
ENV_RECALL_BUDGET_FIXED_MID,
ENV_RECALL_BUDGET_MAX,
ENV_RECALL_BUDGET_MIN,
ENV_RECALL_BUDGET_FUNCTION,
RECALL_BUDGET_FUNCTIONS,
HindsightConfig,
)
from hindsight_api.config_resolver import _validate_recall_budget_updates
from hindsight_api.engine.memory_engine import Budget, _resolve_thinking_budget
_BUDGET_FIELD_NAMES = (
"recall_budget_function",
"recall_budget_fixed_low",
"recall_budget_fixed_mid",
"recall_budget_fixed_high",
"recall_budget_adaptive_low",
"recall_budget_adaptive_mid",
"recall_budget_adaptive_high",
"recall_budget_min",
"recall_budget_max",
)
class TestBudgetConfigFields:
def test_fields_exist_on_dataclass(self):
names = {f.name for f in dataclasses.fields(HindsightConfig)}
for field_name in _BUDGET_FIELD_NAMES:
assert field_name in names, f"Missing dataclass field: {field_name}"
def test_fields_are_configurable(self):
configurable = HindsightConfig.get_configurable_fields()
for field_name in _BUDGET_FIELD_NAMES:
assert field_name in configurable, f"Field not in _CONFIGURABLE_FIELDS: {field_name}"
def test_default_function_is_fixed_for_backwards_compat(self):
# The whole point of function="fixed" being default is to preserve legacy behavior.
assert DEFAULT_RECALL_BUDGET_FUNCTION == "fixed"
assert "fixed" in RECALL_BUDGET_FUNCTIONS
assert "adaptive" in RECALL_BUDGET_FUNCTIONS
def test_default_fixed_values_match_legacy_hardcoded_mapping(self):
# These are the values that used to live in the hardcoded budget_mapping dict.
assert DEFAULT_RECALL_BUDGET_FIXED_LOW == 100
assert DEFAULT_RECALL_BUDGET_FIXED_MID == 300
assert DEFAULT_RECALL_BUDGET_FIXED_HIGH == 1000
def test_default_adaptive_clamps_are_sane(self):
assert DEFAULT_RECALL_BUDGET_MIN >= 1
assert DEFAULT_RECALL_BUDGET_MAX > DEFAULT_RECALL_BUDGET_MIN
def test_env_var_constants(self):
assert ENV_RECALL_BUDGET_FUNCTION == "HINDSIGHT_API_RECALL_BUDGET_FUNCTION"
assert ENV_RECALL_BUDGET_FIXED_LOW == "HINDSIGHT_API_RECALL_BUDGET_FIXED_LOW"
assert ENV_RECALL_BUDGET_FIXED_MID == "HINDSIGHT_API_RECALL_BUDGET_FIXED_MID"
assert ENV_RECALL_BUDGET_FIXED_HIGH == "HINDSIGHT_API_RECALL_BUDGET_FIXED_HIGH"
assert ENV_RECALL_BUDGET_ADAPTIVE_LOW == "HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_LOW"
assert ENV_RECALL_BUDGET_ADAPTIVE_MID == "HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_MID"
assert ENV_RECALL_BUDGET_MIN == "HINDSIGHT_API_RECALL_BUDGET_MIN"
assert ENV_RECALL_BUDGET_MAX == "HINDSIGHT_API_RECALL_BUDGET_MAX"
def test_from_env_reads_overrides(self, monkeypatch):
monkeypatch.setenv(ENV_RECALL_BUDGET_FUNCTION, "adaptive")
monkeypatch.setenv(ENV_RECALL_BUDGET_FIXED_MID, "777")
monkeypatch.setenv(ENV_RECALL_BUDGET_ADAPTIVE_MID, "0.5")
monkeypatch.setenv(ENV_RECALL_BUDGET_MIN, "5")
monkeypatch.setenv(ENV_RECALL_BUDGET_MAX, "9999")
config = HindsightConfig.from_env()
assert config.recall_budget_function == "adaptive"
assert config.recall_budget_fixed_mid == 777
assert config.recall_budget_adaptive_mid == 0.5
assert config.recall_budget_min == 5
assert config.recall_budget_max == 9999
def test_from_env_invalid_function_falls_back_to_default(self, monkeypatch):
# Defensive parsing: an invalid env value logs a warning and falls back.
monkeypatch.setenv(ENV_RECALL_BUDGET_FUNCTION, "garbage")
config = HindsightConfig.from_env()
assert config.recall_budget_function == DEFAULT_RECALL_BUDGET_FUNCTION
class TestResolveThinkingBudgetFixedFunction:
@pytest.fixture
def fixed_config(self):
return {
"recall_budget_function": "fixed",
"recall_budget_fixed_low": 100,
"recall_budget_fixed_mid": 300,
"recall_budget_fixed_high": 1000,
"recall_budget_adaptive_low": 0.025,
"recall_budget_adaptive_mid": 0.075,
"recall_budget_adaptive_high": 0.25,
"recall_budget_min": 20,
"recall_budget_max": 2000,
}
def test_low_mid_high_match_fixed_values(self, fixed_config):
assert _resolve_thinking_budget(fixed_config, Budget.LOW, 4096) == 100
assert _resolve_thinking_budget(fixed_config, Budget.MID, 4096) == 300
assert _resolve_thinking_budget(fixed_config, Budget.HIGH, 4096) == 1000
def test_none_budget_defaults_to_mid(self, fixed_config):
assert _resolve_thinking_budget(fixed_config, None, 4096) == 300
def test_max_tokens_does_not_affect_fixed_function(self, fixed_config):
# Whole point of "fixed": result is independent of max_tokens.
assert _resolve_thinking_budget(fixed_config, Budget.MID, 1) == 300
assert _resolve_thinking_budget(fixed_config, Budget.MID, 1_000_000) == 300
def test_per_bank_overrides_take_effect(self, fixed_config):
fixed_config["recall_budget_fixed_mid"] = 42
assert _resolve_thinking_budget(fixed_config, Budget.MID, 4096) == 42
class TestResolveThinkingBudgetAdaptiveFunction:
@pytest.fixture
def adaptive_config(self):
return {
"recall_budget_function": "adaptive",
"recall_budget_fixed_low": 100,
"recall_budget_fixed_mid": 300,
"recall_budget_fixed_high": 1000,
"recall_budget_adaptive_low": 0.025,
"recall_budget_adaptive_mid": 0.075,
"recall_budget_adaptive_high": 0.25,
"recall_budget_min": 20,
"recall_budget_max": 2000,
}
def test_scales_with_max_tokens(self, adaptive_config):
# 4096 * 0.075 = 307.2 -> 307
assert _resolve_thinking_budget(adaptive_config, Budget.MID, 4096) == 307
# 8192 * 0.075 = 614.4 -> 614
assert _resolve_thinking_budget(adaptive_config, Budget.MID, 8192) == 614
def test_clamps_to_floor_when_max_tokens_tiny(self, adaptive_config):
# 100 * 0.025 = 2.5 -> 2 -> clamped to floor 20
assert _resolve_thinking_budget(adaptive_config, Budget.LOW, 100) == 20
def test_clamps_to_ceiling_when_max_tokens_huge(self, adaptive_config):
# 100_000 * 0.25 = 25_000 -> clamped to ceiling 2000
assert _resolve_thinking_budget(adaptive_config, Budget.HIGH, 100_000) == 2000
def test_none_budget_defaults_to_mid(self, adaptive_config):
assert _resolve_thinking_budget(adaptive_config, None, 4096) == 307
def test_custom_clamps_per_bank(self, adaptive_config):
adaptive_config["recall_budget_min"] = 500
adaptive_config["recall_budget_max"] = 600
# 4096 * 0.075 = 307 -> below floor 500
assert _resolve_thinking_budget(adaptive_config, Budget.MID, 4096) == 500
# 4096 * 0.25 = 1024 -> above ceiling 600
assert _resolve_thinking_budget(adaptive_config, Budget.HIGH, 4096) == 600
class TestResolveThinkingBudgetFallbacks:
def test_empty_config_uses_legacy_defaults(self):
# Resilience: missing keys should not crash; fallback to legacy mapping.
assert _resolve_thinking_budget({}, Budget.LOW, 4096) == 100
assert _resolve_thinking_budget({}, Budget.MID, 4096) == 300
assert _resolve_thinking_budget({}, Budget.HIGH, 4096) == 1000
def test_unknown_function_falls_back_to_fixed(self):
# Defensive: if some bad config slipped past validation, behave like "fixed".
assert _resolve_thinking_budget({"recall_budget_function": "garbage"}, Budget.MID, 4096) == 300
class TestValidateRecallBudgetUpdates:
def test_no_op_passes(self):
_validate_recall_budget_updates({})
_validate_recall_budget_updates({"unrelated_field": 123})
def test_valid_function_values(self):
_validate_recall_budget_updates({"recall_budget_function": "fixed"})
_validate_recall_budget_updates({"recall_budget_function": "adaptive"})
def test_invalid_function_raises(self):
with pytest.raises(ValueError, match="recall_budget_function"):
_validate_recall_budget_updates({"recall_budget_function": "wrong"})
with pytest.raises(ValueError, match="recall_budget_function"):
_validate_recall_budget_updates({"recall_budget_function": 123})
def test_fixed_must_be_positive_integer(self):
for key in ("recall_budget_fixed_low", "recall_budget_fixed_mid", "recall_budget_fixed_high"):
_validate_recall_budget_updates({key: 1})
_validate_recall_budget_updates({key: 100_000})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: 0})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: -5})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: 1.5}) # float not allowed for fixed
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: True}) # bool sneaks past int check
def test_adaptive_must_be_positive_number(self):
for key in ("recall_budget_adaptive_low", "recall_budget_adaptive_mid", "recall_budget_adaptive_high"):
_validate_recall_budget_updates({key: 0.001})
_validate_recall_budget_updates({key: 1.0})
_validate_recall_budget_updates({key: 5}) # int is acceptable as a number
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: 0})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: -0.1})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: True})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: "0.5"})
def test_min_must_be_le_max_when_both_set(self):
_validate_recall_budget_updates({"recall_budget_min": 10, "recall_budget_max": 1000})
_validate_recall_budget_updates({"recall_budget_min": 100, "recall_budget_max": 100})
with pytest.raises(ValueError, match="recall_budget_min"):
_validate_recall_budget_updates({"recall_budget_min": 5000, "recall_budget_max": 100})
def test_min_max_must_be_positive_integers(self):
for key in ("recall_budget_min", "recall_budget_max"):
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: 0})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: -1})
with pytest.raises(ValueError, match=key):
_validate_recall_budget_updates({key: 1.5})
@@ -0,0 +1,463 @@
"""
Tests for RAG mode config flags: temporal extraction and graph retrieval.
Verifies that:
1. Config flags default to True (full retrieval pipeline).
2. Disabling temporal extraction skips dateparser and temporal DB queries.
3. Disabling graph retrieval skips entity/link traversal.
4. Recall still returns relevant results with both disabled (2-way: semantic + BM25).
"""
import os
import time
from datetime import datetime, timezone
import pytest
import pytest_asyncio
from hindsight_api import LocalSTEmbeddings, MemoryEngine, RequestContext
from hindsight_api.engine.cross_encoder import LocalSTCrossEncoder
from hindsight_api.engine.memory_engine import Budget
from hindsight_api.engine.query_analyzer import DateparserQueryAnalyzer
from hindsight_api.engine.task_backend import SyncTaskBackend
from hindsight_api.config import (
ENV_ENABLE_TEMPORAL_EXTRACTION,
DEFAULT_ENABLE_TEMPORAL_EXTRACTION,
ENV_ENABLE_GRAPH_RETRIEVAL,
DEFAULT_ENABLE_GRAPH_RETRIEVAL,
clear_config_cache,
HindsightConfig,
)
# Env vars managed by the clean_env fixture
_MANAGED_ENV_VARS = [ENV_ENABLE_TEMPORAL_EXTRACTION, ENV_ENABLE_GRAPH_RETRIEVAL]
@pytest.fixture(autouse=True)
def clean_env():
"""Save and restore RAG mode env vars around each test."""
originals = {k: os.environ.get(k) for k in _MANAGED_ENV_VARS}
clear_config_cache()
yield
for k, v in originals.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
clear_config_cache()
@pytest_asyncio.fixture(scope="function")
async def none_memory(pg0_db_url, embeddings, cross_encoder, query_analyzer):
"""MemoryEngine with provider=none (chunks mode — no LLM needed for retain)."""
mem = MemoryEngine(
db_url=pg0_db_url,
memory_llm_provider="none",
memory_llm_api_key=None,
memory_llm_model="none",
embeddings=embeddings,
cross_encoder=cross_encoder,
query_analyzer=query_analyzer,
pool_min_size=1,
pool_max_size=5,
run_migrations=False,
task_backend=SyncTaskBackend(),
)
await mem.initialize()
yield mem
try:
if mem._pool and not mem._pool._closing:
await mem.close()
except Exception:
pass
# ---------------------------------------------------------------------------
# Config-level tests
# ---------------------------------------------------------------------------
def test_temporal_extraction_defaults_to_true():
"""Default value should be True (temporal extraction on)."""
assert DEFAULT_ENABLE_TEMPORAL_EXTRACTION is True
def test_config_enables_temporal_extraction_by_default():
"""HindsightConfig.from_env() should set enable_temporal_extraction=True when env var is unset."""
os.environ.pop(ENV_ENABLE_TEMPORAL_EXTRACTION, None)
os.environ.setdefault("HINDSIGHT_API_LLM_PROVIDER", "mock")
config = HindsightConfig.from_env()
assert config.enable_temporal_extraction is True
def test_config_disables_temporal_extraction_when_false():
"""Setting the env var to 'false' should disable temporal extraction."""
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
os.environ.setdefault("HINDSIGHT_API_LLM_PROVIDER", "mock")
config = HindsightConfig.from_env()
assert config.enable_temporal_extraction is False
def test_config_enables_temporal_extraction_when_true():
"""Setting the env var to 'true' explicitly should enable it."""
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "true"
os.environ.setdefault("HINDSIGHT_API_LLM_PROVIDER", "mock")
config = HindsightConfig.from_env()
assert config.enable_temporal_extraction is True
# ---------------------------------------------------------------------------
# Integration tests — full retain + recall with temporal extraction on/off
# Uses provider=none so retain works in chunks mode (no LLM needed).
# Recall finds chunks via semantic search (embeddings only).
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_recall_returns_results_with_temporal_extraction_enabled(none_memory, request_context):
"""
With temporal extraction enabled (default), recall should return results
for a query that contains temporal language.
"""
os.environ.pop(ENV_ENABLE_TEMPORAL_EXTRACTION, None)
clear_config_cache()
bank_id = f"test_temporal_enabled_{datetime.now(timezone.utc).timestamp()}"
await none_memory.retain_async(
bank_id=bank_id,
content="In March 2024, the team shipped the new authentication system. It replaced the legacy OAuth flow.",
context="engineering update",
request_context=request_context,
)
result = await none_memory.recall_async(
bank_id=bank_id,
query="What happened with authentication in March 2024?",
budget=Budget.LOW,
include_chunks=True,
request_context=request_context,
)
has_results = len(result.results) > 0 or (result.chunks is not None and len(result.chunks) > 0)
assert has_results, "Should find results with temporal extraction enabled"
@pytest.mark.asyncio
async def test_recall_returns_results_with_temporal_extraction_disabled(none_memory, request_context):
"""
With temporal extraction disabled, recall should still return results
via semantic + BM25 + graph retrieval (3-way).
"""
bank_id = f"test_temporal_disabled_{datetime.now(timezone.utc).timestamp()}"
await none_memory.retain_async(
bank_id=bank_id,
content="In March 2024, the team shipped the new authentication system. It replaced the legacy OAuth flow.",
context="engineering update",
request_context=request_context,
)
# Now disable temporal extraction for recall
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
clear_config_cache()
result = await none_memory.recall_async(
bank_id=bank_id,
query="What happened with authentication in March 2024?",
budget=Budget.LOW,
include_chunks=True,
request_context=request_context,
)
has_results = len(result.results) > 0 or (result.chunks is not None and len(result.chunks) > 0)
assert has_results, "Should find results even with temporal extraction disabled (3-way retrieval)"
@pytest.mark.asyncio
async def test_recall_non_temporal_query_works_with_extraction_disabled(none_memory, request_context):
"""
A non-temporal query should work with temporal extraction disabled.
"""
bank_id = f"test_non_temporal_{datetime.now(timezone.utc).timestamp()}"
await none_memory.retain_async(
bank_id=bank_id,
content="Alice is a software engineer who specializes in distributed systems and Kubernetes.",
context="team info",
request_context=request_context,
)
# Disable temporal extraction
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
clear_config_cache()
result = await none_memory.recall_async(
bank_id=bank_id,
query="Who is Alice?",
budget=Budget.LOW,
include_chunks=True,
request_context=request_context,
)
has_results = len(result.results) > 0 or (result.chunks is not None and len(result.chunks) > 0)
assert has_results, "Should find results for non-temporal query"
@pytest.mark.asyncio
async def test_recall_latency_lower_with_temporal_extraction_disabled(none_memory, request_context):
"""
Recall with temporal extraction disabled should be measurably faster
than with it enabled, since we skip the dateparser overhead.
"""
bank_id = f"test_latency_{datetime.now(timezone.utc).timestamp()}"
await none_memory.retain_async(
bank_id=bank_id,
content="The quarterly revenue report showed strong growth in Q3 2024.",
context="business update",
request_context=request_context,
)
query = "What was the revenue in Q3 2024?"
# Measure with temporal extraction enabled
os.environ.pop(ENV_ENABLE_TEMPORAL_EXTRACTION, None)
clear_config_cache()
enabled_times = []
for _ in range(3):
start = time.perf_counter()
await none_memory.recall_async(
bank_id=bank_id,
query=query,
budget=Budget.LOW,
request_context=request_context,
)
enabled_times.append((time.perf_counter() - start) * 1000)
# Measure with temporal extraction disabled
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
clear_config_cache()
disabled_times = []
for _ in range(3):
start = time.perf_counter()
await none_memory.recall_async(
bank_id=bank_id,
query=query,
budget=Budget.LOW,
request_context=request_context,
)
disabled_times.append((time.perf_counter() - start) * 1000)
enabled_avg = sum(enabled_times) / len(enabled_times)
disabled_avg = sum(disabled_times) / len(disabled_times)
print(f"\n Enabled avg: {enabled_avg:.1f}ms")
print(f" Disabled avg: {disabled_avg:.1f}ms")
print(f" Savings: {enabled_avg - disabled_avg:.1f}ms")
# Disabled should be faster
assert disabled_avg < enabled_avg, (
f"Expected disabled ({disabled_avg:.1f}ms) to be faster than enabled ({enabled_avg:.1f}ms)"
)
# ---------------------------------------------------------------------------
# Integration tests with real LLM — retains extract facts, recall searches them.
# These require HINDSIGHT_API_LLM_API_KEY in the environment (available in CI).
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_llm_recall_with_temporal_extraction_enabled(memory, request_context):
"""
With a real LLM: retain extracts facts, recall finds them with temporal
extraction enabled (4-way retrieval).
"""
os.environ.pop(ENV_ENABLE_TEMPORAL_EXTRACTION, None)
clear_config_cache()
bank_id = f"test_llm_temporal_on_{datetime.now(timezone.utc).timestamp()}"
await memory.retain_async(
bank_id=bank_id,
content="In March 2024, the team shipped the new authentication system. It replaced the legacy OAuth flow with OIDC.",
context="engineering update",
request_context=request_context,
)
result = await memory.recall_async(
bank_id=bank_id,
query="What happened with authentication in March 2024?",
budget=Budget.LOW,
request_context=request_context,
)
assert len(result.results) > 0, "Should find facts with temporal extraction enabled"
@pytest.mark.asyncio
async def test_llm_recall_with_temporal_extraction_disabled(memory, request_context):
"""
With a real LLM: retain extracts facts, recall finds them with temporal
extraction disabled (3-way retrieval — semantic + BM25 + graph).
"""
bank_id = f"test_llm_temporal_off_{datetime.now(timezone.utc).timestamp()}"
await memory.retain_async(
bank_id=bank_id,
content="In March 2024, the team shipped the new authentication system. It replaced the legacy OAuth flow with OIDC.",
context="engineering update",
request_context=request_context,
)
# Disable temporal extraction for recall
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
clear_config_cache()
result = await memory.recall_async(
bank_id=bank_id,
query="What happened with authentication in March 2024?",
budget=Budget.LOW,
request_context=request_context,
)
assert len(result.results) > 0, "Should find facts even with temporal extraction disabled (3-way retrieval)"
@pytest.mark.asyncio
async def test_llm_recall_latency_comparison(memory, request_context):
"""
With a real LLM: measure recall latency with temporal extraction on vs off.
"""
bank_id = f"test_llm_latency_{datetime.now(timezone.utc).timestamp()}"
await memory.retain_async(
bank_id=bank_id,
content="The quarterly revenue report showed strong growth in Q3 2024. Revenue increased 15% year over year.",
context="business update",
request_context=request_context,
)
query = "What was the revenue in Q3 2024?"
# Measure with temporal extraction enabled
os.environ.pop(ENV_ENABLE_TEMPORAL_EXTRACTION, None)
clear_config_cache()
enabled_times = []
for _ in range(3):
start = time.perf_counter()
await memory.recall_async(
bank_id=bank_id,
query=query,
budget=Budget.LOW,
request_context=request_context,
)
enabled_times.append((time.perf_counter() - start) * 1000)
# Measure with temporal extraction disabled
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
clear_config_cache()
disabled_times = []
for _ in range(3):
start = time.perf_counter()
await memory.recall_async(
bank_id=bank_id,
query=query,
budget=Budget.LOW,
request_context=request_context,
)
disabled_times.append((time.perf_counter() - start) * 1000)
enabled_avg = sum(enabled_times) / len(enabled_times)
disabled_avg = sum(disabled_times) / len(disabled_times)
print(f"\n LLM Enabled avg: {enabled_avg:.1f}ms")
print(f" LLM Disabled avg: {disabled_avg:.1f}ms")
print(f" LLM Savings: {enabled_avg - disabled_avg:.1f}ms")
assert disabled_avg < enabled_avg, (
f"Expected disabled ({disabled_avg:.1f}ms) to be faster than enabled ({enabled_avg:.1f}ms)"
)
# ---------------------------------------------------------------------------
# Graph retrieval config tests
# ---------------------------------------------------------------------------
def test_graph_retrieval_defaults_to_true():
"""Default value should be True (graph retrieval on)."""
assert DEFAULT_ENABLE_GRAPH_RETRIEVAL is True
def test_config_disables_graph_retrieval_when_false():
"""Setting the env var to 'false' should disable graph retrieval."""
os.environ[ENV_ENABLE_GRAPH_RETRIEVAL] = "false"
os.environ.setdefault("HINDSIGHT_API_LLM_PROVIDER", "mock")
config = HindsightConfig.from_env()
assert config.enable_graph_retrieval is False
# ---------------------------------------------------------------------------
# Full RAG mode integration tests — temporal OFF + graph OFF (2-way retrieval)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_recall_with_both_temporal_and_graph_disabled(none_memory, request_context):
"""
With both temporal extraction and graph retrieval disabled, recall should
still return results via semantic + BM25 only (2-way retrieval).
"""
bank_id = f"test_rag_mode_{datetime.now(timezone.utc).timestamp()}"
await none_memory.retain_async(
bank_id=bank_id,
content="Alice is a software engineer who specializes in distributed systems and Kubernetes.",
context="team info",
request_context=request_context,
)
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
os.environ[ENV_ENABLE_GRAPH_RETRIEVAL] = "false"
clear_config_cache()
result = await none_memory.recall_async(
bank_id=bank_id,
query="Who is Alice?",
budget=Budget.LOW,
include_chunks=True,
request_context=request_context,
)
has_results = len(result.results) > 0 or (result.chunks is not None and len(result.chunks) > 0)
assert has_results, "Should find results with 2-way retrieval (semantic + BM25 only)"
@pytest.mark.asyncio
async def test_llm_recall_rag_mode(memory, request_context):
"""
Full RAG mode with real LLM: temporal OFF + graph OFF.
Recall should still find LLM-extracted facts via semantic + BM25.
"""
bank_id = f"test_llm_rag_{datetime.now(timezone.utc).timestamp()}"
await memory.retain_async(
bank_id=bank_id,
content="In March 2024, the team shipped the new authentication system. It replaced the legacy OAuth flow with OIDC.",
context="engineering update",
request_context=request_context,
)
os.environ[ENV_ENABLE_TEMPORAL_EXTRACTION] = "false"
os.environ[ENV_ENABLE_GRAPH_RETRIEVAL] = "false"
clear_config_cache()
result = await memory.recall_async(
bank_id=bank_id,
query="What happened with authentication?",
budget=Budget.LOW,
request_context=request_context,
)
assert len(result.results) > 0, "Should find facts in RAG mode (2-way retrieval)"
+361 -1
View File
@@ -7,7 +7,7 @@ info:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
title: Hindsight HTTP API
version: 0.5.0
version: 0.5.2
servers:
- url: /
paths:
@@ -166,6 +166,14 @@ paths:
nullable: true
type: string
style: form
- explode: true
in: query
name: consolidation_state
required: false
schema:
nullable: true
type: string
style: form
- explode: true
in: query
name: limit
@@ -464,6 +472,53 @@ paths:
summary: Get statistics for memory bank
tags:
- Banks
/v1/default/banks/{bank_id}/stats/memories-timeseries:
get:
description: "Memories ingested over a period, bucketed by time and broken down\
\ by fact type."
operationId: get_memories_timeseries
parameters:
- explode: false
in: path
name: bank_id
required: true
schema:
title: Bank Id
type: string
style: simple
- explode: true
in: query
name: period
required: false
schema:
default: 7d
title: Period
type: string
style: form
- explode: false
in: header
name: authorization
required: false
schema:
nullable: true
type: string
style: simple
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/MemoriesTimeseriesResponse'
description: Successful Response
"422":
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Memory ingestion time-series
tags:
- Banks
/v1/default/banks/{bank_id}/entities:
get:
description: "List all entities (people, organizations, etc.) known by the bank,\
@@ -524,6 +579,66 @@ paths:
summary: List entities
tags:
- Entities
/v1/default/banks/{bank_id}/entities/graph:
get:
description: Return a graph of entities (nodes) and their co-occurrences (edges)
for visualization.
operationId: get_entity_graph
parameters:
- explode: false
in: path
name: bank_id
required: true
schema:
title: Bank Id
type: string
style: simple
- description: Maximum number of co-occurrence edges to return
explode: true
in: query
name: limit
required: false
schema:
default: 1000
description: Maximum number of co-occurrence edges to return
title: Limit
type: integer
style: form
- description: Minimum cooccurrence_count to include an edge
explode: true
in: query
name: min_count
required: false
schema:
default: 1
description: Minimum cooccurrence_count to include an edge
title: Min Count
type: integer
style: form
- explode: false
in: header
name: authorization
required: false
schema:
nullable: true
type: string
style: simple
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/EntityGraphResponse'
description: Successful Response
"422":
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Get entity co-occurrence graph
tags:
- Entities
/v1/default/banks/{bank_id}/entities/{entity_id}:
get:
description: Get detailed information about an entity including observations
@@ -1781,6 +1896,19 @@ paths:
title: Operation Id
type: string
style: simple
- description: Include the raw task payload (submission params) in the response.
May be large.
explode: true
in: query
name: include_payload
required: false
schema:
default: false
description: Include the raw task payload (submission params) in the response.
May be large.
title: Include Payload
type: boolean
style: form
- explode: false
in: header
name: authorization
@@ -3439,6 +3567,7 @@ components:
description: Response model for bank statistics endpoint.
example:
bank_id: user123
failed_consolidation: 0
failed_operations: 0
last_consolidated_at: 2024-01-15T10:30:00Z
links_breakdown:
@@ -3500,6 +3629,12 @@ components:
failed_operations:
title: Failed Operations
type: integer
operations_by_status:
additionalProperties:
type: integer
description: "Async operations grouped by status (pending, in_progress,\
\ completed, failed, cancelled)."
title: Operations By Status
last_consolidated_at:
nullable: true
type: string
@@ -3508,6 +3643,12 @@ components:
description: Number of memories not yet processed into observations
title: Pending Consolidation
type: integer
failed_consolidation:
default: 0
description: Number of source memories (world/experience) whose consolidation
permanently failed and can be retried via the consolidation recovery endpoint.
title: Failed Consolidation
type: integer
total_observations:
default: 0
description: Total number of observations
@@ -3576,6 +3717,66 @@ components:
entities_allow_free_form:
nullable: true
type: boolean
retain_default_strategy:
nullable: true
type: string
retain_strategies:
additionalProperties: {}
nullable: true
retain_chunk_batch_size:
nullable: true
type: integer
mcp_enabled_tools:
items:
type: string
nullable: true
type: array
consolidation_llm_batch_size:
nullable: true
type: integer
consolidation_source_facts_max_tokens:
nullable: true
type: integer
consolidation_source_facts_max_tokens_per_observation:
nullable: true
type: integer
max_observations_per_scope:
nullable: true
type: integer
reflect_source_facts_max_tokens:
nullable: true
type: integer
llm_gemini_safety_settings:
items: {}
nullable: true
type: array
recall_budget_function:
nullable: true
type: string
recall_budget_fixed_low:
nullable: true
type: integer
recall_budget_fixed_mid:
nullable: true
type: integer
recall_budget_fixed_high:
nullable: true
type: integer
recall_budget_adaptive_low:
nullable: true
type: number
recall_budget_adaptive_mid:
nullable: true
type: number
recall_budget_adaptive_high:
nullable: true
type: number
recall_budget_min:
nullable: true
type: integer
recall_budget_max:
nullable: true
type: integer
title: BankTemplateConfig
BankTemplateDirective:
description: |-
@@ -4381,6 +4582,58 @@ components:
- mention_count
- observations
title: EntityDetailResponse
EntityGraphResponse:
description: Response model for entity co-occurrence graph endpoint.
example:
edges:
- data:
color: '#ffd700'
id: uuid-1-uuid-2
lastCooccurred: 2024-02-01T14:00:00Z
lineStyle: solid
linkType: cooccurrence
source: uuid-1
target: uuid-2
weight: 5
limit: 1000
nodes:
- data:
color: '#42a5f5'
id: uuid-1
label: Alice
mentionCount: 12
- data:
color: '#42a5f5'
id: uuid-2
label: Google
mentionCount: 8
total_edges: 1
total_entities: 2
properties:
nodes:
items:
additionalProperties: {}
type: array
edges:
items:
additionalProperties: {}
type: array
total_entities:
title: Total Entities
type: integer
total_edges:
title: Total Edges
type: integer
limit:
title: Limit
type: integer
required:
- edges
- limit
- nodes
- total_edges
- total_entities
title: EntityGraphResponse
EntityIncludeOptions:
description: Options for including entity observations in recall results.
properties:
@@ -4736,6 +4989,44 @@ components:
- offset
- total
title: ListTagsResponse
MemoriesTimeseriesResponse:
description: Time-series of memory ingestion bucketed by time and fact type.
example:
period: period
trunc: trunc
bank_id: bank_id
buckets:
- world: 0
observation: 1
time: time
experience: 6
- world: 0
observation: 1
time: time
experience: 6
properties:
bank_id:
title: Bank Id
type: string
period:
description: "One of: 1h, 12h, 1d, 7d, 30d, 90d."
title: Period
type: string
trunc:
description: "Bucket granularity: minute, hour, day."
title: Trunc
type: string
buckets:
description: "Per-bucket counts, always returned fully padded for the requested\
\ period."
items:
$ref: '#/components/schemas/MemoryTimeseriesBucket'
type: array
required:
- bank_id
- period
- trunc
title: MemoriesTimeseriesResponse
MemoryItem:
description: Single memory item for retain.
example:
@@ -4793,6 +5084,36 @@ components:
required:
- content
title: MemoryItem
MemoryTimeseriesBucket:
description: One bucket in the memory ingestion time-series.
example:
world: 0
observation: 1
time: time
experience: 6
properties:
time:
description: Bucket start timestamp in ISO-8601 (UTC).
title: Time
type: string
world:
default: 0
description: World-fact memories ingested in this bucket.
title: World
type: integer
experience:
default: 0
description: Experience memories ingested in this bucket.
title: Experience
type: integer
observation:
default: 0
description: Observations recorded in this bucket.
title: Observation
type: integer
required:
- time
title: MemoryTimeseriesBucket
MentalModelListResponse:
description: Response model for listing mental models.
example:
@@ -4807,6 +5128,7 @@ components:
id: id
trigger:
refresh_after_consolidation: false
recall_chunks_max_tokens: 1
tag_groups:
- match: any_strict
tags:
@@ -4822,9 +5144,12 @@ components:
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
include_chunks: true
tags_match: any
exclude_mental_models: false
recall_max_tokens: 6
last_refreshed_at: last_refreshed_at
is_stale: true
content: content
tags:
- tags
@@ -4839,6 +5164,7 @@ components:
id: id
trigger:
refresh_after_consolidation: false
recall_chunks_max_tokens: 1
tag_groups:
- match: any_strict
tags:
@@ -4854,9 +5180,12 @@ components:
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
include_chunks: true
tags_match: any
exclude_mental_models: false
recall_max_tokens: 6
last_refreshed_at: last_refreshed_at
is_stale: true
content: content
tags:
- tags
@@ -4882,6 +5211,7 @@ components:
id: id
trigger:
refresh_after_consolidation: false
recall_chunks_max_tokens: 1
tag_groups:
- match: any_strict
tags:
@@ -4897,9 +5227,12 @@ components:
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
include_chunks: true
tags_match: any
exclude_mental_models: false
recall_max_tokens: 6
last_refreshed_at: last_refreshed_at
is_stale: true
content: content
tags:
- tags
@@ -4939,6 +5272,9 @@ components:
reflect_response:
additionalProperties: {}
nullable: true
is_stale:
nullable: true
type: boolean
required:
- bank_id
- id
@@ -4986,11 +5322,21 @@ components:
$ref: '#/components/schemas/MentalModelTrigger_Input_tag_groups_inner'
nullable: true
type: array
include_chunks:
nullable: true
type: boolean
recall_max_tokens:
nullable: true
type: integer
recall_chunks_max_tokens:
nullable: true
type: integer
title: MentalModelTrigger
MentalModelTrigger-Output:
description: Trigger settings for a mental model.
example:
refresh_after_consolidation: false
recall_chunks_max_tokens: 1
tag_groups:
- match: any_strict
tags:
@@ -5006,8 +5352,10 @@ components:
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
include_chunks: true
tags_match: any
exclude_mental_models: false
recall_max_tokens: 6
properties:
refresh_after_consolidation:
default: false
@@ -5048,6 +5396,15 @@ components:
$ref: '#/components/schemas/MentalModelTrigger_Output_tag_groups_inner'
nullable: true
type: array
include_chunks:
nullable: true
type: boolean
recall_max_tokens:
nullable: true
type: integer
recall_chunks_max_tokens:
nullable: true
type: integer
title: MentalModelTrigger
OperationResponse:
description: Response model for a single async operation.
@@ -5131,6 +5488,9 @@ components:
$ref: '#/components/schemas/ChildOperationStatus'
nullable: true
type: array
task_payload:
additionalProperties: {}
nullable: true
required:
- operation_id
- status
@@ -3,7 +3,7 @@ Hindsight HTTP API
HTTP API for Hindsight
API version: 0.5.0
API version: 0.5.2
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -31,6 +31,25 @@ type BankTemplateConfig struct {
DispositionEmpathy NullableInt32 `json:"disposition_empathy,omitempty"`
EntityLabels []map[string]interface{} `json:"entity_labels,omitempty"`
EntitiesAllowFreeForm NullableBool `json:"entities_allow_free_form,omitempty"`
RetainDefaultStrategy NullableString `json:"retain_default_strategy,omitempty"`
RetainStrategies map[string]interface{} `json:"retain_strategies,omitempty"`
RetainChunkBatchSize NullableInt32 `json:"retain_chunk_batch_size,omitempty"`
McpEnabledTools []string `json:"mcp_enabled_tools,omitempty"`
ConsolidationLlmBatchSize NullableInt32 `json:"consolidation_llm_batch_size,omitempty"`
ConsolidationSourceFactsMaxTokens NullableInt32 `json:"consolidation_source_facts_max_tokens,omitempty"`
ConsolidationSourceFactsMaxTokensPerObservation NullableInt32 `json:"consolidation_source_facts_max_tokens_per_observation,omitempty"`
MaxObservationsPerScope NullableInt32 `json:"max_observations_per_scope,omitempty"`
ReflectSourceFactsMaxTokens NullableInt32 `json:"reflect_source_facts_max_tokens,omitempty"`
LlmGeminiSafetySettings []interface{} `json:"llm_gemini_safety_settings,omitempty"`
RecallBudgetFunction NullableString `json:"recall_budget_function,omitempty"`
RecallBudgetFixedLow NullableInt32 `json:"recall_budget_fixed_low,omitempty"`
RecallBudgetFixedMid NullableInt32 `json:"recall_budget_fixed_mid,omitempty"`
RecallBudgetFixedHigh NullableInt32 `json:"recall_budget_fixed_high,omitempty"`
RecallBudgetAdaptiveLow NullableFloat32 `json:"recall_budget_adaptive_low,omitempty"`
RecallBudgetAdaptiveMid NullableFloat32 `json:"recall_budget_adaptive_mid,omitempty"`
RecallBudgetAdaptiveHigh NullableFloat32 `json:"recall_budget_adaptive_high,omitempty"`
RecallBudgetMin NullableInt32 `json:"recall_budget_min,omitempty"`
RecallBudgetMax NullableInt32 `json:"recall_budget_max,omitempty"`
}
// NewBankTemplateConfig instantiates a new BankTemplateConfig object
@@ -545,6 +564,777 @@ func (o *BankTemplateConfig) UnsetEntitiesAllowFreeForm() {
o.EntitiesAllowFreeForm.Unset()
}
// GetRetainDefaultStrategy returns the RetainDefaultStrategy field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainDefaultStrategy() string {
if o == nil || IsNil(o.RetainDefaultStrategy.Get()) {
var ret string
return ret
}
return *o.RetainDefaultStrategy.Get()
}
// GetRetainDefaultStrategyOk returns a tuple with the RetainDefaultStrategy field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRetainDefaultStrategyOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.RetainDefaultStrategy.Get(), o.RetainDefaultStrategy.IsSet()
}
// HasRetainDefaultStrategy returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainDefaultStrategy() bool {
if o != nil && o.RetainDefaultStrategy.IsSet() {
return true
}
return false
}
// SetRetainDefaultStrategy gets a reference to the given NullableString and assigns it to the RetainDefaultStrategy field.
func (o *BankTemplateConfig) SetRetainDefaultStrategy(v string) {
o.RetainDefaultStrategy.Set(&v)
}
// SetRetainDefaultStrategyNil sets the value for RetainDefaultStrategy to be an explicit nil
func (o *BankTemplateConfig) SetRetainDefaultStrategyNil() {
o.RetainDefaultStrategy.Set(nil)
}
// UnsetRetainDefaultStrategy ensures that no value is present for RetainDefaultStrategy, not even an explicit nil
func (o *BankTemplateConfig) UnsetRetainDefaultStrategy() {
o.RetainDefaultStrategy.Unset()
}
// GetRetainStrategies returns the RetainStrategies field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainStrategies() map[string]interface{} {
if o == nil {
var ret map[string]interface{}
return ret
}
return o.RetainStrategies
}
// GetRetainStrategiesOk returns a tuple with the RetainStrategies field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRetainStrategiesOk() (map[string]interface{}, bool) {
if o == nil || IsNil(o.RetainStrategies) {
return map[string]interface{}{}, false
}
return o.RetainStrategies, true
}
// HasRetainStrategies returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainStrategies() bool {
if o != nil && !IsNil(o.RetainStrategies) {
return true
}
return false
}
// SetRetainStrategies gets a reference to the given map[string]interface{} and assigns it to the RetainStrategies field.
func (o *BankTemplateConfig) SetRetainStrategies(v map[string]interface{}) {
o.RetainStrategies = v
}
// GetRetainChunkBatchSize returns the RetainChunkBatchSize field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainChunkBatchSize() int32 {
if o == nil || IsNil(o.RetainChunkBatchSize.Get()) {
var ret int32
return ret
}
return *o.RetainChunkBatchSize.Get()
}
// GetRetainChunkBatchSizeOk returns a tuple with the RetainChunkBatchSize field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRetainChunkBatchSizeOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RetainChunkBatchSize.Get(), o.RetainChunkBatchSize.IsSet()
}
// HasRetainChunkBatchSize returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainChunkBatchSize() bool {
if o != nil && o.RetainChunkBatchSize.IsSet() {
return true
}
return false
}
// SetRetainChunkBatchSize gets a reference to the given NullableInt32 and assigns it to the RetainChunkBatchSize field.
func (o *BankTemplateConfig) SetRetainChunkBatchSize(v int32) {
o.RetainChunkBatchSize.Set(&v)
}
// SetRetainChunkBatchSizeNil sets the value for RetainChunkBatchSize to be an explicit nil
func (o *BankTemplateConfig) SetRetainChunkBatchSizeNil() {
o.RetainChunkBatchSize.Set(nil)
}
// UnsetRetainChunkBatchSize ensures that no value is present for RetainChunkBatchSize, not even an explicit nil
func (o *BankTemplateConfig) UnsetRetainChunkBatchSize() {
o.RetainChunkBatchSize.Unset()
}
// GetMcpEnabledTools returns the McpEnabledTools field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetMcpEnabledTools() []string {
if o == nil {
var ret []string
return ret
}
return o.McpEnabledTools
}
// GetMcpEnabledToolsOk returns a tuple with the McpEnabledTools field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetMcpEnabledToolsOk() ([]string, bool) {
if o == nil || IsNil(o.McpEnabledTools) {
return nil, false
}
return o.McpEnabledTools, true
}
// HasMcpEnabledTools returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasMcpEnabledTools() bool {
if o != nil && !IsNil(o.McpEnabledTools) {
return true
}
return false
}
// SetMcpEnabledTools gets a reference to the given []string and assigns it to the McpEnabledTools field.
func (o *BankTemplateConfig) SetMcpEnabledTools(v []string) {
o.McpEnabledTools = v
}
// GetConsolidationLlmBatchSize returns the ConsolidationLlmBatchSize field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetConsolidationLlmBatchSize() int32 {
if o == nil || IsNil(o.ConsolidationLlmBatchSize.Get()) {
var ret int32
return ret
}
return *o.ConsolidationLlmBatchSize.Get()
}
// GetConsolidationLlmBatchSizeOk returns a tuple with the ConsolidationLlmBatchSize field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetConsolidationLlmBatchSizeOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.ConsolidationLlmBatchSize.Get(), o.ConsolidationLlmBatchSize.IsSet()
}
// HasConsolidationLlmBatchSize returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasConsolidationLlmBatchSize() bool {
if o != nil && o.ConsolidationLlmBatchSize.IsSet() {
return true
}
return false
}
// SetConsolidationLlmBatchSize gets a reference to the given NullableInt32 and assigns it to the ConsolidationLlmBatchSize field.
func (o *BankTemplateConfig) SetConsolidationLlmBatchSize(v int32) {
o.ConsolidationLlmBatchSize.Set(&v)
}
// SetConsolidationLlmBatchSizeNil sets the value for ConsolidationLlmBatchSize to be an explicit nil
func (o *BankTemplateConfig) SetConsolidationLlmBatchSizeNil() {
o.ConsolidationLlmBatchSize.Set(nil)
}
// UnsetConsolidationLlmBatchSize ensures that no value is present for ConsolidationLlmBatchSize, not even an explicit nil
func (o *BankTemplateConfig) UnsetConsolidationLlmBatchSize() {
o.ConsolidationLlmBatchSize.Unset()
}
// GetConsolidationSourceFactsMaxTokens returns the ConsolidationSourceFactsMaxTokens field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetConsolidationSourceFactsMaxTokens() int32 {
if o == nil || IsNil(o.ConsolidationSourceFactsMaxTokens.Get()) {
var ret int32
return ret
}
return *o.ConsolidationSourceFactsMaxTokens.Get()
}
// GetConsolidationSourceFactsMaxTokensOk returns a tuple with the ConsolidationSourceFactsMaxTokens field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetConsolidationSourceFactsMaxTokensOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.ConsolidationSourceFactsMaxTokens.Get(), o.ConsolidationSourceFactsMaxTokens.IsSet()
}
// HasConsolidationSourceFactsMaxTokens returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasConsolidationSourceFactsMaxTokens() bool {
if o != nil && o.ConsolidationSourceFactsMaxTokens.IsSet() {
return true
}
return false
}
// SetConsolidationSourceFactsMaxTokens gets a reference to the given NullableInt32 and assigns it to the ConsolidationSourceFactsMaxTokens field.
func (o *BankTemplateConfig) SetConsolidationSourceFactsMaxTokens(v int32) {
o.ConsolidationSourceFactsMaxTokens.Set(&v)
}
// SetConsolidationSourceFactsMaxTokensNil sets the value for ConsolidationSourceFactsMaxTokens to be an explicit nil
func (o *BankTemplateConfig) SetConsolidationSourceFactsMaxTokensNil() {
o.ConsolidationSourceFactsMaxTokens.Set(nil)
}
// UnsetConsolidationSourceFactsMaxTokens ensures that no value is present for ConsolidationSourceFactsMaxTokens, not even an explicit nil
func (o *BankTemplateConfig) UnsetConsolidationSourceFactsMaxTokens() {
o.ConsolidationSourceFactsMaxTokens.Unset()
}
// GetConsolidationSourceFactsMaxTokensPerObservation returns the ConsolidationSourceFactsMaxTokensPerObservation field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetConsolidationSourceFactsMaxTokensPerObservation() int32 {
if o == nil || IsNil(o.ConsolidationSourceFactsMaxTokensPerObservation.Get()) {
var ret int32
return ret
}
return *o.ConsolidationSourceFactsMaxTokensPerObservation.Get()
}
// GetConsolidationSourceFactsMaxTokensPerObservationOk returns a tuple with the ConsolidationSourceFactsMaxTokensPerObservation field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetConsolidationSourceFactsMaxTokensPerObservationOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.ConsolidationSourceFactsMaxTokensPerObservation.Get(), o.ConsolidationSourceFactsMaxTokensPerObservation.IsSet()
}
// HasConsolidationSourceFactsMaxTokensPerObservation returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasConsolidationSourceFactsMaxTokensPerObservation() bool {
if o != nil && o.ConsolidationSourceFactsMaxTokensPerObservation.IsSet() {
return true
}
return false
}
// SetConsolidationSourceFactsMaxTokensPerObservation gets a reference to the given NullableInt32 and assigns it to the ConsolidationSourceFactsMaxTokensPerObservation field.
func (o *BankTemplateConfig) SetConsolidationSourceFactsMaxTokensPerObservation(v int32) {
o.ConsolidationSourceFactsMaxTokensPerObservation.Set(&v)
}
// SetConsolidationSourceFactsMaxTokensPerObservationNil sets the value for ConsolidationSourceFactsMaxTokensPerObservation to be an explicit nil
func (o *BankTemplateConfig) SetConsolidationSourceFactsMaxTokensPerObservationNil() {
o.ConsolidationSourceFactsMaxTokensPerObservation.Set(nil)
}
// UnsetConsolidationSourceFactsMaxTokensPerObservation ensures that no value is present for ConsolidationSourceFactsMaxTokensPerObservation, not even an explicit nil
func (o *BankTemplateConfig) UnsetConsolidationSourceFactsMaxTokensPerObservation() {
o.ConsolidationSourceFactsMaxTokensPerObservation.Unset()
}
// GetMaxObservationsPerScope returns the MaxObservationsPerScope field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetMaxObservationsPerScope() int32 {
if o == nil || IsNil(o.MaxObservationsPerScope.Get()) {
var ret int32
return ret
}
return *o.MaxObservationsPerScope.Get()
}
// GetMaxObservationsPerScopeOk returns a tuple with the MaxObservationsPerScope field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetMaxObservationsPerScopeOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.MaxObservationsPerScope.Get(), o.MaxObservationsPerScope.IsSet()
}
// HasMaxObservationsPerScope returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasMaxObservationsPerScope() bool {
if o != nil && o.MaxObservationsPerScope.IsSet() {
return true
}
return false
}
// SetMaxObservationsPerScope gets a reference to the given NullableInt32 and assigns it to the MaxObservationsPerScope field.
func (o *BankTemplateConfig) SetMaxObservationsPerScope(v int32) {
o.MaxObservationsPerScope.Set(&v)
}
// SetMaxObservationsPerScopeNil sets the value for MaxObservationsPerScope to be an explicit nil
func (o *BankTemplateConfig) SetMaxObservationsPerScopeNil() {
o.MaxObservationsPerScope.Set(nil)
}
// UnsetMaxObservationsPerScope ensures that no value is present for MaxObservationsPerScope, not even an explicit nil
func (o *BankTemplateConfig) UnsetMaxObservationsPerScope() {
o.MaxObservationsPerScope.Unset()
}
// GetReflectSourceFactsMaxTokens returns the ReflectSourceFactsMaxTokens field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetReflectSourceFactsMaxTokens() int32 {
if o == nil || IsNil(o.ReflectSourceFactsMaxTokens.Get()) {
var ret int32
return ret
}
return *o.ReflectSourceFactsMaxTokens.Get()
}
// GetReflectSourceFactsMaxTokensOk returns a tuple with the ReflectSourceFactsMaxTokens field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetReflectSourceFactsMaxTokensOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.ReflectSourceFactsMaxTokens.Get(), o.ReflectSourceFactsMaxTokens.IsSet()
}
// HasReflectSourceFactsMaxTokens returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasReflectSourceFactsMaxTokens() bool {
if o != nil && o.ReflectSourceFactsMaxTokens.IsSet() {
return true
}
return false
}
// SetReflectSourceFactsMaxTokens gets a reference to the given NullableInt32 and assigns it to the ReflectSourceFactsMaxTokens field.
func (o *BankTemplateConfig) SetReflectSourceFactsMaxTokens(v int32) {
o.ReflectSourceFactsMaxTokens.Set(&v)
}
// SetReflectSourceFactsMaxTokensNil sets the value for ReflectSourceFactsMaxTokens to be an explicit nil
func (o *BankTemplateConfig) SetReflectSourceFactsMaxTokensNil() {
o.ReflectSourceFactsMaxTokens.Set(nil)
}
// UnsetReflectSourceFactsMaxTokens ensures that no value is present for ReflectSourceFactsMaxTokens, not even an explicit nil
func (o *BankTemplateConfig) UnsetReflectSourceFactsMaxTokens() {
o.ReflectSourceFactsMaxTokens.Unset()
}
// GetLlmGeminiSafetySettings returns the LlmGeminiSafetySettings field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetLlmGeminiSafetySettings() []interface{} {
if o == nil {
var ret []interface{}
return ret
}
return o.LlmGeminiSafetySettings
}
// GetLlmGeminiSafetySettingsOk returns a tuple with the LlmGeminiSafetySettings field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetLlmGeminiSafetySettingsOk() ([]interface{}, bool) {
if o == nil || IsNil(o.LlmGeminiSafetySettings) {
return nil, false
}
return o.LlmGeminiSafetySettings, true
}
// HasLlmGeminiSafetySettings returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasLlmGeminiSafetySettings() bool {
if o != nil && !IsNil(o.LlmGeminiSafetySettings) {
return true
}
return false
}
// SetLlmGeminiSafetySettings gets a reference to the given []interface{} and assigns it to the LlmGeminiSafetySettings field.
func (o *BankTemplateConfig) SetLlmGeminiSafetySettings(v []interface{}) {
o.LlmGeminiSafetySettings = v
}
// GetRecallBudgetFunction returns the RecallBudgetFunction field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetFunction() string {
if o == nil || IsNil(o.RecallBudgetFunction.Get()) {
var ret string
return ret
}
return *o.RecallBudgetFunction.Get()
}
// GetRecallBudgetFunctionOk returns a tuple with the RecallBudgetFunction field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetFunctionOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetFunction.Get(), o.RecallBudgetFunction.IsSet()
}
// HasRecallBudgetFunction returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetFunction() bool {
if o != nil && o.RecallBudgetFunction.IsSet() {
return true
}
return false
}
// SetRecallBudgetFunction gets a reference to the given NullableString and assigns it to the RecallBudgetFunction field.
func (o *BankTemplateConfig) SetRecallBudgetFunction(v string) {
o.RecallBudgetFunction.Set(&v)
}
// SetRecallBudgetFunctionNil sets the value for RecallBudgetFunction to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetFunctionNil() {
o.RecallBudgetFunction.Set(nil)
}
// UnsetRecallBudgetFunction ensures that no value is present for RecallBudgetFunction, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetFunction() {
o.RecallBudgetFunction.Unset()
}
// GetRecallBudgetFixedLow returns the RecallBudgetFixedLow field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetFixedLow() int32 {
if o == nil || IsNil(o.RecallBudgetFixedLow.Get()) {
var ret int32
return ret
}
return *o.RecallBudgetFixedLow.Get()
}
// GetRecallBudgetFixedLowOk returns a tuple with the RecallBudgetFixedLow field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetFixedLowOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetFixedLow.Get(), o.RecallBudgetFixedLow.IsSet()
}
// HasRecallBudgetFixedLow returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetFixedLow() bool {
if o != nil && o.RecallBudgetFixedLow.IsSet() {
return true
}
return false
}
// SetRecallBudgetFixedLow gets a reference to the given NullableInt32 and assigns it to the RecallBudgetFixedLow field.
func (o *BankTemplateConfig) SetRecallBudgetFixedLow(v int32) {
o.RecallBudgetFixedLow.Set(&v)
}
// SetRecallBudgetFixedLowNil sets the value for RecallBudgetFixedLow to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetFixedLowNil() {
o.RecallBudgetFixedLow.Set(nil)
}
// UnsetRecallBudgetFixedLow ensures that no value is present for RecallBudgetFixedLow, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetFixedLow() {
o.RecallBudgetFixedLow.Unset()
}
// GetRecallBudgetFixedMid returns the RecallBudgetFixedMid field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetFixedMid() int32 {
if o == nil || IsNil(o.RecallBudgetFixedMid.Get()) {
var ret int32
return ret
}
return *o.RecallBudgetFixedMid.Get()
}
// GetRecallBudgetFixedMidOk returns a tuple with the RecallBudgetFixedMid field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetFixedMidOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetFixedMid.Get(), o.RecallBudgetFixedMid.IsSet()
}
// HasRecallBudgetFixedMid returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetFixedMid() bool {
if o != nil && o.RecallBudgetFixedMid.IsSet() {
return true
}
return false
}
// SetRecallBudgetFixedMid gets a reference to the given NullableInt32 and assigns it to the RecallBudgetFixedMid field.
func (o *BankTemplateConfig) SetRecallBudgetFixedMid(v int32) {
o.RecallBudgetFixedMid.Set(&v)
}
// SetRecallBudgetFixedMidNil sets the value for RecallBudgetFixedMid to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetFixedMidNil() {
o.RecallBudgetFixedMid.Set(nil)
}
// UnsetRecallBudgetFixedMid ensures that no value is present for RecallBudgetFixedMid, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetFixedMid() {
o.RecallBudgetFixedMid.Unset()
}
// GetRecallBudgetFixedHigh returns the RecallBudgetFixedHigh field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetFixedHigh() int32 {
if o == nil || IsNil(o.RecallBudgetFixedHigh.Get()) {
var ret int32
return ret
}
return *o.RecallBudgetFixedHigh.Get()
}
// GetRecallBudgetFixedHighOk returns a tuple with the RecallBudgetFixedHigh field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetFixedHighOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetFixedHigh.Get(), o.RecallBudgetFixedHigh.IsSet()
}
// HasRecallBudgetFixedHigh returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetFixedHigh() bool {
if o != nil && o.RecallBudgetFixedHigh.IsSet() {
return true
}
return false
}
// SetRecallBudgetFixedHigh gets a reference to the given NullableInt32 and assigns it to the RecallBudgetFixedHigh field.
func (o *BankTemplateConfig) SetRecallBudgetFixedHigh(v int32) {
o.RecallBudgetFixedHigh.Set(&v)
}
// SetRecallBudgetFixedHighNil sets the value for RecallBudgetFixedHigh to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetFixedHighNil() {
o.RecallBudgetFixedHigh.Set(nil)
}
// UnsetRecallBudgetFixedHigh ensures that no value is present for RecallBudgetFixedHigh, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetFixedHigh() {
o.RecallBudgetFixedHigh.Unset()
}
// GetRecallBudgetAdaptiveLow returns the RecallBudgetAdaptiveLow field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetAdaptiveLow() float32 {
if o == nil || IsNil(o.RecallBudgetAdaptiveLow.Get()) {
var ret float32
return ret
}
return *o.RecallBudgetAdaptiveLow.Get()
}
// GetRecallBudgetAdaptiveLowOk returns a tuple with the RecallBudgetAdaptiveLow field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetAdaptiveLowOk() (*float32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetAdaptiveLow.Get(), o.RecallBudgetAdaptiveLow.IsSet()
}
// HasRecallBudgetAdaptiveLow returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetAdaptiveLow() bool {
if o != nil && o.RecallBudgetAdaptiveLow.IsSet() {
return true
}
return false
}
// SetRecallBudgetAdaptiveLow gets a reference to the given NullableFloat32 and assigns it to the RecallBudgetAdaptiveLow field.
func (o *BankTemplateConfig) SetRecallBudgetAdaptiveLow(v float32) {
o.RecallBudgetAdaptiveLow.Set(&v)
}
// SetRecallBudgetAdaptiveLowNil sets the value for RecallBudgetAdaptiveLow to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetAdaptiveLowNil() {
o.RecallBudgetAdaptiveLow.Set(nil)
}
// UnsetRecallBudgetAdaptiveLow ensures that no value is present for RecallBudgetAdaptiveLow, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetAdaptiveLow() {
o.RecallBudgetAdaptiveLow.Unset()
}
// GetRecallBudgetAdaptiveMid returns the RecallBudgetAdaptiveMid field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetAdaptiveMid() float32 {
if o == nil || IsNil(o.RecallBudgetAdaptiveMid.Get()) {
var ret float32
return ret
}
return *o.RecallBudgetAdaptiveMid.Get()
}
// GetRecallBudgetAdaptiveMidOk returns a tuple with the RecallBudgetAdaptiveMid field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetAdaptiveMidOk() (*float32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetAdaptiveMid.Get(), o.RecallBudgetAdaptiveMid.IsSet()
}
// HasRecallBudgetAdaptiveMid returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetAdaptiveMid() bool {
if o != nil && o.RecallBudgetAdaptiveMid.IsSet() {
return true
}
return false
}
// SetRecallBudgetAdaptiveMid gets a reference to the given NullableFloat32 and assigns it to the RecallBudgetAdaptiveMid field.
func (o *BankTemplateConfig) SetRecallBudgetAdaptiveMid(v float32) {
o.RecallBudgetAdaptiveMid.Set(&v)
}
// SetRecallBudgetAdaptiveMidNil sets the value for RecallBudgetAdaptiveMid to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetAdaptiveMidNil() {
o.RecallBudgetAdaptiveMid.Set(nil)
}
// UnsetRecallBudgetAdaptiveMid ensures that no value is present for RecallBudgetAdaptiveMid, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetAdaptiveMid() {
o.RecallBudgetAdaptiveMid.Unset()
}
// GetRecallBudgetAdaptiveHigh returns the RecallBudgetAdaptiveHigh field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetAdaptiveHigh() float32 {
if o == nil || IsNil(o.RecallBudgetAdaptiveHigh.Get()) {
var ret float32
return ret
}
return *o.RecallBudgetAdaptiveHigh.Get()
}
// GetRecallBudgetAdaptiveHighOk returns a tuple with the RecallBudgetAdaptiveHigh field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetAdaptiveHighOk() (*float32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetAdaptiveHigh.Get(), o.RecallBudgetAdaptiveHigh.IsSet()
}
// HasRecallBudgetAdaptiveHigh returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetAdaptiveHigh() bool {
if o != nil && o.RecallBudgetAdaptiveHigh.IsSet() {
return true
}
return false
}
// SetRecallBudgetAdaptiveHigh gets a reference to the given NullableFloat32 and assigns it to the RecallBudgetAdaptiveHigh field.
func (o *BankTemplateConfig) SetRecallBudgetAdaptiveHigh(v float32) {
o.RecallBudgetAdaptiveHigh.Set(&v)
}
// SetRecallBudgetAdaptiveHighNil sets the value for RecallBudgetAdaptiveHigh to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetAdaptiveHighNil() {
o.RecallBudgetAdaptiveHigh.Set(nil)
}
// UnsetRecallBudgetAdaptiveHigh ensures that no value is present for RecallBudgetAdaptiveHigh, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetAdaptiveHigh() {
o.RecallBudgetAdaptiveHigh.Unset()
}
// GetRecallBudgetMin returns the RecallBudgetMin field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetMin() int32 {
if o == nil || IsNil(o.RecallBudgetMin.Get()) {
var ret int32
return ret
}
return *o.RecallBudgetMin.Get()
}
// GetRecallBudgetMinOk returns a tuple with the RecallBudgetMin field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetMinOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetMin.Get(), o.RecallBudgetMin.IsSet()
}
// HasRecallBudgetMin returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetMin() bool {
if o != nil && o.RecallBudgetMin.IsSet() {
return true
}
return false
}
// SetRecallBudgetMin gets a reference to the given NullableInt32 and assigns it to the RecallBudgetMin field.
func (o *BankTemplateConfig) SetRecallBudgetMin(v int32) {
o.RecallBudgetMin.Set(&v)
}
// SetRecallBudgetMinNil sets the value for RecallBudgetMin to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetMinNil() {
o.RecallBudgetMin.Set(nil)
}
// UnsetRecallBudgetMin ensures that no value is present for RecallBudgetMin, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetMin() {
o.RecallBudgetMin.Unset()
}
// GetRecallBudgetMax returns the RecallBudgetMax field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRecallBudgetMax() int32 {
if o == nil || IsNil(o.RecallBudgetMax.Get()) {
var ret int32
return ret
}
return *o.RecallBudgetMax.Get()
}
// GetRecallBudgetMaxOk returns a tuple with the RecallBudgetMax field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *BankTemplateConfig) GetRecallBudgetMaxOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RecallBudgetMax.Get(), o.RecallBudgetMax.IsSet()
}
// HasRecallBudgetMax returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRecallBudgetMax() bool {
if o != nil && o.RecallBudgetMax.IsSet() {
return true
}
return false
}
// SetRecallBudgetMax gets a reference to the given NullableInt32 and assigns it to the RecallBudgetMax field.
func (o *BankTemplateConfig) SetRecallBudgetMax(v int32) {
o.RecallBudgetMax.Set(&v)
}
// SetRecallBudgetMaxNil sets the value for RecallBudgetMax to be an explicit nil
func (o *BankTemplateConfig) SetRecallBudgetMaxNil() {
o.RecallBudgetMax.Set(nil)
}
// UnsetRecallBudgetMax ensures that no value is present for RecallBudgetMax, not even an explicit nil
func (o *BankTemplateConfig) UnsetRecallBudgetMax() {
o.RecallBudgetMax.Unset()
}
func (o BankTemplateConfig) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -591,6 +1381,63 @@ func (o BankTemplateConfig) ToMap() (map[string]interface{}, error) {
if o.EntitiesAllowFreeForm.IsSet() {
toSerialize["entities_allow_free_form"] = o.EntitiesAllowFreeForm.Get()
}
if o.RetainDefaultStrategy.IsSet() {
toSerialize["retain_default_strategy"] = o.RetainDefaultStrategy.Get()
}
if o.RetainStrategies != nil {
toSerialize["retain_strategies"] = o.RetainStrategies
}
if o.RetainChunkBatchSize.IsSet() {
toSerialize["retain_chunk_batch_size"] = o.RetainChunkBatchSize.Get()
}
if o.McpEnabledTools != nil {
toSerialize["mcp_enabled_tools"] = o.McpEnabledTools
}
if o.ConsolidationLlmBatchSize.IsSet() {
toSerialize["consolidation_llm_batch_size"] = o.ConsolidationLlmBatchSize.Get()
}
if o.ConsolidationSourceFactsMaxTokens.IsSet() {
toSerialize["consolidation_source_facts_max_tokens"] = o.ConsolidationSourceFactsMaxTokens.Get()
}
if o.ConsolidationSourceFactsMaxTokensPerObservation.IsSet() {
toSerialize["consolidation_source_facts_max_tokens_per_observation"] = o.ConsolidationSourceFactsMaxTokensPerObservation.Get()
}
if o.MaxObservationsPerScope.IsSet() {
toSerialize["max_observations_per_scope"] = o.MaxObservationsPerScope.Get()
}
if o.ReflectSourceFactsMaxTokens.IsSet() {
toSerialize["reflect_source_facts_max_tokens"] = o.ReflectSourceFactsMaxTokens.Get()
}
if o.LlmGeminiSafetySettings != nil {
toSerialize["llm_gemini_safety_settings"] = o.LlmGeminiSafetySettings
}
if o.RecallBudgetFunction.IsSet() {
toSerialize["recall_budget_function"] = o.RecallBudgetFunction.Get()
}
if o.RecallBudgetFixedLow.IsSet() {
toSerialize["recall_budget_fixed_low"] = o.RecallBudgetFixedLow.Get()
}
if o.RecallBudgetFixedMid.IsSet() {
toSerialize["recall_budget_fixed_mid"] = o.RecallBudgetFixedMid.Get()
}
if o.RecallBudgetFixedHigh.IsSet() {
toSerialize["recall_budget_fixed_high"] = o.RecallBudgetFixedHigh.Get()
}
if o.RecallBudgetAdaptiveLow.IsSet() {
toSerialize["recall_budget_adaptive_low"] = o.RecallBudgetAdaptiveLow.Get()
}
if o.RecallBudgetAdaptiveMid.IsSet() {
toSerialize["recall_budget_adaptive_mid"] = o.RecallBudgetAdaptiveMid.Get()
}
if o.RecallBudgetAdaptiveHigh.IsSet() {
toSerialize["recall_budget_adaptive_high"] = o.RecallBudgetAdaptiveHigh.Get()
}
if o.RecallBudgetMin.IsSet() {
toSerialize["recall_budget_min"] = o.RecallBudgetMin.Get()
}
if o.RecallBudgetMax.IsSet() {
toSerialize["recall_budget_max"] = o.RecallBudgetMax.Get()
}
return toSerialize, nil
}
@@ -5,7 +5,7 @@
HTTP API for Hindsight
The version of the OpenAPI document: 0.5.0
The version of the OpenAPI document: 0.5.2
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
@@ -17,8 +17,8 @@ import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional, Union
from typing_extensions import Annotated
from typing import Optional, Set
from typing_extensions import Self
@@ -39,7 +39,26 @@ class BankTemplateConfig(BaseModel):
disposition_empathy: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None
entity_labels: Optional[List[Dict[str, Any]]] = None
entities_allow_free_form: Optional[StrictBool] = None
__properties: ClassVar[List[str]] = ["reflect_mission", "retain_mission", "retain_extraction_mode", "retain_custom_instructions", "retain_chunk_size", "enable_observations", "observations_mission", "disposition_skepticism", "disposition_literalism", "disposition_empathy", "entity_labels", "entities_allow_free_form"]
retain_default_strategy: Optional[StrictStr] = None
retain_strategies: Optional[Dict[str, Any]] = None
retain_chunk_batch_size: Optional[StrictInt] = None
mcp_enabled_tools: Optional[List[StrictStr]] = None
consolidation_llm_batch_size: Optional[StrictInt] = None
consolidation_source_facts_max_tokens: Optional[StrictInt] = None
consolidation_source_facts_max_tokens_per_observation: Optional[StrictInt] = None
max_observations_per_scope: Optional[StrictInt] = None
reflect_source_facts_max_tokens: Optional[StrictInt] = None
llm_gemini_safety_settings: Optional[List[Any]] = None
recall_budget_function: Optional[StrictStr] = None
recall_budget_fixed_low: Optional[StrictInt] = None
recall_budget_fixed_mid: Optional[StrictInt] = None
recall_budget_fixed_high: Optional[StrictInt] = None
recall_budget_adaptive_low: Optional[Union[StrictFloat, StrictInt]] = None
recall_budget_adaptive_mid: Optional[Union[StrictFloat, StrictInt]] = None
recall_budget_adaptive_high: Optional[Union[StrictFloat, StrictInt]] = None
recall_budget_min: Optional[StrictInt] = None
recall_budget_max: Optional[StrictInt] = None
__properties: ClassVar[List[str]] = ["reflect_mission", "retain_mission", "retain_extraction_mode", "retain_custom_instructions", "retain_chunk_size", "enable_observations", "observations_mission", "disposition_skepticism", "disposition_literalism", "disposition_empathy", "entity_labels", "entities_allow_free_form", "retain_default_strategy", "retain_strategies", "retain_chunk_batch_size", "mcp_enabled_tools", "consolidation_llm_batch_size", "consolidation_source_facts_max_tokens", "consolidation_source_facts_max_tokens_per_observation", "max_observations_per_scope", "reflect_source_facts_max_tokens", "llm_gemini_safety_settings", "recall_budget_function", "recall_budget_fixed_low", "recall_budget_fixed_mid", "recall_budget_fixed_high", "recall_budget_adaptive_low", "recall_budget_adaptive_mid", "recall_budget_adaptive_high", "recall_budget_min", "recall_budget_max"]
model_config = ConfigDict(
populate_by_name=True,
@@ -140,6 +159,101 @@ class BankTemplateConfig(BaseModel):
if self.entities_allow_free_form is None and "entities_allow_free_form" in self.model_fields_set:
_dict['entities_allow_free_form'] = None
# set to None if retain_default_strategy (nullable) is None
# and model_fields_set contains the field
if self.retain_default_strategy is None and "retain_default_strategy" in self.model_fields_set:
_dict['retain_default_strategy'] = None
# set to None if retain_strategies (nullable) is None
# and model_fields_set contains the field
if self.retain_strategies is None and "retain_strategies" in self.model_fields_set:
_dict['retain_strategies'] = None
# set to None if retain_chunk_batch_size (nullable) is None
# and model_fields_set contains the field
if self.retain_chunk_batch_size is None and "retain_chunk_batch_size" in self.model_fields_set:
_dict['retain_chunk_batch_size'] = None
# set to None if mcp_enabled_tools (nullable) is None
# and model_fields_set contains the field
if self.mcp_enabled_tools is None and "mcp_enabled_tools" in self.model_fields_set:
_dict['mcp_enabled_tools'] = None
# set to None if consolidation_llm_batch_size (nullable) is None
# and model_fields_set contains the field
if self.consolidation_llm_batch_size is None and "consolidation_llm_batch_size" in self.model_fields_set:
_dict['consolidation_llm_batch_size'] = None
# set to None if consolidation_source_facts_max_tokens (nullable) is None
# and model_fields_set contains the field
if self.consolidation_source_facts_max_tokens is None and "consolidation_source_facts_max_tokens" in self.model_fields_set:
_dict['consolidation_source_facts_max_tokens'] = None
# set to None if consolidation_source_facts_max_tokens_per_observation (nullable) is None
# and model_fields_set contains the field
if self.consolidation_source_facts_max_tokens_per_observation is None and "consolidation_source_facts_max_tokens_per_observation" in self.model_fields_set:
_dict['consolidation_source_facts_max_tokens_per_observation'] = None
# set to None if max_observations_per_scope (nullable) is None
# and model_fields_set contains the field
if self.max_observations_per_scope is None and "max_observations_per_scope" in self.model_fields_set:
_dict['max_observations_per_scope'] = None
# set to None if reflect_source_facts_max_tokens (nullable) is None
# and model_fields_set contains the field
if self.reflect_source_facts_max_tokens is None and "reflect_source_facts_max_tokens" in self.model_fields_set:
_dict['reflect_source_facts_max_tokens'] = None
# set to None if llm_gemini_safety_settings (nullable) is None
# and model_fields_set contains the field
if self.llm_gemini_safety_settings is None and "llm_gemini_safety_settings" in self.model_fields_set:
_dict['llm_gemini_safety_settings'] = None
# set to None if recall_budget_function (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_function is None and "recall_budget_function" in self.model_fields_set:
_dict['recall_budget_function'] = None
# set to None if recall_budget_fixed_low (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_fixed_low is None and "recall_budget_fixed_low" in self.model_fields_set:
_dict['recall_budget_fixed_low'] = None
# set to None if recall_budget_fixed_mid (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_fixed_mid is None and "recall_budget_fixed_mid" in self.model_fields_set:
_dict['recall_budget_fixed_mid'] = None
# set to None if recall_budget_fixed_high (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_fixed_high is None and "recall_budget_fixed_high" in self.model_fields_set:
_dict['recall_budget_fixed_high'] = None
# set to None if recall_budget_adaptive_low (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_adaptive_low is None and "recall_budget_adaptive_low" in self.model_fields_set:
_dict['recall_budget_adaptive_low'] = None
# set to None if recall_budget_adaptive_mid (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_adaptive_mid is None and "recall_budget_adaptive_mid" in self.model_fields_set:
_dict['recall_budget_adaptive_mid'] = None
# set to None if recall_budget_adaptive_high (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_adaptive_high is None and "recall_budget_adaptive_high" in self.model_fields_set:
_dict['recall_budget_adaptive_high'] = None
# set to None if recall_budget_min (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_min is None and "recall_budget_min" in self.model_fields_set:
_dict['recall_budget_min'] = None
# set to None if recall_budget_max (nullable) is None
# and model_fields_set contains the field
if self.recall_budget_max is None and "recall_budget_max" in self.model_fields_set:
_dict['recall_budget_max'] = None
return _dict
@classmethod
@@ -163,7 +277,26 @@ class BankTemplateConfig(BaseModel):
"disposition_literalism": obj.get("disposition_literalism"),
"disposition_empathy": obj.get("disposition_empathy"),
"entity_labels": obj.get("entity_labels"),
"entities_allow_free_form": obj.get("entities_allow_free_form")
"entities_allow_free_form": obj.get("entities_allow_free_form"),
"retain_default_strategy": obj.get("retain_default_strategy"),
"retain_strategies": obj.get("retain_strategies"),
"retain_chunk_batch_size": obj.get("retain_chunk_batch_size"),
"mcp_enabled_tools": obj.get("mcp_enabled_tools"),
"consolidation_llm_batch_size": obj.get("consolidation_llm_batch_size"),
"consolidation_source_facts_max_tokens": obj.get("consolidation_source_facts_max_tokens"),
"consolidation_source_facts_max_tokens_per_observation": obj.get("consolidation_source_facts_max_tokens_per_observation"),
"max_observations_per_scope": obj.get("max_observations_per_scope"),
"reflect_source_facts_max_tokens": obj.get("reflect_source_facts_max_tokens"),
"llm_gemini_safety_settings": obj.get("llm_gemini_safety_settings"),
"recall_budget_function": obj.get("recall_budget_function"),
"recall_budget_fixed_low": obj.get("recall_budget_fixed_low"),
"recall_budget_fixed_mid": obj.get("recall_budget_fixed_mid"),
"recall_budget_fixed_high": obj.get("recall_budget_fixed_high"),
"recall_budget_adaptive_low": obj.get("recall_budget_adaptive_low"),
"recall_budget_adaptive_mid": obj.get("recall_budget_adaptive_mid"),
"recall_budget_adaptive_high": obj.get("recall_budget_adaptive_high"),
"recall_budget_min": obj.get("recall_budget_min"),
"recall_budget_max": obj.get("recall_budget_max")
})
return _obj
@@ -365,6 +365,14 @@ export type BankStatsResponse = {
* Failed Operations
*/
failed_operations: number;
/**
* Operations By Status
*
* Async operations grouped by status (pending, in_progress, completed, failed, cancelled).
*/
operations_by_status?: {
[key: string]: number;
};
/**
* Last Consolidated At
*
@@ -377,6 +385,12 @@ export type BankStatsResponse = {
* Number of memories not yet processed into observations
*/
pending_consolidation?: number;
/**
* Failed Consolidation
*
* Number of source memories (world/experience) whose consolidation permanently failed and can be retried via the consolidation recovery endpoint.
*/
failed_consolidation?: number;
/**
* Total Observations
*
@@ -468,6 +482,122 @@ export type BankTemplateConfig = {
* Allow entities outside the label vocabulary
*/
entities_allow_free_form?: boolean | null;
/**
* Retain Default Strategy
*
* Name of the default retain strategy (key into retain_strategies map)
*/
retain_default_strategy?: string | null;
/**
* Retain Strategies
*
* Map of retain strategy name to per-strategy config dict
*/
retain_strategies?: {
[key: string]: unknown;
} | null;
/**
* Retain Chunk Batch Size
*
* Max chunks per streaming batch (0 disables batching)
*/
retain_chunk_batch_size?: number | null;
/**
* Mcp Enabled Tools
*
* MCP tool allowlist for this bank (None = all tools)
*/
mcp_enabled_tools?: Array<string> | null;
/**
* Consolidation Llm Batch Size
*
* LLM batch size for observation consolidation
*/
consolidation_llm_batch_size?: number | null;
/**
* Consolidation Source Facts Max Tokens
*
* Max tokens of source facts per consolidation batch
*/
consolidation_source_facts_max_tokens?: number | null;
/**
* Consolidation Source Facts Max Tokens Per Observation
*
* Max tokens of source facts per observation
*/
consolidation_source_facts_max_tokens_per_observation?: number | null;
/**
* Max Observations Per Scope
*
* Max observations to retain per consolidation scope
*/
max_observations_per_scope?: number | null;
/**
* Reflect Source Facts Max Tokens
*
* Max tokens of source facts per reflect call
*/
reflect_source_facts_max_tokens?: number | null;
/**
* Llm Gemini Safety Settings
*
* Per-bank Gemini/VertexAI safety filter settings
*/
llm_gemini_safety_settings?: Array<unknown> | null;
/**
* Recall Budget Function
*
* Recall budget mapping function: 'fixed' or 'adaptive'
*/
recall_budget_function?: string | null;
/**
* Recall Budget Fixed Low
*
* Fixed thinking_budget for budget=low (function='fixed')
*/
recall_budget_fixed_low?: number | null;
/**
* Recall Budget Fixed Mid
*
* Fixed thinking_budget for budget=mid (function='fixed')
*/
recall_budget_fixed_mid?: number | null;
/**
* Recall Budget Fixed High
*
* Fixed thinking_budget for budget=high (function='fixed')
*/
recall_budget_fixed_high?: number | null;
/**
* Recall Budget Adaptive Low
*
* Ratio of max_tokens for budget=low (function='adaptive')
*/
recall_budget_adaptive_low?: number | null;
/**
* Recall Budget Adaptive Mid
*
* Ratio of max_tokens for budget=mid (function='adaptive')
*/
recall_budget_adaptive_mid?: number | null;
/**
* Recall Budget Adaptive High
*
* Ratio of max_tokens for budget=high (function='adaptive')
*/
recall_budget_adaptive_high?: number | null;
/**
* Recall Budget Min
*
* Floor for the adaptive function (after clamping)
*/
recall_budget_min?: number | null;
/**
* Recall Budget Max
*
* Ceiling for the adaptive function (after clamping)
*/
recall_budget_max?: number | null;
};
/**
@@ -1267,6 +1397,38 @@ export type EntityDetailResponse = {
observations: Array<EntityObservationResponse>;
};
/**
* EntityGraphResponse
*
* Response model for entity co-occurrence graph endpoint.
*/
export type EntityGraphResponse = {
/**
* Nodes
*/
nodes: Array<{
[key: string]: unknown;
}>;
/**
* Edges
*/
edges: Array<{
[key: string]: unknown;
}>;
/**
* Total Entities
*/
total_entities: number;
/**
* Total Edges
*/
total_edges: number;
/**
* Limit
*/
limit: number;
};
/**
* EntityIncludeOptions
*
@@ -1596,6 +1758,36 @@ export type ListTagsResponse = {
offset: number;
};
/**
* MemoriesTimeseriesResponse
*
* Time-series of memory ingestion bucketed by time and fact type.
*/
export type MemoriesTimeseriesResponse = {
/**
* Bank Id
*/
bank_id: string;
/**
* Period
*
* One of: 1h, 12h, 1d, 7d, 30d, 90d.
*/
period: string;
/**
* Trunc
*
* Bucket granularity: minute, hour, day.
*/
trunc: string;
/**
* Buckets
*
* Per-bucket counts, always returned fully padded for the requested period.
*/
buckets?: Array<MemoryTimeseriesBucket>;
};
/**
* MemoryItem
*
@@ -1665,6 +1857,38 @@ export type MemoryItem = {
update_mode?: "replace" | "append" | null;
};
/**
* MemoryTimeseriesBucket
*
* One bucket in the memory ingestion time-series.
*/
export type MemoryTimeseriesBucket = {
/**
* Time
*
* Bucket start timestamp in ISO-8601 (UTC).
*/
time: string;
/**
* World
*
* World-fact memories ingested in this bucket.
*/
world?: number;
/**
* Experience
*
* Experience memories ingested in this bucket.
*/
experience?: number;
/**
* Observation
*
* Observations recorded in this bucket.
*/
observation?: number;
};
/**
* MentalModelListResponse
*
@@ -1730,6 +1954,12 @@ export type MentalModelResponse = {
reflect_response?: {
[key: string]: unknown;
} | null;
/**
* Is Stale
*
* True when new memories matching this mental model's tag/fact_type scope have been ingested since last_refreshed_at, or consolidation has pending items. Only populated when detail=full.
*/
is_stale?: boolean | null;
};
/**
@@ -1776,6 +2006,24 @@ export type MentalModelTriggerInput = {
tag_groups?: Array<
TagGroupLeaf | TagGroupAndInput | TagGroupOrInput | TagGroupNotInput
> | null;
/**
* Include Chunks
*
* Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks).
*/
include_chunks?: boolean | null;
/**
* Recall Max Tokens
*
* Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens).
*/
recall_max_tokens?: number | null;
/**
* Recall Chunks Max Tokens
*
* Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens).
*/
recall_chunks_max_tokens?: number | null;
};
/**
@@ -1822,6 +2070,24 @@ export type MentalModelTriggerOutput = {
tag_groups?: Array<
TagGroupLeaf | TagGroupAndOutput | TagGroupOrOutput | TagGroupNotOutput
> | null;
/**
* Include Chunks
*
* Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks).
*/
include_chunks?: boolean | null;
/**
* Recall Max Tokens
*
* Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens).
*/
recall_max_tokens?: number | null;
/**
* Recall Chunks Max Tokens
*
* Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens).
*/
recall_chunks_max_tokens?: number | null;
};
/**
@@ -1908,6 +2174,14 @@ export type OperationStatusResponse = {
* Child operations for batch operations (if applicable)
*/
child_operations?: Array<ChildOperationStatus> | null;
/**
* Task Payload
*
* Raw task payload (params the operation was submitted with). Only populated when include_payload=true.
*/
task_payload?: {
[key: string]: unknown;
} | null;
};
/**
@@ -3185,6 +3459,10 @@ export type ListMemoriesData = {
* Q
*/
q?: string | null;
/**
* Consolidation State
*/
consolidation_state?: string | null;
/**
* Limit
*/
@@ -3435,6 +3713,49 @@ export type GetAgentStatsResponses = {
export type GetAgentStatsResponse =
GetAgentStatsResponses[keyof GetAgentStatsResponses];
export type GetMemoriesTimeseriesData = {
body?: never;
headers?: {
/**
* Authorization
*/
authorization?: string | null;
};
path: {
/**
* Bank Id
*/
bank_id: string;
};
query?: {
/**
* Period
*/
period?: string;
};
url: "/v1/default/banks/{bank_id}/stats/memories-timeseries";
};
export type GetMemoriesTimeseriesErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type GetMemoriesTimeseriesError =
GetMemoriesTimeseriesErrors[keyof GetMemoriesTimeseriesErrors];
export type GetMemoriesTimeseriesResponses = {
/**
* Successful Response
*/
200: MemoriesTimeseriesResponse;
};
export type GetMemoriesTimeseriesResponse =
GetMemoriesTimeseriesResponses[keyof GetMemoriesTimeseriesResponses];
export type ListEntitiesData = {
body?: never;
headers?: {
@@ -3485,6 +3806,57 @@ export type ListEntitiesResponses = {
export type ListEntitiesResponse =
ListEntitiesResponses[keyof ListEntitiesResponses];
export type GetEntityGraphData = {
body?: never;
headers?: {
/**
* Authorization
*/
authorization?: string | null;
};
path: {
/**
* Bank Id
*/
bank_id: string;
};
query?: {
/**
* Limit
*
* Maximum number of co-occurrence edges to return
*/
limit?: number;
/**
* Min Count
*
* Minimum cooccurrence_count to include an edge
*/
min_count?: number;
};
url: "/v1/default/banks/{bank_id}/entities/graph";
};
export type GetEntityGraphErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type GetEntityGraphError =
GetEntityGraphErrors[keyof GetEntityGraphErrors];
export type GetEntityGraphResponses = {
/**
* Successful Response
*/
200: EntityGraphResponse;
};
export type GetEntityGraphResponse =
GetEntityGraphResponses[keyof GetEntityGraphResponses];
export type GetEntityData = {
body?: never;
headers?: {
@@ -4509,7 +4881,14 @@ export type GetOperationStatusData = {
*/
operation_id: string;
};
query?: never;
query?: {
/**
* Include Payload
*
* Include the raw task payload (submission params) in the response. May be large.
*/
include_payload?: boolean;
};
url: "/v1/default/banks/{bank_id}/operations/{operation_id}";
};
@@ -269,6 +269,40 @@ Controls content filtering thresholds for Gemini and VertexAI providers. Accepts
Only applies when `HINDSIGHT_API_LLM_PROVIDER` is `gemini` or `vertexai`.
### recall_budget_function {#recall-budget-configuration}
Selects how the [`recall` request's `budget` parameter](./recall) (`low` / `mid` / `high`) maps to the internal `thinking_budget` integer used by every retrieval method (semantic, BM25, graph, temporal). Two functions are supported:
| Function | Behaviour |
|----------|-----------|
| `fixed` *(default)* | `thinking_budget = recall_budget_fixed_<level>` — independent of `max_tokens`. Preserves legacy behavior. |
| `adaptive` | `thinking_budget = round(max_tokens * recall_budget_adaptive_<level>)`, clamped to `[recall_budget_min, recall_budget_max]`. Retrieval breadth scales with the requested output size. |
```json
{
"recall_budget_function": "adaptive",
"recall_budget_adaptive_low": 0.05,
"recall_budget_adaptive_mid": 0.1,
"recall_budget_adaptive_high": 0.3,
"recall_budget_min": 30,
"recall_budget_max": 1500
}
```
### recall_budget_fixed_low / recall_budget_fixed_mid / recall_budget_fixed_high
When `recall_budget_function` is `fixed` (the default), these positive integers are used directly as the per-method retrieval limit for each `budget` level. Defaults: `100` / `300` / `1000` — exactly matching the legacy hardcoded mapping.
### recall_budget_adaptive_low / recall_budget_adaptive_mid / recall_budget_adaptive_high
When `recall_budget_function` is `adaptive`, these positive ratios multiply the request's `max_tokens` to derive the per-method retrieval limit. Defaults: `0.025` / `0.075` / `0.25` — chosen to roughly match the fixed defaults at `max_tokens = 4096`.
### recall_budget_min / recall_budget_max
Floor and ceiling applied to the result of the adaptive function (after the ratio multiplication). Both must be positive integers and `min ≤ max`. Defaults: `20` / `2000`.
See [Recall budget mapping](/developer/configuration#recall-budget-mapping) for environment variable names and full defaults.
---
## Updating Configuration
@@ -685,6 +685,27 @@ For advanced authentication (JWT, OAuth, multi-tenant schemas), implement a cust
- **`link_expansion`** (default): Fast graph expansion from semantic seeds via entity co-occurrence, semantic kNN, and causal links. Target latency under 100ms.
#### Recall budget mapping
The recall request takes a `budget` parameter (`low` / `mid` / `high`, default `mid`) that maps to an integer `thinking_budget` used by every retrieval method (semantic, BM25, graph, temporal). These knobs control that mapping. They are hierarchical — overridable per bank via the [config API](#hierarchical-configuration).
Two functions are available:
- **`fixed`** (default — preserves legacy behavior): `thinking_budget = recall_budget_fixed_<level>` (independent of `max_tokens`).
- **`adaptive`**: `thinking_budget = round(max_tokens * recall_budget_adaptive_<level>)`, clamped to `[recall_budget_min, recall_budget_max]`. Useful when callers vary `max_tokens` and you want retrieval breadth to scale with the requested output size.
| Variable | Description | Default |
|----------|-------------|---------|
| `HINDSIGHT_API_RECALL_BUDGET_FUNCTION` | Mapping function: `fixed` or `adaptive`. | `fixed` |
| `HINDSIGHT_API_RECALL_BUDGET_FIXED_LOW` | Items per retrieval method per fact type when `budget=low` and function is `fixed`. | `100` |
| `HINDSIGHT_API_RECALL_BUDGET_FIXED_MID` | Items per retrieval method per fact type when `budget=mid` and function is `fixed`. | `300` |
| `HINDSIGHT_API_RECALL_BUDGET_FIXED_HIGH` | Items per retrieval method per fact type when `budget=high` and function is `fixed`. | `1000` |
| `HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_LOW` | Ratio of request `max_tokens` used when `budget=low` and function is `adaptive`. | `0.025` |
| `HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_MID` | Ratio of request `max_tokens` used when `budget=mid` and function is `adaptive`. | `0.075` |
| `HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_HIGH` | Ratio of request `max_tokens` used when `budget=high` and function is `adaptive`. | `0.25` |
| `HINDSIGHT_API_RECALL_BUDGET_MIN` | Floor for the adaptive function (after clamping). | `20` |
| `HINDSIGHT_API_RECALL_BUDGET_MAX` | Ceiling for the adaptive function (after clamping). | `2000` |
### Retain
Controls the retain (memory ingestion) pipeline.
+24
View File
@@ -125,6 +125,30 @@
}
]
}
},
{
"id": "rag",
"name": "RAG Mode",
"description": "Low-latency retrieval-only mode. Stores raw chunks without LLM processing and retrieves via semantic + BM25 search only. No fact extraction, no observations, no graph traversal, no temporal filtering, no reranking.",
"category": "retrieval",
"integrations": [
"litellm",
"langgraph",
"pydantic-ai",
"ai-sdk",
"llamaindex",
"local-mcp"
],
"manifest": {
"version": "1",
"bank": {
"retain_extraction_mode": "chunks",
"enable_observations": false,
"enable_temporal_extraction": false,
"enable_graph_retrieval": false,
"enable_reranking": false
}
}
}
]
}
+2 -1
View File
@@ -5,7 +5,7 @@ import templatesData from '@site/src/data/templates.json';
import integrationsData from '@site/src/data/integrations.json';
import styles from './index.module.css';
const CATEGORIES = ['all', 'chat', 'coding', 'assistant'] as const;
const CATEGORIES = ['all', 'chat', 'coding', 'assistant', 'retrieval'] as const;
type Category = (typeof CATEGORIES)[number];
const CATEGORY_LABELS: Record<Category, string> = {
@@ -13,6 +13,7 @@ const CATEGORY_LABELS: Record<Category, string> = {
chat: 'Chat',
coding: 'Coding',
assistant: 'Assistant',
retrieval: 'Retrieval',
};
// Build a lookup from integration ID to icon path and name
+293 -1
View File
@@ -143,7 +143,8 @@
"anyOf": [
{
"items": {
"type": "string"
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
@@ -167,6 +168,258 @@
"default": null,
"description": "Allow entities outside the label vocabulary",
"title": "Entities Allow Free Form"
},
"retain_default_strategy": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Name of the default retain strategy (key into retain_strategies map)",
"title": "Retain Default Strategy"
},
"retain_strategies": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Map of retain strategy name to per-strategy config dict",
"title": "Retain Strategies"
},
"retain_chunk_batch_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Max chunks per streaming batch (0 disables batching)",
"title": "Retain Chunk Batch Size"
},
"mcp_enabled_tools": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "MCP tool allowlist for this bank (None = all tools)",
"title": "Mcp Enabled Tools"
},
"consolidation_llm_batch_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "LLM batch size for observation consolidation",
"title": "Consolidation Llm Batch Size"
},
"consolidation_source_facts_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Max tokens of source facts per consolidation batch",
"title": "Consolidation Source Facts Max Tokens"
},
"consolidation_source_facts_max_tokens_per_observation": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Max tokens of source facts per observation",
"title": "Consolidation Source Facts Max Tokens Per Observation"
},
"max_observations_per_scope": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Max observations to retain per consolidation scope",
"title": "Max Observations Per Scope"
},
"reflect_source_facts_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Max tokens of source facts per reflect call",
"title": "Reflect Source Facts Max Tokens"
},
"llm_gemini_safety_settings": {
"anyOf": [
{
"items": {},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Per-bank Gemini/VertexAI safety filter settings",
"title": "Llm Gemini Safety Settings"
},
"recall_budget_function": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Recall budget mapping function: 'fixed' or 'adaptive'",
"title": "Recall Budget Function"
},
"recall_budget_fixed_low": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Fixed thinking_budget for budget=low (function='fixed')",
"title": "Recall Budget Fixed Low"
},
"recall_budget_fixed_mid": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Fixed thinking_budget for budget=mid (function='fixed')",
"title": "Recall Budget Fixed Mid"
},
"recall_budget_fixed_high": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Fixed thinking_budget for budget=high (function='fixed')",
"title": "Recall Budget Fixed High"
},
"recall_budget_adaptive_low": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Ratio of max_tokens for budget=low (function='adaptive')",
"title": "Recall Budget Adaptive Low"
},
"recall_budget_adaptive_mid": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Ratio of max_tokens for budget=mid (function='adaptive')",
"title": "Recall Budget Adaptive Mid"
},
"recall_budget_adaptive_high": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Ratio of max_tokens for budget=high (function='adaptive')",
"title": "Recall Budget Adaptive High"
},
"recall_budget_min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Floor for the adaptive function (after clamping)",
"title": "Recall Budget Min"
},
"recall_budget_max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Ceiling for the adaptive function (after clamping)",
"title": "Recall Budget Max"
}
},
"title": "BankTemplateConfig",
@@ -362,6 +615,45 @@
"default": null,
"description": "Compound boolean tag expressions to use during refresh instead of the model's own tags. When set, these tag groups are passed to reflect and the model's flat tags are NOT used for filtering. Supports nested and/or/not expressions for complex tag-based scoping.",
"title": "Tag Groups"
},
"include_chunks": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks).",
"title": "Include Chunks"
},
"recall_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens).",
"title": "Recall Max Tokens"
},
"recall_chunks_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens).",
"title": "Recall Chunks Max Tokens"
}
},
"title": "MentalModelTrigger",
+672 -1
View File
@@ -10,7 +10,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "0.5.0"
"version": "0.5.2"
},
"paths": {
"/health": {
@@ -255,6 +255,22 @@
"title": "Q"
}
},
{
"name": "consolidation_state",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Consolidation State"
}
},
{
"name": "limit",
"in": "query",
@@ -695,6 +711,75 @@
}
}
},
"/v1/default/banks/{bank_id}/stats/memories-timeseries": {
"get": {
"tags": [
"Banks"
],
"summary": "Memory ingestion time-series",
"description": "Memories ingested over a period, bucketed by time and broken down by fact type.",
"operationId": "get_memories_timeseries",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "period",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "7d",
"title": "Period"
}
},
{
"name": "authorization",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Authorization"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MemoriesTimeseriesResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/entities": {
"get": {
"tags": [
@@ -778,6 +863,89 @@
}
}
},
"/v1/default/banks/{bank_id}/entities/graph": {
"get": {
"tags": [
"Entities"
],
"summary": "Get entity co-occurrence graph",
"description": "Return a graph of entities (nodes) and their co-occurrences (edges) for visualization.",
"operationId": "get_entity_graph",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"description": "Maximum number of co-occurrence edges to return",
"default": 1000,
"title": "Limit"
},
"description": "Maximum number of co-occurrence edges to return"
},
{
"name": "min_count",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"description": "Minimum cooccurrence_count to include an edge",
"default": 1,
"title": "Min Count"
},
"description": "Minimum cooccurrence_count to include an edge"
},
{
"name": "authorization",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Authorization"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EntityGraphResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/entities/{entity_id}": {
"get": {
"tags": [
@@ -2534,6 +2702,18 @@
"title": "Operation Id"
}
},
{
"name": "include_payload",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"description": "Include the raw task payload (submission params) in the response. May be large.",
"default": false,
"title": "Include Payload"
},
"description": "Include the raw task payload (submission params) in the response. May be large."
},
{
"name": "authorization",
"in": "header",
@@ -5069,6 +5249,14 @@
"type": "integer",
"title": "Failed Operations"
},
"operations_by_status": {
"additionalProperties": {
"type": "integer"
},
"type": "object",
"title": "Operations By Status",
"description": "Async operations grouped by status (pending, in_progress, completed, failed, cancelled)."
},
"last_consolidated_at": {
"anyOf": [
{
@@ -5087,6 +5275,12 @@
"description": "Number of memories not yet processed into observations",
"default": 0
},
"failed_consolidation": {
"type": "integer",
"title": "Failed Consolidation",
"description": "Number of source memories (world/experience) whose consolidation permanently failed and can be retried via the consolidation recovery endpoint.",
"default": 0
},
"total_observations": {
"type": "integer",
"title": "Total Observations",
@@ -5111,6 +5305,7 @@
"description": "Response model for bank statistics endpoint.",
"example": {
"bank_id": "user123",
"failed_consolidation": 0,
"failed_operations": 0,
"last_consolidated_at": "2024-01-15T10:30:00Z",
"links_breakdown": {
@@ -5298,6 +5493,239 @@
],
"title": "Entities Allow Free Form",
"description": "Allow entities outside the label vocabulary"
},
"retain_default_strategy": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Default Strategy",
"description": "Name of the default retain strategy (key into retain_strategies map)"
},
"retain_strategies": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Retain Strategies",
"description": "Map of retain strategy name to per-strategy config dict"
},
"retain_chunk_batch_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Retain Chunk Batch Size",
"description": "Max chunks per streaming batch (0 disables batching)"
},
"mcp_enabled_tools": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Mcp Enabled Tools",
"description": "MCP tool allowlist for this bank (None = all tools)"
},
"consolidation_llm_batch_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Consolidation Llm Batch Size",
"description": "LLM batch size for observation consolidation"
},
"consolidation_source_facts_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Consolidation Source Facts Max Tokens",
"description": "Max tokens of source facts per consolidation batch"
},
"consolidation_source_facts_max_tokens_per_observation": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Consolidation Source Facts Max Tokens Per Observation",
"description": "Max tokens of source facts per observation"
},
"max_observations_per_scope": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Max Observations Per Scope",
"description": "Max observations to retain per consolidation scope"
},
"reflect_source_facts_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Reflect Source Facts Max Tokens",
"description": "Max tokens of source facts per reflect call"
},
"llm_gemini_safety_settings": {
"anyOf": [
{
"items": {},
"type": "array"
},
{
"type": "null"
}
],
"title": "Llm Gemini Safety Settings",
"description": "Per-bank Gemini/VertexAI safety filter settings"
},
"recall_budget_function": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Recall Budget Function",
"description": "Recall budget mapping function: 'fixed' or 'adaptive'"
},
"recall_budget_fixed_low": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Fixed Low",
"description": "Fixed thinking_budget for budget=low (function='fixed')"
},
"recall_budget_fixed_mid": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Fixed Mid",
"description": "Fixed thinking_budget for budget=mid (function='fixed')"
},
"recall_budget_fixed_high": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Fixed High",
"description": "Fixed thinking_budget for budget=high (function='fixed')"
},
"recall_budget_adaptive_low": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Recall Budget Adaptive Low",
"description": "Ratio of max_tokens for budget=low (function='adaptive')"
},
"recall_budget_adaptive_mid": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Recall Budget Adaptive Mid",
"description": "Ratio of max_tokens for budget=mid (function='adaptive')"
},
"recall_budget_adaptive_high": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Recall Budget Adaptive High",
"description": "Ratio of max_tokens for budget=high (function='adaptive')"
},
"recall_budget_min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Min",
"description": "Floor for the adaptive function (after clamping)"
},
"recall_budget_max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Max",
"description": "Ceiling for the adaptive function (after clamping)"
}
},
"type": "object",
@@ -6546,6 +6974,85 @@
]
}
},
"EntityGraphResponse": {
"properties": {
"nodes": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array",
"title": "Nodes"
},
"edges": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array",
"title": "Edges"
},
"total_entities": {
"type": "integer",
"title": "Total Entities"
},
"total_edges": {
"type": "integer",
"title": "Total Edges"
},
"limit": {
"type": "integer",
"title": "Limit"
}
},
"type": "object",
"required": [
"nodes",
"edges",
"total_entities",
"total_edges",
"limit"
],
"title": "EntityGraphResponse",
"description": "Response model for entity co-occurrence graph endpoint.",
"example": {
"edges": [
{
"data": {
"color": "#ffd700",
"id": "uuid-1-uuid-2",
"lastCooccurred": "2024-02-01T14:00:00Z",
"lineStyle": "solid",
"linkType": "cooccurrence",
"source": "uuid-1",
"target": "uuid-2",
"weight": 5
}
}
],
"limit": 1000,
"nodes": [
{
"data": {
"color": "#42a5f5",
"id": "uuid-1",
"label": "Alice",
"mentionCount": 12
}
},
{
"data": {
"color": "#42a5f5",
"id": "uuid-2",
"label": "Google",
"mentionCount": 8
}
}
],
"total_edges": 1,
"total_entities": 2
}
},
"EntityIncludeOptions": {
"properties": {
"max_tokens": {
@@ -7106,6 +7613,40 @@
"total": 25
}
},
"MemoriesTimeseriesResponse": {
"properties": {
"bank_id": {
"type": "string",
"title": "Bank Id"
},
"period": {
"type": "string",
"title": "Period",
"description": "One of: 1h, 12h, 1d, 7d, 30d, 90d."
},
"trunc": {
"type": "string",
"title": "Trunc",
"description": "Bucket granularity: minute, hour, day."
},
"buckets": {
"items": {
"$ref": "#/components/schemas/MemoryTimeseriesBucket"
},
"type": "array",
"title": "Buckets",
"description": "Per-bucket counts, always returned fully padded for the requested period."
}
},
"type": "object",
"required": [
"bank_id",
"period",
"trunc"
],
"title": "MemoriesTimeseriesResponse",
"description": "Time-series of memory ingestion bucketed by time and fact type."
},
"MemoryItem": {
"properties": {
"content": {
@@ -7280,6 +7821,39 @@
"timestamp": "2024-01-15T10:30:00Z"
}
},
"MemoryTimeseriesBucket": {
"properties": {
"time": {
"type": "string",
"title": "Time",
"description": "Bucket start timestamp in ISO-8601 (UTC)."
},
"world": {
"type": "integer",
"title": "World",
"description": "World-fact memories ingested in this bucket.",
"default": 0
},
"experience": {
"type": "integer",
"title": "Experience",
"description": "Experience memories ingested in this bucket.",
"default": 0
},
"observation": {
"type": "integer",
"title": "Observation",
"description": "Observations recorded in this bucket.",
"default": 0
}
},
"type": "object",
"required": [
"time"
],
"title": "MemoryTimeseriesBucket",
"description": "One bucket in the memory ingestion time-series."
},
"MentalModelListResponse": {
"properties": {
"items": {
@@ -7397,6 +7971,18 @@
],
"title": "Reflect Response",
"description": "Full reflect API response payload including based_on facts and observations"
},
"is_stale": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Is Stale",
"description": "True when new memories matching this mental model's tag/fact_type scope have been ingested since last_refreshed_at, or consolidation has pending items. Only populated when detail=full."
}
},
"type": "object",
@@ -7502,6 +8088,42 @@
],
"title": "Tag Groups",
"description": "Compound boolean tag expressions to use during refresh instead of the model's own tags. When set, these tag groups are passed to reflect and the model's flat tags are NOT used for filtering. Supports nested and/or/not expressions for complex tag-based scoping."
},
"include_chunks": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Include Chunks",
"description": "Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks)."
},
"recall_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Max Tokens",
"description": "Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens)."
},
"recall_chunks_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Chunks Max Tokens",
"description": "Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens)."
}
},
"type": "object",
@@ -7602,6 +8224,42 @@
],
"title": "Tag Groups",
"description": "Compound boolean tag expressions to use during refresh instead of the model's own tags. When set, these tag groups are passed to reflect and the model's flat tags are NOT used for filtering. Supports nested and/or/not expressions for complex tag-based scoping."
},
"include_chunks": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Include Chunks",
"description": "Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks)."
},
"recall_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Max Tokens",
"description": "Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens)."
},
"recall_chunks_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Chunks Max Tokens",
"description": "Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens)."
}
},
"type": "object",
@@ -7770,6 +8428,19 @@
],
"title": "Child Operations",
"description": "Child operations for batch operations (if applicable)"
},
"task_payload": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Task Payload",
"description": "Raw task payload (params the operation was submitted with). Only populated when include_payload=true."
}
},
"type": "object",
@@ -287,6 +287,40 @@ Controls content filtering thresholds for Gemini and VertexAI providers. Accepts
Only applies when `HINDSIGHT_API_LLM_PROVIDER` is `gemini` or `vertexai`.
### recall_budget_function {#recall-budget-configuration}
Selects how the [`recall` request's `budget` parameter](./recall) (`low` / `mid` / `high`) maps to the internal `thinking_budget` integer used by every retrieval method (semantic, BM25, graph, temporal). Two functions are supported:
| Function | Behaviour |
|----------|-----------|
| `fixed` *(default)* | `thinking_budget = recall_budget_fixed_<level>` — independent of `max_tokens`. Preserves legacy behavior. |
| `adaptive` | `thinking_budget = round(max_tokens * recall_budget_adaptive_<level>)`, clamped to `[recall_budget_min, recall_budget_max]`. Retrieval breadth scales with the requested output size. |
```json
{
"recall_budget_function": "adaptive",
"recall_budget_adaptive_low": 0.05,
"recall_budget_adaptive_mid": 0.1,
"recall_budget_adaptive_high": 0.3,
"recall_budget_min": 30,
"recall_budget_max": 1500
}
```
### recall_budget_fixed_low / recall_budget_fixed_mid / recall_budget_fixed_high
When `recall_budget_function` is `fixed` (the default), these positive integers are used directly as the per-method retrieval limit for each `budget` level. Defaults: `100` / `300` / `1000` — exactly matching the legacy hardcoded mapping.
### recall_budget_adaptive_low / recall_budget_adaptive_mid / recall_budget_adaptive_high
When `recall_budget_function` is `adaptive`, these positive ratios multiply the request's `max_tokens` to derive the per-method retrieval limit. Defaults: `0.025` / `0.075` / `0.25` — chosen to roughly match the fixed defaults at `max_tokens = 4096`.
### recall_budget_min / recall_budget_max
Floor and ceiling applied to the result of the adaptive function (after the ratio multiplication). Both must be positive integers and `min ≤ max`. Defaults: `20` / `2000`.
See [Recall budget mapping](../configuration.md#recall-budget-mapping) for environment variable names and full defaults.
---
## Updating Configuration
@@ -685,6 +685,27 @@ For advanced authentication (JWT, OAuth, multi-tenant schemas), implement a cust
- **`link_expansion`** (default): Fast graph expansion from semantic seeds via entity co-occurrence, semantic kNN, and causal links. Target latency under 100ms.
#### Recall budget mapping
The recall request takes a `budget` parameter (`low` / `mid` / `high`, default `mid`) that maps to an integer `thinking_budget` used by every retrieval method (semantic, BM25, graph, temporal). These knobs control that mapping. They are hierarchical — overridable per bank via the [config API](#hierarchical-configuration).
Two functions are available:
- **`fixed`** (default — preserves legacy behavior): `thinking_budget = recall_budget_fixed_<level>` (independent of `max_tokens`).
- **`adaptive`**: `thinking_budget = round(max_tokens * recall_budget_adaptive_<level>)`, clamped to `[recall_budget_min, recall_budget_max]`. Useful when callers vary `max_tokens` and you want retrieval breadth to scale with the requested output size.
| Variable | Description | Default |
|----------|-------------|---------|
| `HINDSIGHT_API_RECALL_BUDGET_FUNCTION` | Mapping function: `fixed` or `adaptive`. | `fixed` |
| `HINDSIGHT_API_RECALL_BUDGET_FIXED_LOW` | Items per retrieval method per fact type when `budget=low` and function is `fixed`. | `100` |
| `HINDSIGHT_API_RECALL_BUDGET_FIXED_MID` | Items per retrieval method per fact type when `budget=mid` and function is `fixed`. | `300` |
| `HINDSIGHT_API_RECALL_BUDGET_FIXED_HIGH` | Items per retrieval method per fact type when `budget=high` and function is `fixed`. | `1000` |
| `HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_LOW` | Ratio of request `max_tokens` used when `budget=low` and function is `adaptive`. | `0.025` |
| `HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_MID` | Ratio of request `max_tokens` used when `budget=mid` and function is `adaptive`. | `0.075` |
| `HINDSIGHT_API_RECALL_BUDGET_ADAPTIVE_HIGH` | Ratio of request `max_tokens` used when `budget=high` and function is `adaptive`. | `0.25` |
| `HINDSIGHT_API_RECALL_BUDGET_MIN` | Floor for the adaptive function (after clamping). | `20` |
| `HINDSIGHT_API_RECALL_BUDGET_MAX` | Ceiling for the adaptive function (after clamping). | `2000` |
### Retain
Controls the retain (memory ingestion) pipeline.
+672 -1
View File
@@ -10,7 +10,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "0.5.0"
"version": "0.5.2"
},
"paths": {
"/health": {
@@ -255,6 +255,22 @@
"title": "Q"
}
},
{
"name": "consolidation_state",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Consolidation State"
}
},
{
"name": "limit",
"in": "query",
@@ -695,6 +711,75 @@
}
}
},
"/v1/default/banks/{bank_id}/stats/memories-timeseries": {
"get": {
"tags": [
"Banks"
],
"summary": "Memory ingestion time-series",
"description": "Memories ingested over a period, bucketed by time and broken down by fact type.",
"operationId": "get_memories_timeseries",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "period",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "7d",
"title": "Period"
}
},
{
"name": "authorization",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Authorization"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MemoriesTimeseriesResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/entities": {
"get": {
"tags": [
@@ -778,6 +863,89 @@
}
}
},
"/v1/default/banks/{bank_id}/entities/graph": {
"get": {
"tags": [
"Entities"
],
"summary": "Get entity co-occurrence graph",
"description": "Return a graph of entities (nodes) and their co-occurrences (edges) for visualization.",
"operationId": "get_entity_graph",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"description": "Maximum number of co-occurrence edges to return",
"default": 1000,
"title": "Limit"
},
"description": "Maximum number of co-occurrence edges to return"
},
{
"name": "min_count",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"description": "Minimum cooccurrence_count to include an edge",
"default": 1,
"title": "Min Count"
},
"description": "Minimum cooccurrence_count to include an edge"
},
{
"name": "authorization",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Authorization"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EntityGraphResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/entities/{entity_id}": {
"get": {
"tags": [
@@ -2534,6 +2702,18 @@
"title": "Operation Id"
}
},
{
"name": "include_payload",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"description": "Include the raw task payload (submission params) in the response. May be large.",
"default": false,
"title": "Include Payload"
},
"description": "Include the raw task payload (submission params) in the response. May be large."
},
{
"name": "authorization",
"in": "header",
@@ -5069,6 +5249,14 @@
"type": "integer",
"title": "Failed Operations"
},
"operations_by_status": {
"additionalProperties": {
"type": "integer"
},
"type": "object",
"title": "Operations By Status",
"description": "Async operations grouped by status (pending, in_progress, completed, failed, cancelled)."
},
"last_consolidated_at": {
"anyOf": [
{
@@ -5087,6 +5275,12 @@
"description": "Number of memories not yet processed into observations",
"default": 0
},
"failed_consolidation": {
"type": "integer",
"title": "Failed Consolidation",
"description": "Number of source memories (world/experience) whose consolidation permanently failed and can be retried via the consolidation recovery endpoint.",
"default": 0
},
"total_observations": {
"type": "integer",
"title": "Total Observations",
@@ -5111,6 +5305,7 @@
"description": "Response model for bank statistics endpoint.",
"example": {
"bank_id": "user123",
"failed_consolidation": 0,
"failed_operations": 0,
"last_consolidated_at": "2024-01-15T10:30:00Z",
"links_breakdown": {
@@ -5298,6 +5493,239 @@
],
"title": "Entities Allow Free Form",
"description": "Allow entities outside the label vocabulary"
},
"retain_default_strategy": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Default Strategy",
"description": "Name of the default retain strategy (key into retain_strategies map)"
},
"retain_strategies": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Retain Strategies",
"description": "Map of retain strategy name to per-strategy config dict"
},
"retain_chunk_batch_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Retain Chunk Batch Size",
"description": "Max chunks per streaming batch (0 disables batching)"
},
"mcp_enabled_tools": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Mcp Enabled Tools",
"description": "MCP tool allowlist for this bank (None = all tools)"
},
"consolidation_llm_batch_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Consolidation Llm Batch Size",
"description": "LLM batch size for observation consolidation"
},
"consolidation_source_facts_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Consolidation Source Facts Max Tokens",
"description": "Max tokens of source facts per consolidation batch"
},
"consolidation_source_facts_max_tokens_per_observation": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Consolidation Source Facts Max Tokens Per Observation",
"description": "Max tokens of source facts per observation"
},
"max_observations_per_scope": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Max Observations Per Scope",
"description": "Max observations to retain per consolidation scope"
},
"reflect_source_facts_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Reflect Source Facts Max Tokens",
"description": "Max tokens of source facts per reflect call"
},
"llm_gemini_safety_settings": {
"anyOf": [
{
"items": {},
"type": "array"
},
{
"type": "null"
}
],
"title": "Llm Gemini Safety Settings",
"description": "Per-bank Gemini/VertexAI safety filter settings"
},
"recall_budget_function": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Recall Budget Function",
"description": "Recall budget mapping function: 'fixed' or 'adaptive'"
},
"recall_budget_fixed_low": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Fixed Low",
"description": "Fixed thinking_budget for budget=low (function='fixed')"
},
"recall_budget_fixed_mid": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Fixed Mid",
"description": "Fixed thinking_budget for budget=mid (function='fixed')"
},
"recall_budget_fixed_high": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Fixed High",
"description": "Fixed thinking_budget for budget=high (function='fixed')"
},
"recall_budget_adaptive_low": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Recall Budget Adaptive Low",
"description": "Ratio of max_tokens for budget=low (function='adaptive')"
},
"recall_budget_adaptive_mid": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Recall Budget Adaptive Mid",
"description": "Ratio of max_tokens for budget=mid (function='adaptive')"
},
"recall_budget_adaptive_high": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Recall Budget Adaptive High",
"description": "Ratio of max_tokens for budget=high (function='adaptive')"
},
"recall_budget_min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Min",
"description": "Floor for the adaptive function (after clamping)"
},
"recall_budget_max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Budget Max",
"description": "Ceiling for the adaptive function (after clamping)"
}
},
"type": "object",
@@ -6546,6 +6974,85 @@
]
}
},
"EntityGraphResponse": {
"properties": {
"nodes": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array",
"title": "Nodes"
},
"edges": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array",
"title": "Edges"
},
"total_entities": {
"type": "integer",
"title": "Total Entities"
},
"total_edges": {
"type": "integer",
"title": "Total Edges"
},
"limit": {
"type": "integer",
"title": "Limit"
}
},
"type": "object",
"required": [
"nodes",
"edges",
"total_entities",
"total_edges",
"limit"
],
"title": "EntityGraphResponse",
"description": "Response model for entity co-occurrence graph endpoint.",
"example": {
"edges": [
{
"data": {
"color": "#ffd700",
"id": "uuid-1-uuid-2",
"lastCooccurred": "2024-02-01T14:00:00Z",
"lineStyle": "solid",
"linkType": "cooccurrence",
"source": "uuid-1",
"target": "uuid-2",
"weight": 5
}
}
],
"limit": 1000,
"nodes": [
{
"data": {
"color": "#42a5f5",
"id": "uuid-1",
"label": "Alice",
"mentionCount": 12
}
},
{
"data": {
"color": "#42a5f5",
"id": "uuid-2",
"label": "Google",
"mentionCount": 8
}
}
],
"total_edges": 1,
"total_entities": 2
}
},
"EntityIncludeOptions": {
"properties": {
"max_tokens": {
@@ -7106,6 +7613,40 @@
"total": 25
}
},
"MemoriesTimeseriesResponse": {
"properties": {
"bank_id": {
"type": "string",
"title": "Bank Id"
},
"period": {
"type": "string",
"title": "Period",
"description": "One of: 1h, 12h, 1d, 7d, 30d, 90d."
},
"trunc": {
"type": "string",
"title": "Trunc",
"description": "Bucket granularity: minute, hour, day."
},
"buckets": {
"items": {
"$ref": "#/components/schemas/MemoryTimeseriesBucket"
},
"type": "array",
"title": "Buckets",
"description": "Per-bucket counts, always returned fully padded for the requested period."
}
},
"type": "object",
"required": [
"bank_id",
"period",
"trunc"
],
"title": "MemoriesTimeseriesResponse",
"description": "Time-series of memory ingestion bucketed by time and fact type."
},
"MemoryItem": {
"properties": {
"content": {
@@ -7280,6 +7821,39 @@
"timestamp": "2024-01-15T10:30:00Z"
}
},
"MemoryTimeseriesBucket": {
"properties": {
"time": {
"type": "string",
"title": "Time",
"description": "Bucket start timestamp in ISO-8601 (UTC)."
},
"world": {
"type": "integer",
"title": "World",
"description": "World-fact memories ingested in this bucket.",
"default": 0
},
"experience": {
"type": "integer",
"title": "Experience",
"description": "Experience memories ingested in this bucket.",
"default": 0
},
"observation": {
"type": "integer",
"title": "Observation",
"description": "Observations recorded in this bucket.",
"default": 0
}
},
"type": "object",
"required": [
"time"
],
"title": "MemoryTimeseriesBucket",
"description": "One bucket in the memory ingestion time-series."
},
"MentalModelListResponse": {
"properties": {
"items": {
@@ -7397,6 +7971,18 @@
],
"title": "Reflect Response",
"description": "Full reflect API response payload including based_on facts and observations"
},
"is_stale": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Is Stale",
"description": "True when new memories matching this mental model's tag/fact_type scope have been ingested since last_refreshed_at, or consolidation has pending items. Only populated when detail=full."
}
},
"type": "object",
@@ -7502,6 +8088,42 @@
],
"title": "Tag Groups",
"description": "Compound boolean tag expressions to use during refresh instead of the model's own tags. When set, these tag groups are passed to reflect and the model's flat tags are NOT used for filtering. Supports nested and/or/not expressions for complex tag-based scoping."
},
"include_chunks": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Include Chunks",
"description": "Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks)."
},
"recall_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Max Tokens",
"description": "Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens)."
},
"recall_chunks_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Chunks Max Tokens",
"description": "Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens)."
}
},
"type": "object",
@@ -7602,6 +8224,42 @@
],
"title": "Tag Groups",
"description": "Compound boolean tag expressions to use during refresh instead of the model's own tags. When set, these tag groups are passed to reflect and the model's flat tags are NOT used for filtering. Supports nested and/or/not expressions for complex tag-based scoping."
},
"include_chunks": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Include Chunks",
"description": "Override whether the internal recall used during refresh returns raw chunk text. None means use the bank/global config default (recall_include_chunks)."
},
"recall_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Max Tokens",
"description": "Override the token budget for facts returned by the internal recall during refresh. None means use the bank/global config default (recall_max_tokens)."
},
"recall_chunks_max_tokens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Recall Chunks Max Tokens",
"description": "Override the token budget for raw chunks returned by the internal recall during refresh. None means use the bank/global config default (recall_chunks_max_tokens)."
}
},
"type": "object",
@@ -7770,6 +8428,19 @@
],
"title": "Child Operations",
"description": "Child operations for batch operations (if applicable)"
},
"task_payload": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Task Payload",
"description": "Raw task payload (params the operation was submitted with). Only populated when include_payload=true."
}
},
"type": "object",