Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e03af83dc2 | ||
|
|
cd71cf9105 |
@@ -715,25 +715,32 @@ class LLMProvider:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def for_memory(cls) -> "LLMProvider":
|
||||
"""Create provider for memory operations from environment variables."""
|
||||
provider = os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq")
|
||||
api_key = os.getenv("HINDSIGHT_API_LLM_API_KEY", "")
|
||||
def from_env(cls) -> "LLMProvider":
|
||||
"""Create provider from environment variables using config.py constants."""
|
||||
from ..config import (
|
||||
DEFAULT_LLM_MODEL,
|
||||
DEFAULT_LLM_PROVIDER,
|
||||
ENV_LLM_API_KEY,
|
||||
ENV_LLM_BASE_URL,
|
||||
ENV_LLM_EXTRA_BODY,
|
||||
ENV_LLM_MODEL,
|
||||
ENV_LLM_PROVIDER,
|
||||
)
|
||||
|
||||
provider = os.getenv(ENV_LLM_PROVIDER, DEFAULT_LLM_PROVIDER)
|
||||
api_key = os.getenv(ENV_LLM_API_KEY, "")
|
||||
|
||||
# API key not needed for openai-codex (uses OAuth), claude-code (uses Keychain OAuth),
|
||||
# ollama (local), vertexai (uses GCP service account credentials),
|
||||
# or litellm (uses provider-specific auth, e.g. AWS credentials for Bedrock)
|
||||
if not api_key and not requires_api_key(provider):
|
||||
pass # Provider handles its own auth
|
||||
elif not api_key:
|
||||
raise ValueError(
|
||||
"HINDSIGHT_API_LLM_API_KEY environment variable is required (unless using openai-codex, claude-code, or litellm)"
|
||||
f"{ENV_LLM_API_KEY} environment variable is required (unless using openai-codex, claude-code, or litellm)"
|
||||
)
|
||||
|
||||
base_url = os.getenv("HINDSIGHT_API_LLM_BASE_URL", "")
|
||||
model = os.getenv("HINDSIGHT_API_LLM_MODEL", "openai/gpt-oss-120b")
|
||||
base_url = os.getenv(ENV_LLM_BASE_URL, "")
|
||||
model = os.getenv(ENV_LLM_MODEL, DEFAULT_LLM_MODEL)
|
||||
extra_body = json.loads(os.getenv(ENV_LLM_EXTRA_BODY, "null"))
|
||||
|
||||
extra_body = json.loads(os.getenv("HINDSIGHT_API_LLM_EXTRA_BODY", "null"))
|
||||
return cls(
|
||||
provider=provider,
|
||||
api_key=api_key,
|
||||
@@ -743,62 +750,6 @@ class LLMProvider:
|
||||
extra_body=extra_body,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def for_answer_generation(cls) -> "LLMProvider":
|
||||
"""Create provider for answer generation. Falls back to memory config if not set."""
|
||||
provider = os.getenv("HINDSIGHT_API_ANSWER_LLM_PROVIDER", os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq"))
|
||||
api_key = os.getenv("HINDSIGHT_API_ANSWER_LLM_API_KEY", os.getenv("HINDSIGHT_API_LLM_API_KEY", ""))
|
||||
|
||||
# API key not needed for providers with their own auth mechanisms
|
||||
if not api_key and not requires_api_key(provider):
|
||||
pass # Provider handles its own auth
|
||||
elif not api_key:
|
||||
raise ValueError(
|
||||
"HINDSIGHT_API_LLM_API_KEY or HINDSIGHT_API_ANSWER_LLM_API_KEY environment variable is required "
|
||||
"(unless using openai-codex, claude-code, or litellm)"
|
||||
)
|
||||
|
||||
base_url = os.getenv("HINDSIGHT_API_ANSWER_LLM_BASE_URL", os.getenv("HINDSIGHT_API_LLM_BASE_URL", ""))
|
||||
model = os.getenv("HINDSIGHT_API_ANSWER_LLM_MODEL", os.getenv("HINDSIGHT_API_LLM_MODEL", "openai/gpt-oss-120b"))
|
||||
|
||||
extra_body = json.loads(os.getenv("HINDSIGHT_API_LLM_EXTRA_BODY", "null"))
|
||||
return cls(
|
||||
provider=provider,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
model=model,
|
||||
reasoning_effort="high",
|
||||
extra_body=extra_body,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def for_judge(cls) -> "LLMProvider":
|
||||
"""Create provider for judge/evaluator operations. Falls back to memory config if not set."""
|
||||
provider = os.getenv("HINDSIGHT_API_JUDGE_LLM_PROVIDER", os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq"))
|
||||
api_key = os.getenv("HINDSIGHT_API_JUDGE_LLM_API_KEY", os.getenv("HINDSIGHT_API_LLM_API_KEY", ""))
|
||||
|
||||
# API key not needed for providers with their own auth mechanisms
|
||||
if not api_key and not requires_api_key(provider):
|
||||
pass # Provider handles its own auth
|
||||
elif not api_key:
|
||||
raise ValueError(
|
||||
"HINDSIGHT_API_LLM_API_KEY or HINDSIGHT_API_JUDGE_LLM_API_KEY environment variable is required "
|
||||
"(unless using openai-codex, claude-code, or litellm)"
|
||||
)
|
||||
|
||||
base_url = os.getenv("HINDSIGHT_API_JUDGE_LLM_BASE_URL", os.getenv("HINDSIGHT_API_LLM_BASE_URL", ""))
|
||||
model = os.getenv("HINDSIGHT_API_JUDGE_LLM_MODEL", os.getenv("HINDSIGHT_API_LLM_MODEL", "openai/gpt-oss-120b"))
|
||||
|
||||
extra_body = json.loads(os.getenv("HINDSIGHT_API_LLM_EXTRA_BODY", "null"))
|
||||
return cls(
|
||||
provider=provider,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
model=model,
|
||||
reasoning_effort="high",
|
||||
extra_body=extra_body,
|
||||
)
|
||||
|
||||
|
||||
class ConfiguredLLMProvider:
|
||||
"""
|
||||
|
||||
@@ -126,7 +126,7 @@ def llm_config():
|
||||
Provide LLM configuration for tests.
|
||||
This can be used by tests that need to call LLM directly without memory system.
|
||||
"""
|
||||
return LLMConfig.for_memory()
|
||||
return LLMConfig.from_env()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestCausalRelationsValidation:
|
||||
"""
|
||||
|
||||
context = "Personal life update"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
event_date = datetime(2024, 3, 15)
|
||||
|
||||
facts, _, usage = await extract_facts_from_text(
|
||||
@@ -81,7 +81,7 @@ class TestCausalRelationsValidation:
|
||||
"""
|
||||
|
||||
context = "Project update"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
event_date = datetime(2024, 6, 1)
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
@@ -118,7 +118,7 @@ class TestCausalRelationsValidation:
|
||||
"""
|
||||
|
||||
context = "Personal achievement story"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
event_date = datetime(2024, 7, 15)
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
@@ -168,7 +168,7 @@ class TestCausalRelationsValidation:
|
||||
"""
|
||||
|
||||
context = "Business impact analysis"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
event_date = datetime(2024, 4, 1)
|
||||
|
||||
facts, _, usage = await extract_facts_from_text(
|
||||
@@ -205,7 +205,7 @@ class TestCausalRelationsValidation:
|
||||
"""
|
||||
|
||||
context = "Career progression"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
event_date = datetime(2024, 5, 1)
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
|
||||
@@ -35,7 +35,7 @@ After searching for weeks, I finally found a cheaper apartment in Brooklyn.
|
||||
"""
|
||||
|
||||
context = "Personal story about housing change"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text, event_date=datetime(2024, 3, 15), context=context, llm_config=llm_config, agent_name="TestUser",
|
||||
@@ -105,7 +105,7 @@ The renovation took three months and cost $15,000.
|
||||
"""
|
||||
|
||||
context = "Home repair story"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text, event_date=datetime(2024, 6, 1), context=context, llm_config=llm_config, agent_name="TestUser",
|
||||
@@ -136,7 +136,7 @@ Machine learning fascinated me so much that I changed my career to data science.
|
||||
"""
|
||||
|
||||
context = "Career change story"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text, event_date=datetime(2024, 1, 1), context=context, llm_config=llm_config, agent_name="TestUser",
|
||||
@@ -164,7 +164,7 @@ The new role enabled me to lead a team of engineers.
|
||||
"""
|
||||
|
||||
context = "Work promotion story"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text, event_date=datetime(2024, 2, 15), context=context, llm_config=llm_config, agent_name="TestUser",
|
||||
@@ -192,7 +192,7 @@ Reduced spending somewhat affected local businesses.
|
||||
"""
|
||||
|
||||
context = "Economic impact story"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text, event_date=datetime(2024, 4, 1), context=context, llm_config=llm_config, agent_name="TestUser",
|
||||
|
||||
@@ -23,7 +23,7 @@ I changed the return type of the `process_request` function from `dict` to `Resp
|
||||
After that, I updated the three callers in `api/handlers.py` to destructure the new model fields.
|
||||
The type checker was happy after the change but I noticed one test was still using the old dict keys.
|
||||
"""
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
event_date=datetime(2025, 3, 28),
|
||||
@@ -50,7 +50,7 @@ The tests were failing with a ConnectionRefusedError on the Redis integration su
|
||||
I traced it to the connection pool not being initialized before the first test ran.
|
||||
I added a setup fixture that ensures the pool is warmed up, and all 47 tests pass now.
|
||||
"""
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
event_date=datetime(2025, 3, 28),
|
||||
@@ -78,7 +78,7 @@ I proposed splitting it into two modules: token_validation.py and session_manage
|
||||
The user approved my approach and I started with the token validation logic.
|
||||
I discovered that the existing tests were mocking the wrong interface, so I had to rewrite them first.
|
||||
"""
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
event_date=datetime(2025, 3, 28),
|
||||
@@ -106,7 +106,7 @@ I migrated our codebase from the old TypeVar approach to the new syntax.
|
||||
The migration touched 23 files but was mostly mechanical.
|
||||
PEP 695 defines the new type statement that makes generics more readable.
|
||||
"""
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
event_date=datetime(2025, 3, 28),
|
||||
|
||||
@@ -38,7 +38,7 @@ I ran into my neighbor Sarah who mentioned she's planning a trip to Italy next m
|
||||
"""
|
||||
|
||||
context = "Personal diary entry"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -83,7 +83,7 @@ User: Perfect, I'll make a reservation for Saturday at 7pm.
|
||||
"""
|
||||
|
||||
context = "Restaurant recommendation conversation"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -140,7 +140,7 @@ I edited about 20 photos from my recent trip to the mountains.
|
||||
"""
|
||||
|
||||
context = "Personal blog post"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -205,7 +205,7 @@ I edited about 20 photos from my recent trip to the mountains.
|
||||
text = "\n".join([f"{turn['speaker']}: {turn['text']}" for turn in session])
|
||||
|
||||
context = f"Conversation between {data['conversation']['speaker_a']} and {data['conversation']['speaker_b']}"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -267,7 +267,7 @@ I'm planning to visit Japan next year.
|
||||
"""
|
||||
|
||||
context = "Personal info"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
|
||||
@@ -42,7 +42,7 @@ Marcus felt anxious about the upcoming interview.
|
||||
"""
|
||||
|
||||
context = "Personal journal entry"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -75,7 +75,7 @@ The music was so loud I could barely hear myself think.
|
||||
"""
|
||||
|
||||
context = "Personal experience"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -109,7 +109,7 @@ Maybe we should reconsider the timeline.
|
||||
"""
|
||||
|
||||
context = "Team discussion"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -143,7 +143,7 @@ I'm unable to attend the conference due to scheduling conflicts.
|
||||
"""
|
||||
|
||||
context = "Personal profile discussion"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -176,7 +176,7 @@ Unlike last year, we're ahead of schedule.
|
||||
"""
|
||||
|
||||
context = "Project review"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -210,7 +210,7 @@ She's enthusiastic about the opportunity.
|
||||
"""
|
||||
|
||||
context = "Team meeting"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -244,7 +244,7 @@ I'm planning to switch careers because I'm not fulfilled in my current role.
|
||||
"""
|
||||
|
||||
context = "Personal goals discussion"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -282,7 +282,7 @@ Family is the most important thing to her.
|
||||
"""
|
||||
|
||||
context = "Personal values discussion"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -315,7 +315,7 @@ I prefer presenting in person rather than virtually because I can read the room
|
||||
"""
|
||||
|
||||
context = "Personal reflection"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
event_date = datetime(2024, 11, 13)
|
||||
|
||||
@@ -373,7 +373,7 @@ I'm planning to visit Tokyo next month.
|
||||
"""
|
||||
|
||||
context = "Personal conversation"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
event_date = datetime(2024, 11, 13)
|
||||
|
||||
@@ -421,7 +421,7 @@ with a concert surrounded by music, joy and the warm summer breeze.
|
||||
"""
|
||||
|
||||
context = "Conversation between Melanie and Caroline"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
event_date = datetime(2023, 8, 14, 14, 24)
|
||||
|
||||
last_error = None
|
||||
@@ -496,7 +496,7 @@ It was a beautiful day and I plan to make this a regular habit.
|
||||
"""
|
||||
|
||||
context = "Personal diary"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
event_date = datetime(2024, 11, 13)
|
||||
|
||||
@@ -547,7 +547,7 @@ It was a beautiful day and I plan to make this a regular habit.
|
||||
"""Test that relative dates are converted to absolute dates."""
|
||||
|
||||
reference_date = datetime(2024, 3, 20, 14, 0, 0, tzinfo=UTC)
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
text = """
|
||||
Yesterday I went hiking in Yosemite.
|
||||
@@ -582,7 +582,7 @@ It was a beautiful day and I plan to make this a regular habit.
|
||||
"""Test that facts without temporal info are still extracted."""
|
||||
|
||||
reference_date = datetime(2024, 3, 20, 14, 0, 0, tzinfo=UTC)
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
text = "Alice works at Google. She loves Python programming."
|
||||
|
||||
@@ -607,7 +607,7 @@ It was a beautiful day and I plan to make this a regular habit.
|
||||
"""Test that absolute dates in text are preserved."""
|
||||
|
||||
reference_date = datetime(2024, 3, 20, 14, 0, 0, tzinfo=UTC)
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
text = """
|
||||
On March 15, 2024, Alice joined Google.
|
||||
@@ -662,7 +662,7 @@ great time! Every time I see it, I can't help but smile.
|
||||
"""
|
||||
|
||||
context = "Conversation between Deborah and Jolene"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
event_date = datetime(2023, 2, 23)
|
||||
|
||||
@@ -715,7 +715,7 @@ I've learned so much from it.
|
||||
"""
|
||||
|
||||
context = "Personal update"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -785,7 +785,7 @@ Jamie: Congratulations! I'd love to read it.
|
||||
|
||||
context = "Podcast episode between you (Marcus) and Jamie discussing AI research"
|
||||
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=transcript,
|
||||
@@ -831,7 +831,7 @@ We presented our findings to the team yesterday.
|
||||
|
||||
context = "Personal work log"
|
||||
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -867,7 +867,7 @@ Jamie: [teasing] We'll see who's right, my Niners pick is solid.
|
||||
context = "podcast episode on match prediction of week 10 - Marcus (you) and Jamie - 14 nov"
|
||||
agent_name = "Marcus"
|
||||
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=transcript,
|
||||
@@ -929,7 +929,7 @@ so the algorithm learns to box out. See you next week!
|
||||
|
||||
context = "Podcast episode between you (Marcus) and Jamie about AI"
|
||||
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
max_retries = 3
|
||||
last_error = None
|
||||
|
||||
@@ -2256,7 +2256,7 @@ If the text contains both Italian and English content, extract ONLY the Italian
|
||||
Il sistema di autenticazione è stato migrato a OAuth 2.0.
|
||||
"""
|
||||
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
|
||||
facts, _, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
@@ -2491,7 +2491,7 @@ async def test_verbatim_extraction_mode():
|
||||
"She holds a CKA certification and has 5 years of Kubernetes experience."
|
||||
)
|
||||
|
||||
llm_config = LLMConfig.for_memory()
|
||||
llm_config = LLMConfig.from_env()
|
||||
contents = [RetainContent(content=text, event_date=datetime(2024, 3, 10, tzinfo=timezone.utc), context="onboarding notes")]
|
||||
facts, chunks, _ = await extract_facts_from_contents(
|
||||
contents=contents,
|
||||
|
||||
@@ -212,10 +212,22 @@ class LLMAnswerEvaluator:
|
||||
"""LLM-based answer evaluator with configurable provider."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize with LLM configuration for judge/evaluator."""
|
||||
"""Initialize with LLM configuration for judge/evaluator.
|
||||
|
||||
Uses HINDSIGHT_API_JUDGE_LLM_* env vars with fallback to HINDSIGHT_API_LLM_* for
|
||||
benchmark-specific LLM configuration (separate from the API config system).
|
||||
"""
|
||||
import os
|
||||
|
||||
from hindsight_api.engine.llm_wrapper import LLMConfig
|
||||
|
||||
self.llm_config = LLMConfig.for_judge()
|
||||
self.llm_config = LLMConfig(
|
||||
provider=os.getenv("HINDSIGHT_API_JUDGE_LLM_PROVIDER", os.getenv("HINDSIGHT_API_LLM_PROVIDER", "openai")),
|
||||
api_key=os.getenv("HINDSIGHT_API_JUDGE_LLM_API_KEY", os.getenv("HINDSIGHT_API_LLM_API_KEY", "")),
|
||||
base_url=os.getenv("HINDSIGHT_API_JUDGE_LLM_BASE_URL", os.getenv("HINDSIGHT_API_LLM_BASE_URL", "")),
|
||||
model=os.getenv("HINDSIGHT_API_JUDGE_LLM_MODEL", os.getenv("HINDSIGHT_API_LLM_MODEL", "gpt-4o-mini")),
|
||||
reasoning_effort="high",
|
||||
)
|
||||
self.client = self.llm_config._client
|
||||
self.model = self.llm_config.model
|
||||
|
||||
|
||||
@@ -111,8 +111,18 @@ class LoComoAnswerGenerator(LLMAnswerGenerator):
|
||||
"""LoComo-specific answer generator using configurable LLM provider."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize with LLM configuration for answer generation."""
|
||||
self.llm_config = LLMConfig.for_answer_generation()
|
||||
"""Initialize with LLM configuration for answer generation.
|
||||
|
||||
Uses HINDSIGHT_API_ANSWER_LLM_* env vars with fallback to HINDSIGHT_API_LLM_* for
|
||||
benchmark-specific LLM configuration (separate from the API config system).
|
||||
"""
|
||||
self.llm_config = LLMConfig(
|
||||
provider=os.getenv("HINDSIGHT_API_ANSWER_LLM_PROVIDER", os.getenv("HINDSIGHT_API_LLM_PROVIDER", "openai")),
|
||||
api_key=os.getenv("HINDSIGHT_API_ANSWER_LLM_API_KEY", os.getenv("HINDSIGHT_API_LLM_API_KEY", "")),
|
||||
base_url=os.getenv("HINDSIGHT_API_ANSWER_LLM_BASE_URL", os.getenv("HINDSIGHT_API_LLM_BASE_URL", "")),
|
||||
model=os.getenv("HINDSIGHT_API_ANSWER_LLM_MODEL", os.getenv("HINDSIGHT_API_LLM_MODEL", "gpt-4o-mini")),
|
||||
reasoning_effort="high",
|
||||
)
|
||||
self.client = self.llm_config._client
|
||||
self.model = self.llm_config.model
|
||||
|
||||
|
||||
@@ -150,7 +150,15 @@ class LongMemEvalAnswerGenerator(LLMAnswerGenerator):
|
||||
- "json": Raw JSON dump of recall_result (original behavior)
|
||||
- "structured": Human-readable format with facts grouped with source chunks
|
||||
"""
|
||||
self.llm_config = LLMConfig.for_answer_generation()
|
||||
# Uses HINDSIGHT_API_ANSWER_LLM_* env vars with fallback to HINDSIGHT_API_LLM_* for
|
||||
# benchmark-specific LLM configuration (separate from the API config system).
|
||||
self.llm_config = LLMConfig(
|
||||
provider=os.getenv("HINDSIGHT_API_ANSWER_LLM_PROVIDER", os.getenv("HINDSIGHT_API_LLM_PROVIDER", "openai")),
|
||||
api_key=os.getenv("HINDSIGHT_API_ANSWER_LLM_API_KEY", os.getenv("HINDSIGHT_API_LLM_API_KEY", "")),
|
||||
base_url=os.getenv("HINDSIGHT_API_ANSWER_LLM_BASE_URL", os.getenv("HINDSIGHT_API_LLM_BASE_URL", "")),
|
||||
model=os.getenv("HINDSIGHT_API_ANSWER_LLM_MODEL", os.getenv("HINDSIGHT_API_LLM_MODEL", "gpt-4o-mini")),
|
||||
reasoning_effort="high",
|
||||
)
|
||||
self.client = self.llm_config._client
|
||||
self.model = self.llm_config.model
|
||||
self.context_format = context_format
|
||||
|
||||
@@ -172,6 +172,8 @@ To switch between backends:
|
||||
| `HINDSIGHT_API_LLM_TIMEOUT` | LLM request timeout in seconds | `120` |
|
||||
| `HINDSIGHT_API_LLM_GROQ_SERVICE_TIER` | Groq service tier: `on_demand`, `flex`, `auto` | `auto` |
|
||||
| `HINDSIGHT_API_LLM_OPENAI_SERVICE_TIER` | OpenAI service tier: `flex` for 50% cost savings (OpenAI Flex Processing) | None (default) |
|
||||
| `HINDSIGHT_API_LLM_EXTRA_BODY` | JSON dict merged into `extra_body` for all OpenAI-compatible API calls. Useful for custom model servers (e.g., vLLM `chat_template_kwargs`). | `null` |
|
||||
| `HINDSIGHT_API_LLM_GEMINI_SAFETY_SETTINGS` | JSON-encoded list of `{category, threshold}` dicts for Gemini/VertexAI content safety filtering | `null` |
|
||||
|
||||
**Provider Examples**
|
||||
|
||||
@@ -353,6 +355,7 @@ export HINDSIGHT_API_RETAIN_LLM_MAX_BACKOFF=120.0 # Cap at 2min instead of 1m
|
||||
| `HINDSIGHT_API_EMBEDDINGS_PROVIDER` | Provider: `local`, `tei`, `openai`, `cohere`, `litellm`, or `litellm-sdk` | `local` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL` | Model for local provider | `BAAI/bge-small-en-v1.5` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LOCAL_TRUST_REMOTE_CODE` | Allow loading models with custom code (security risk, disabled by default) | `false` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU` | Force CPU mode for local embeddings (avoids MPS/XPC issues on macOS) | `false` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_TEI_URL` | TEI server URL | - |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY` | OpenAI API key (falls back to `HINDSIGHT_API_LLM_API_KEY`) | - |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL` | OpenAI embedding model | `text-embedding-3-small` |
|
||||
@@ -451,6 +454,7 @@ Supported OpenAI embedding dimensions:
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_MODEL` | Model for local provider | `cross-encoder/ms-marco-MiniLM-L-6-v2` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_MAX_CONCURRENT` | Max concurrent local reranking (prevents CPU thrashing under load) | `4` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_TRUST_REMOTE_CODE` | Allow loading models with custom code (security risk, disabled by default) | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU` | Force CPU mode for local reranker (avoids MPS/XPC issues on macOS) | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_FP16` | Half-precision (FP16) inference for the local reranker. 27–36% faster on MPS; quality-identical. Disabled by default to avoid regressions on non-MPS deployments — some CPUs lack native FP16 support. | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_BUCKET_BATCHING` | Sort pairs by token length before batching to reduce padding waste. 36–54% faster across models; quality-identical by construction. | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_BATCH_SIZE` | Batch size for local reranker `predict()`. Optimal value varies by hardware and model (smaller batches can outperform larger ones on MPS). | `32` |
|
||||
@@ -611,6 +615,9 @@ Controls the retain (memory ingestion) pipeline.
|
||||
| `HINDSIGHT_API_RETAIN_CUSTOM_INSTRUCTIONS` | Full prompt override for fact extraction (only used when mode is `custom`). Replaces built-in extraction rules entirely. | - |
|
||||
| `HINDSIGHT_API_RETAIN_EXTRACT_CAUSAL_LINKS` | Extract causal relationships between facts | `true` |
|
||||
| `HINDSIGHT_API_RETAIN_BATCH_ENABLED` | Use LLM Batch API for fact extraction (50% cost savings, only with async operations) | `false` |
|
||||
| `HINDSIGHT_API_RETAIN_BATCH_TOKENS` | Max characters per sub-batch for async retain auto-splitting | `10000` |
|
||||
| `HINDSIGHT_API_RETAIN_ENTITY_LOOKUP` | Entity lookup method during retain: `full` (exact match) or `trigram` (fuzzy trigram matching) | `trigram` |
|
||||
| `HINDSIGHT_API_RETAIN_DEFAULT_STRATEGY` | Default retain strategy name. When set, all retain calls without an explicit `strategy` parameter use this strategy. | - |
|
||||
| `HINDSIGHT_API_RETAIN_BATCH_POLL_INTERVAL_SECONDS` | Batch API polling interval in seconds | `60` |
|
||||
|
||||
> **Entity labels** (`entity_labels`) and **free-form entity extraction** (`entities_allow_free_form`) are configured per bank via the [bank config API](/developer/api/memory-banks#retain-configuration), not as global environment variables — each bank can have its own controlled vocabulary. See [Entity Labels](/developer/retain#entity-labels) for details.
|
||||
@@ -1022,6 +1029,23 @@ Configuration for background task processing. By default, the API processes task
|
||||
| `HINDSIGHT_API_SKIP_LLM_VERIFICATION` | Skip LLM connection check on startup | `false` |
|
||||
| `HINDSIGHT_API_LAZY_RERANKER` | Lazy-load reranker model (faster startup) | `false` |
|
||||
|
||||
### Webhooks
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_WEBHOOK_URL` | Global webhook URL for event delivery | - (disabled) |
|
||||
| `HINDSIGHT_API_WEBHOOK_SECRET` | HMAC signing secret for webhook payloads | - (unsigned) |
|
||||
| `HINDSIGHT_API_WEBHOOK_EVENT_TYPES` | Comma-separated list of event types to deliver via webhook | `consolidation.completed` |
|
||||
| `HINDSIGHT_API_WEBHOOK_DELIVERY_POLL_INTERVAL_SECONDS` | How often the webhook delivery worker polls for pending deliveries (seconds) | `30` |
|
||||
|
||||
### Audit Logging
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_AUDIT_LOG_ENABLED` | Master switch for audit logging | `false` |
|
||||
| `HINDSIGHT_API_AUDIT_LOG_ACTIONS` | Comma-separated allowlist of action types to audit (empty = all eligible actions) | `""` |
|
||||
| `HINDSIGHT_API_AUDIT_LOG_RETENTION_DAYS` | Number of days to retain audit log entries. `-1` = keep forever. | `-1` |
|
||||
|
||||
### Programmatic Configuration
|
||||
|
||||
You can also configure the API programmatically using `MemoryEngine.from_env()`:
|
||||
|
||||
@@ -172,6 +172,8 @@ To switch between backends:
|
||||
| `HINDSIGHT_API_LLM_TIMEOUT` | LLM request timeout in seconds | `120` |
|
||||
| `HINDSIGHT_API_LLM_GROQ_SERVICE_TIER` | Groq service tier: `on_demand`, `flex`, `auto` | `auto` |
|
||||
| `HINDSIGHT_API_LLM_OPENAI_SERVICE_TIER` | OpenAI service tier: `flex` for 50% cost savings (OpenAI Flex Processing) | None (default) |
|
||||
| `HINDSIGHT_API_LLM_EXTRA_BODY` | JSON dict merged into `extra_body` for all OpenAI-compatible API calls. Useful for custom model servers (e.g., vLLM `chat_template_kwargs`). | `null` |
|
||||
| `HINDSIGHT_API_LLM_GEMINI_SAFETY_SETTINGS` | JSON-encoded list of `{category, threshold}` dicts for Gemini/VertexAI content safety filtering | `null` |
|
||||
|
||||
**Provider Examples**
|
||||
|
||||
@@ -353,6 +355,7 @@ export HINDSIGHT_API_RETAIN_LLM_MAX_BACKOFF=120.0 # Cap at 2min instead of 1m
|
||||
| `HINDSIGHT_API_EMBEDDINGS_PROVIDER` | Provider: `local`, `tei`, `openai`, `cohere`, `litellm`, or `litellm-sdk` | `local` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL` | Model for local provider | `BAAI/bge-small-en-v1.5` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LOCAL_TRUST_REMOTE_CODE` | Allow loading models with custom code (security risk, disabled by default) | `false` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU` | Force CPU mode for local embeddings (avoids MPS/XPC issues on macOS) | `false` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_TEI_URL` | TEI server URL | - |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY` | OpenAI API key (falls back to `HINDSIGHT_API_LLM_API_KEY`) | - |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL` | OpenAI embedding model | `text-embedding-3-small` |
|
||||
@@ -366,6 +369,7 @@ export HINDSIGHT_API_RETAIN_LLM_MAX_BACKOFF=120.0 # Cap at 2min instead of 1m
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_API_KEY` | LiteLLM SDK API key for direct embedding provider access | - |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_MODEL` | LiteLLM SDK embedding model (use provider prefix, e.g., `cohere/embed-english-v3.0`) | `cohere/embed-english-v3.0` |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_API_BASE` | Custom base URL for LiteLLM SDK embeddings (optional) | - |
|
||||
| `HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_OUTPUT_DIMENSIONS` | Optional output embedding dimensions (provider-dependent, e.g., `768` for Gemini embedding models) | - |
|
||||
|
||||
```bash
|
||||
# Local (default) - uses SentenceTransformers
|
||||
@@ -413,6 +417,8 @@ export HINDSIGHT_API_EMBEDDINGS_LITELLM_MODEL=text-embedding-3-small # or coher
|
||||
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=litellm-sdk
|
||||
export HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_API_KEY=your-provider-api-key
|
||||
export HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_MODEL=cohere/embed-english-v3.0
|
||||
# Optional: request a specific output dimension when the provider supports it
|
||||
# export HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_OUTPUT_DIMENSIONS=768
|
||||
|
||||
# Supported LiteLLM SDK embedding providers:
|
||||
# - cohere/embed-english-v3.0 (1024 dimensions)
|
||||
@@ -426,6 +432,8 @@ export HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_MODEL=cohere/embed-english-v3.0
|
||||
|
||||
Hindsight automatically detects the embedding dimension from the model at startup and adjusts the database schema accordingly. The default model (`BAAI/bge-small-en-v1.5`) produces 384-dimensional vectors, while OpenAI models produce 1536 or 3072 dimensions.
|
||||
|
||||
For `litellm-sdk`, if you set `HINDSIGHT_API_EMBEDDINGS_LITELLM_SDK_OUTPUT_DIMENSIONS`, startup uses that output size when the underlying provider supports LiteLLM's `dimensions` parameter (otherwise behavior is unchanged). The same dimension-change rules below apply.
|
||||
|
||||
:::warning Dimension Changes
|
||||
Once memories are stored, you cannot change the embedding dimension without losing data. If you need to switch to a model with different dimensions:
|
||||
|
||||
@@ -446,6 +454,7 @@ Supported OpenAI embedding dimensions:
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_MODEL` | Model for local provider | `cross-encoder/ms-marco-MiniLM-L-6-v2` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_MAX_CONCURRENT` | Max concurrent local reranking (prevents CPU thrashing under load) | `4` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_TRUST_REMOTE_CODE` | Allow loading models with custom code (security risk, disabled by default) | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU` | Force CPU mode for local reranker (avoids MPS/XPC issues on macOS) | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_FP16` | Half-precision (FP16) inference for the local reranker. 27–36% faster on MPS; quality-identical. Disabled by default to avoid regressions on non-MPS deployments — some CPUs lack native FP16 support. | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_BUCKET_BATCHING` | Sort pairs by token length before batching to reduce padding waste. 36–54% faster across models; quality-identical by construction. | `false` |
|
||||
| `HINDSIGHT_API_RERANKER_LOCAL_BATCH_SIZE` | Batch size for local reranker `predict()`. Optimal value varies by hardware and model (smaller batches can outperform larger ones on MPS). | `32` |
|
||||
@@ -606,6 +615,9 @@ Controls the retain (memory ingestion) pipeline.
|
||||
| `HINDSIGHT_API_RETAIN_CUSTOM_INSTRUCTIONS` | Full prompt override for fact extraction (only used when mode is `custom`). Replaces built-in extraction rules entirely. | - |
|
||||
| `HINDSIGHT_API_RETAIN_EXTRACT_CAUSAL_LINKS` | Extract causal relationships between facts | `true` |
|
||||
| `HINDSIGHT_API_RETAIN_BATCH_ENABLED` | Use LLM Batch API for fact extraction (50% cost savings, only with async operations) | `false` |
|
||||
| `HINDSIGHT_API_RETAIN_BATCH_TOKENS` | Max characters per sub-batch for async retain auto-splitting | `10000` |
|
||||
| `HINDSIGHT_API_RETAIN_ENTITY_LOOKUP` | Entity lookup method during retain: `full` (exact match) or `trigram` (fuzzy trigram matching) | `trigram` |
|
||||
| `HINDSIGHT_API_RETAIN_DEFAULT_STRATEGY` | Default retain strategy name. When set, all retain calls without an explicit `strategy` parameter use this strategy. | - |
|
||||
| `HINDSIGHT_API_RETAIN_BATCH_POLL_INTERVAL_SECONDS` | Batch API polling interval in seconds | `60` |
|
||||
|
||||
> **Entity labels** (`entity_labels`) and **free-form entity extraction** (`entities_allow_free_form`) are configured per bank via the [bank config API](api/memory-banks.md#retain-configuration), not as global environment variables — each bank can have its own controlled vocabulary. See [Entity Labels](retain.md#entity-labels) for details.
|
||||
@@ -1017,6 +1029,23 @@ Configuration for background task processing. By default, the API processes task
|
||||
| `HINDSIGHT_API_SKIP_LLM_VERIFICATION` | Skip LLM connection check on startup | `false` |
|
||||
| `HINDSIGHT_API_LAZY_RERANKER` | Lazy-load reranker model (faster startup) | `false` |
|
||||
|
||||
### Webhooks
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_WEBHOOK_URL` | Global webhook URL for event delivery | - (disabled) |
|
||||
| `HINDSIGHT_API_WEBHOOK_SECRET` | HMAC signing secret for webhook payloads | - (unsigned) |
|
||||
| `HINDSIGHT_API_WEBHOOK_EVENT_TYPES` | Comma-separated list of event types to deliver via webhook | `consolidation.completed` |
|
||||
| `HINDSIGHT_API_WEBHOOK_DELIVERY_POLL_INTERVAL_SECONDS` | How often the webhook delivery worker polls for pending deliveries (seconds) | `30` |
|
||||
|
||||
### Audit Logging
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_AUDIT_LOG_ENABLED` | Master switch for audit logging | `false` |
|
||||
| `HINDSIGHT_API_AUDIT_LOG_ACTIONS` | Comma-separated allowlist of action types to audit (empty = all eligible actions) | `""` |
|
||||
| `HINDSIGHT_API_AUDIT_LOG_RETENTION_DAYS` | Number of days to retain audit log entries. `-1` = keep forever. | `-1` |
|
||||
|
||||
### Programmatic Configuration
|
||||
|
||||
You can also configure the API programmatically using `MemoryEngine.from_env()`:
|
||||
|
||||
@@ -5815,6 +5815,32 @@
|
||||
"title": "Tags",
|
||||
"description": "Tags associated with this document",
|
||||
"default": []
|
||||
},
|
||||
"document_metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Document Metadata",
|
||||
"description": "Document metadata"
|
||||
},
|
||||
"retain_params": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Retain Params",
|
||||
"description": "Parameters used during retain"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -5833,9 +5859,17 @@
|
||||
"bank_id": "user123",
|
||||
"content_hash": "abc123",
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"document_metadata": {
|
||||
"channel": "#general",
|
||||
"source": "slack"
|
||||
},
|
||||
"id": "session_1",
|
||||
"memory_unit_count": 15,
|
||||
"original_text": "Full document text here...",
|
||||
"retain_params": {
|
||||
"context": "Team meeting notes",
|
||||
"event_date": "2024-01-15"
|
||||
},
|
||||
"tags": [
|
||||
"user_a",
|
||||
"session_123"
|
||||
|
||||
Reference in New Issue
Block a user