Skip to content

Commit dfccbf2

Browse files
DK09876claude
andauthored
Added hindsight_liteLLM implementation (#17)
* Added hindsight_liteLLM implementation * Add instructions for entity vs bank id * Add another line about entity * Address PR review comments and enhance litellm integration - Remove deprecated limit parameter from recall() and arecall() functions since Hindsight uses budget/max_tokens for result control - Remove dead MODEL_MAX_OUTPUT_TOKENS dict and max_output_tokens property from LLMProvider (superseded by hardcoded max_completion_tokens) - Add test-litellm-integration job to CI workflow - Add reflect API support with use_reflect config option - Add verbose mode debug info via get_last_injection_debug() - Add entity_id support for multi-user memory isolation - Add retain() and reflect() wrapper functions - Update docstrings and examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Make max_memories optional to allow unlimited memory injection - Change max_memories default from 10 to None (no limit) - When max_memories is None, all results from the API are used - Fix recall result handling to properly detect list vs object return - Update wrappers (OpenAI, Anthropic) with same optional behavior This allows users to control memory limits via max_memory_tokens and recall_budget without an artificial count limit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove entity_id from hindsight_litellm; add gpt-4o token cap Multi-user support now uses separate bank_ids per user instead of entity_id scoping (e.g., bank_id=f"user-{user_id}"). This simplifies the API and aligns with the Hindsight architecture. Also fixes max_completion_tokens error for gpt-4o models by capping the value at 16384 (gpt-4o's limit) instead of sending the default 65000 which exceeds the model's supported maximum. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix dark mode styling across Control Plane UI components Improvements to ensure proper text visibility and contrast in both light and dark modes: - Add global CSS rules for datetime-local calendar picker icon visibility using filter: invert() for both light (0.5) and dark (1) modes - Fix text colors in dialog components to use theme-aware foreground colors - Update memory detail panel, document/chunk modals, and data views to use proper dark mode text classes (text-foreground, text-card-foreground) - Fix form labels, headings, and content text in bank selector dialogs - Update entities view and documents view table styling for dark mode - Bump package versions to 0.1.4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove session_id feature and add How It Works section to README - Remove session_id and session management (new_session, set_session, get_session) from config.py, callbacks.py, and __init__.py - Session management was a client-only abstraction not backed by core API - Add "How It Works" section to README with visual flow diagram - Update README to remove session management documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix readme example * Add dark mode again --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dfea4db commit dfccbf2

20 files changed

Lines changed: 3772 additions & 70 deletions

File tree

.github/workflows/test.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,32 @@ jobs:
441441
run: |
442442
echo "=== API Server Logs ==="
443443
cat /tmp/api-server.log || echo "No API server log found"
444+
445+
test-litellm-integration:
446+
runs-on: ubuntu-latest
447+
448+
steps:
449+
- uses: actions/checkout@v4
450+
451+
- name: Install uv
452+
uses: astral-sh/setup-uv@v5
453+
with:
454+
enable-cache: true
455+
prune-cache: false
456+
457+
- name: Set up Python
458+
uses: actions/setup-python@v5
459+
with:
460+
python-version-file: ".python-version"
461+
462+
- name: Build litellm integration
463+
working-directory: ./hindsight-integrations/litellm
464+
run: uv build
465+
466+
- name: Install dependencies
467+
working-directory: ./hindsight-integrations/litellm
468+
run: uv sync --extra dev
469+
470+
- name: Run tests
471+
working-directory: ./hindsight-integrations/litellm
472+
run: uv run pytest tests -v

hindsight-api/hindsight_api/engine/llm_wrapper.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,13 @@ async def call(
175175
is_reasoning_model = any(x in model_lower for x in ["gpt-5", "o1", "o3"])
176176

177177
# For GPT-4 and GPT-4.1 models, cap max_completion_tokens to 32000
178+
# For GPT-4o models, cap to 16384
178179
is_gpt4_model = any(x in model_lower for x in ["gpt-4.1", "gpt-4-"])
180+
is_gpt4o_model = "gpt-4o" in model_lower
179181
if max_completion_tokens is not None:
180-
if is_gpt4_model and max_completion_tokens > 32000:
182+
if is_gpt4o_model and max_completion_tokens > 16384:
183+
max_completion_tokens = 16384
184+
elif is_gpt4_model and max_completion_tokens > 32000:
181185
max_completion_tokens = 32000
182186
# For reasoning models, max_completion_tokens includes reasoning + output tokens
183187
# Enforce minimum of 16000 to ensure enough space for both

hindsight-api/hindsight_api/pg0.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ async def start(self, max_retries: int = 3, retry_delay: float = 2.0) -> str:
5454
loop = asyncio.get_event_loop()
5555
info = await loop.run_in_executor(None, pg0.start)
5656
logger.info(f"PostgreSQL started on port {self.port}")
57-
return info.uri
57+
# Construct URI manually since pg0-embedded may return None
58+
uri = info.uri if info and info.uri else f"postgresql://{self.username}:{self.password}@localhost:{self.port}/{self.database}"
59+
return uri
5860
except Exception as e:
5961
last_error = str(e)
6062
if attempt < max_retries:
@@ -89,9 +91,9 @@ async def get_uri(self) -> str:
8991
pg0 = self._get_pg0()
9092
loop = asyncio.get_event_loop()
9193
info = await loop.run_in_executor(None, pg0.info)
92-
if info is None or not info.running:
93-
raise RuntimeError("PostgreSQL server is not running or URI not available")
94-
return info.uri
94+
# Construct URI manually since pg0-embedded may return None
95+
uri = info.uri if info and info.uri else f"postgresql://{self.username}:{self.password}@localhost:{self.port}/{self.database}"
96+
return uri
9597

9698
async def is_running(self) -> bool:
9799
"""Check if the PostgreSQL server is currently running."""

hindsight-control-plane/src/app/globals.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,13 @@ code, pre {
180180
-webkit-background-clip: text;
181181
-webkit-text-fill-color: transparent;
182182
background-clip: text;
183+
}
184+
185+
/* Fix datetime-local calendar icon visibility in both light and dark modes */
186+
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
187+
filter: invert(0.5);
188+
}
189+
190+
.dark input[type="datetime-local"]::-webkit-calendar-picker-indicator {
191+
filter: invert(1);
183192
}

hindsight-control-plane/src/components/bank-profile-view.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export function BankProfileView() {
239239
<div className="flex gap-2">
240240
{editMode ? (
241241
<>
242-
<Button onClick={handleCancel} variant="outline" disabled={saving}>
242+
<Button onClick={handleCancel} variant="secondary" disabled={saving}>
243243
Cancel
244244
</Button>
245245
<Button onClick={handleSave} disabled={saving}>
@@ -258,7 +258,7 @@ export function BankProfileView() {
258258
</>
259259
) : (
260260
<>
261-
<Button onClick={loadData} variant="outline" size="sm">
261+
<Button onClick={loadData} variant="secondary" size="sm">
262262
<RefreshCw className="w-4 h-4 mr-2" />
263263
Refresh
264264
</Button>

hindsight-control-plane/src/components/bank-selector.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ function BankSelectorInner() {
266266
</div>
267267
<DialogFooter>
268268
<Button
269-
variant="outline"
269+
variant="secondary"
270270
onClick={() => {
271271
setCreateDialogOpen(false);
272272
setNewBankId('');
@@ -295,7 +295,7 @@ function BankSelectorInner() {
295295
</DialogHeader>
296296
<div className="py-4 space-y-4">
297297
<div>
298-
<label className="font-bold block mb-1 text-sm">Content *</label>
298+
<label className="font-bold block mb-1 text-sm text-foreground">Content *</label>
299299
<Textarea
300300
value={docContent}
301301
onChange={(e) => setDocContent(e.target.value)}
@@ -306,7 +306,7 @@ function BankSelectorInner() {
306306
</div>
307307

308308
<div>
309-
<label className="font-bold block mb-1 text-sm">Context</label>
309+
<label className="font-bold block mb-1 text-sm text-foreground">Context</label>
310310
<Input
311311
type="text"
312312
value={docContext}
@@ -317,16 +317,17 @@ function BankSelectorInner() {
317317

318318
<div className="grid grid-cols-2 gap-4">
319319
<div>
320-
<label className="font-bold block mb-1 text-sm">Event Date</label>
320+
<label className="font-bold block mb-1 text-sm text-foreground">Event Date</label>
321321
<Input
322322
type="datetime-local"
323323
value={docEventDate}
324324
onChange={(e) => setDocEventDate(e.target.value)}
325+
className="text-foreground"
325326
/>
326327
</div>
327328

328329
<div>
329-
<label className="font-bold block mb-1 text-sm">Document ID</label>
330+
<label className="font-bold block mb-1 text-sm text-foreground">Document ID</label>
330331
<Input
331332
type="text"
332333
value={docDocumentId}
@@ -342,7 +343,7 @@ function BankSelectorInner() {
342343
checked={docAsync}
343344
onCheckedChange={(checked) => setDocAsync(checked as boolean)}
344345
/>
345-
<label htmlFor="async-doc" className="text-sm cursor-pointer">
346+
<label htmlFor="async-doc" className="text-sm cursor-pointer text-foreground">
346347
Process in background (async)
347348
</label>
348349
</div>
@@ -353,7 +354,7 @@ function BankSelectorInner() {
353354
</div>
354355
<DialogFooter>
355356
<Button
356-
variant="outline"
357+
variant="secondary"
357358
onClick={() => {
358359
setDocDialogOpen(false);
359360
setDocContent('');

hindsight-control-plane/src/components/data-view.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ export function DataView({ factType }: DataViewProps) {
480480
}`}
481481
>
482482
<TableCell className="py-2">
483-
<div className="line-clamp-2 text-sm leading-snug">{row.text}</div>
483+
<div className="line-clamp-2 text-sm leading-snug text-foreground">{row.text}</div>
484484
{row.context && (
485485
<div className="text-xs text-muted-foreground mt-0.5 truncate">{row.context}</div>
486486
)}
@@ -506,10 +506,10 @@ export function DataView({ factType }: DataViewProps) {
506506
<span className="text-xs text-muted-foreground">-</span>
507507
)}
508508
</TableCell>
509-
<TableCell className="text-xs py-2">
509+
<TableCell className="text-xs py-2 text-foreground">
510510
{occurredDisplay || <span className="text-muted-foreground">-</span>}
511511
</TableCell>
512-
<TableCell className="text-xs py-2">
512+
<TableCell className="text-xs py-2 text-foreground">
513513
{mentionedDisplay || <span className="text-muted-foreground">-</span>}
514514
</TableCell>
515515
<TableCell className="py-2">
@@ -519,7 +519,7 @@ export function DataView({ factType }: DataViewProps) {
519519
copyToClipboard(row.id);
520520
}}
521521
size="sm"
522-
variant="ghost"
522+
variant="secondary"
523523
className="h-6 w-6 p-0"
524524
title="Copy ID"
525525
>
@@ -799,7 +799,7 @@ function TimelineView({ data, filteredRows }: { data: any; filteredRows: any[] }
799799
{/* Zoom controls */}
800800
<div className="flex items-center border border-border rounded mr-2">
801801
<Button
802-
variant="ghost"
802+
variant="secondary"
803803
size="sm"
804804
onClick={zoomOut}
805805
disabled={granularity === 'year'}
@@ -808,11 +808,11 @@ function TimelineView({ data, filteredRows }: { data: any; filteredRows: any[] }
808808
>
809809
<ZoomOut className="h-3 w-3" />
810810
</Button>
811-
<span className="text-[10px] px-2 min-w-[50px] text-center border-x border-border">
811+
<span className="text-[10px] px-2 min-w-[50px] text-center border-x border-border text-foreground">
812812
{granularityLabels[granularity]}
813813
</span>
814814
<Button
815-
variant="ghost"
815+
variant="secondary"
816816
size="sm"
817817
onClick={zoomIn}
818818
disabled={granularity === 'day'}
@@ -826,7 +826,7 @@ function TimelineView({ data, filteredRows }: { data: any; filteredRows: any[] }
826826
{/* Navigation controls */}
827827
<div className="flex items-center border border-border rounded">
828828
<Button
829-
variant="ghost"
829+
variant="secondary"
830830
size="sm"
831831
onClick={() => scrollToGroup(0)}
832832
disabled={timelineGroups.length <= 1}
@@ -836,7 +836,7 @@ function TimelineView({ data, filteredRows }: { data: any; filteredRows: any[] }
836836
<ChevronsLeft className="h-3 w-3" />
837837
</Button>
838838
<Button
839-
variant="ghost"
839+
variant="secondary"
840840
size="sm"
841841
onClick={() => scrollToGroup(currentIndex - 1)}
842842
disabled={currentIndex === 0}
@@ -845,11 +845,11 @@ function TimelineView({ data, filteredRows }: { data: any; filteredRows: any[] }
845845
>
846846
<ChevronLeft className="h-3 w-3" />
847847
</Button>
848-
<span className="text-[10px] px-2 min-w-[60px] text-center border-x border-border">
848+
<span className="text-[10px] px-2 min-w-[60px] text-center border-x border-border text-foreground">
849849
{currentIndex + 1} / {timelineGroups.length}
850850
</span>
851851
<Button
852-
variant="ghost"
852+
variant="secondary"
853853
size="sm"
854854
onClick={() => scrollToGroup(currentIndex + 1)}
855855
disabled={currentIndex >= timelineGroups.length - 1}
@@ -859,7 +859,7 @@ function TimelineView({ data, filteredRows }: { data: any; filteredRows: any[] }
859859
<ChevronRight className="h-3 w-3" />
860860
</Button>
861861
<Button
862-
variant="ghost"
862+
variant="secondary"
863863
size="sm"
864864
onClick={() => scrollToGroup(timelineGroups.length - 1)}
865865
disabled={timelineGroups.length <= 1}

hindsight-control-plane/src/components/document-chunk-modal.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,23 +94,23 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
9494
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
9595
Document ID
9696
</div>
97-
<div className="text-sm font-mono break-all">{data.id}</div>
97+
<div className="text-sm font-mono break-all text-foreground">{data.id}</div>
9898
</div>
9999
{data.created_at && (
100100
<div className="grid grid-cols-2 gap-3">
101101
<div className="p-3 bg-muted rounded-lg">
102102
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
103103
Created
104104
</div>
105-
<div className="text-sm">
105+
<div className="text-sm text-foreground">
106106
{new Date(data.created_at).toLocaleString()}
107107
</div>
108108
</div>
109109
<div className="p-3 bg-muted rounded-lg">
110110
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
111111
Memory Units
112112
</div>
113-
<div className="text-sm">{data.memory_unit_count}</div>
113+
<div className="text-sm text-foreground">{data.memory_unit_count}</div>
114114
</div>
115115
</div>
116116
)}
@@ -119,7 +119,7 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
119119
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
120120
Text Length
121121
</div>
122-
<div className="text-sm">
122+
<div className="text-sm text-foreground">
123123
{data.original_text.length.toLocaleString()} characters
124124
</div>
125125
</div>
@@ -132,7 +132,7 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
132132
Original Text
133133
</div>
134134
<div className="p-4 bg-muted rounded-lg border border-border max-h-[300px] overflow-y-auto">
135-
<pre className="text-sm whitespace-pre-wrap font-mono">
135+
<pre className="text-sm whitespace-pre-wrap font-mono text-foreground">
136136
{data.original_text}
137137
</pre>
138138
</div>
@@ -146,7 +146,7 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
146146
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
147147
Chunk ID
148148
</div>
149-
<div className="text-sm font-mono break-all">
149+
<div className="text-sm font-mono break-all text-foreground">
150150
{data.chunk_id}
151151
</div>
152152
</div>
@@ -155,23 +155,23 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
155155
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
156156
Document ID
157157
</div>
158-
<div className="text-sm font-mono break-all">
158+
<div className="text-sm font-mono break-all text-foreground">
159159
{data.document_id}
160160
</div>
161161
</div>
162162
<div className="p-3 bg-muted rounded-lg">
163163
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
164164
Chunk Index
165165
</div>
166-
<div className="text-sm">{data.chunk_index}</div>
166+
<div className="text-sm text-foreground">{data.chunk_index}</div>
167167
</div>
168168
</div>
169169
{data.created_at && (
170170
<div className="p-3 bg-muted rounded-lg">
171171
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
172172
Created
173173
</div>
174-
<div className="text-sm">
174+
<div className="text-sm text-foreground">
175175
{new Date(data.created_at).toLocaleString()}
176176
</div>
177177
</div>
@@ -181,7 +181,7 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
181181
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
182182
Text Length
183183
</div>
184-
<div className="text-sm">
184+
<div className="text-sm text-foreground">
185185
{data.chunk_text.length.toLocaleString()} characters
186186
</div>
187187
</div>
@@ -194,7 +194,7 @@ export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProp
194194
Chunk Text
195195
</div>
196196
<div className="p-4 bg-muted rounded-lg border border-border max-h-[300px] overflow-y-auto">
197-
<pre className="text-sm whitespace-pre-wrap font-mono">
197+
<pre className="text-sm whitespace-pre-wrap font-mono text-foreground">
198198
{data.chunk_text}
199199
</pre>
200200
</div>

0 commit comments

Comments
 (0)