Compare commits

...
Author SHA1 Message Date
Nicolò Boschi dec85b9446 fix ci and release 2025-12-03 15:47:42 +01:00
Nicolò Boschi 9fcdd35ac3 fix ci 2025-12-03 15:24:59 +01:00
12 changed files with 378 additions and 29 deletions
-2
View File
@@ -202,8 +202,6 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
package-helm-chart:
runs-on: ubuntu-latest
-2
View File
@@ -125,8 +125,6 @@ jobs:
file: docker/standalone/Dockerfile
target: ${{ matrix.target }}
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
test-api:
runs-on: ubuntu-latest
@@ -1084,7 +1084,8 @@ class MemoryEngine:
temporal_results = []
aggregated_timings = {"semantic": 0.0, "bm25": 0.0, "graph": 0.0, "temporal": 0.0}
for idx, (ft_semantic, ft_bm25, ft_graph, ft_temporal, ft_timings) in enumerate(all_retrievals):
detected_temporal_constraint = None
for idx, (ft_semantic, ft_bm25, ft_graph, ft_temporal, ft_timings, ft_temporal_constraint) in enumerate(all_retrievals):
# Log fact types in this retrieval batch
ft_name = fact_type[idx] if idx < len(fact_type) else "unknown"
logger.debug(f"[SEARCH {search_id}] Fact type '{ft_name}': semantic={len(ft_semantic)}, bm25={len(ft_bm25)}, graph={len(ft_graph)}, temporal={len(ft_temporal) if ft_temporal else 0}")
@@ -1097,6 +1098,9 @@ class MemoryEngine:
# Track max timing for each method (since they run in parallel across fact types)
for method, duration in ft_timings.items():
aggregated_timings[method] = max(aggregated_timings[method], duration)
# Capture temporal constraint (same across all fact types)
if ft_temporal_constraint:
detected_temporal_constraint = ft_temporal_constraint
# If no temporal results from any fact type, set to None
if not temporal_results:
@@ -1120,9 +1124,13 @@ class MemoryEngine:
f"bm25={len(bm25_results)}({aggregated_timings['bm25']:.3f}s)",
f"graph={len(graph_results)}({aggregated_timings['graph']:.3f}s)"
]
if temporal_results:
timing_parts.append(f"temporal={len(temporal_results)}({aggregated_timings['temporal']:.3f}s)")
log_buffer.append(f" [2] {total_retrievals}-way retrieval ({len(fact_type)} fact_types): {', '.join(timing_parts)} in {step_duration:.3f}s")
temporal_info = ""
if detected_temporal_constraint:
start_dt, end_dt = detected_temporal_constraint
temporal_count = len(temporal_results) if temporal_results else 0
timing_parts.append(f"temporal={temporal_count}({aggregated_timings['temporal']:.3f}s)")
temporal_info = f" | temporal_range={start_dt.strftime('%Y-%m-%d')} to {end_dt.strftime('%Y-%m-%d')}"
log_buffer.append(f" [2] {total_retrievals}-way retrieval ({len(fact_type)} fact_types): {', '.join(timing_parts)} in {step_duration:.3f}s{temporal_info}")
# Record retrieval results for tracer (convert typed results to old format)
if tracer:
@@ -184,6 +184,34 @@ class DateparserQueryAnalyzer(QueryAnalyzer):
if re.search(r'\b(today|hoy|oggi|aujourd\'?hui|heute)\b', query, re.IGNORECASE):
return constraint(reference_date, reference_date)
# "a couple of days ago" / "a few days ago" patterns
# These are imprecise so we create a range
if re.search(r'\b(a\s+)?couple\s+(of\s+)?days?\s+ago\b', query, re.IGNORECASE):
# "a couple of days" = approximately 2 days, give range of 1-3 days
return constraint(reference_date - timedelta(days=3), reference_date - timedelta(days=1))
if re.search(r'\b(a\s+)?few\s+days?\s+ago\b', query, re.IGNORECASE):
# "a few days" = approximately 3-4 days, give range of 2-5 days
return constraint(reference_date - timedelta(days=5), reference_date - timedelta(days=2))
# "a couple of weeks ago" / "a few weeks ago" patterns
if re.search(r'\b(a\s+)?couple\s+(of\s+)?weeks?\s+ago\b', query, re.IGNORECASE):
# "a couple of weeks" = approximately 2 weeks, give range of 1-3 weeks
return constraint(reference_date - timedelta(weeks=3), reference_date - timedelta(weeks=1))
if re.search(r'\b(a\s+)?few\s+weeks?\s+ago\b', query, re.IGNORECASE):
# "a few weeks" = approximately 3-4 weeks, give range of 2-5 weeks
return constraint(reference_date - timedelta(weeks=5), reference_date - timedelta(weeks=2))
# "a couple of months ago" / "a few months ago" patterns
if re.search(r'\b(a\s+)?couple\s+(of\s+)?months?\s+ago\b', query, re.IGNORECASE):
# "a couple of months" = approximately 2 months, give range of 1-3 months
return constraint(reference_date - timedelta(days=90), reference_date - timedelta(days=30))
if re.search(r'\b(a\s+)?few\s+months?\s+ago\b', query, re.IGNORECASE):
# "a few months" = approximately 3-4 months, give range of 2-5 months
return constraint(reference_date - timedelta(days=150), reference_date - timedelta(days=60))
# Last week patterns (English, Spanish, Italian, French, German)
if re.search(r'\b(last\s+week|la\s+semana\s+pasada|la\s+settimana\s+scorsa|la\s+semaine\s+derni[eè]re|letzte\s+woche)\b', query, re.IGNORECASE):
start = reference_date - timedelta(days=reference_date.weekday() + 7)
@@ -330,7 +330,9 @@ FACT FORMAT - ALL FIVE DIMENSIONS REQUIRED - MAXIMUM VERBOSITY
For EACH fact, CAPTURE ALL DETAILS - NEVER SUMMARIZE OR OMIT:
1. **what**: WHAT happened - COMPLETE description with ALL specifics (objects, actions, quantities, details)
2. **when**: WHEN it happened - ALWAYS include temporal info (dates, times, durations, relative times)
2. **when**: WHEN it happened - ALWAYS include temporal info with DAY OF WEEK (e.g., "Monday, June 10, 2024")
- Always include the day name: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
- Format: "day_name, month day, year" (e.g., "Saturday, June 9, 2024")
3. **where**: WHERE it happened or is about - SPECIFIC locations, places, areas, regions (if applicable)
4. **who**: WHO is involved - ALL people/entities with FULL relationships and background
5. **why**: WHY it matters - ALL emotions, preferences, motivations, significance, nuance
@@ -350,7 +352,7 @@ Example input: "I went to my college roommate's wedding last June. Emily finally
CORRECT output:
- what: "Emily got married to Sarah at a rooftop garden ceremony"
- when: "in June 2024, after dating for 5 years"
- when: "Saturday, June 8, 2024, after dating for 5 years"
- where: "downtown San Francisco, at a rooftop garden venue"
- who: "Emily (user's college roommate), Sarah (Emily's partner of 5 years)"
- why: "User found it romantic and beautiful, dreams of similar outdoor ceremony"
@@ -366,7 +368,8 @@ TEMPORAL HANDLING
══════════════════════════════════════════════════════════════════════════
For EVENTS (fact_kind="event"):
- Convert relative dates → absolute: "yesterday" on March 15 → "March 14, 2024"
- Convert relative dates → absolute WITH DAY OF WEEK: "yesterday" on Saturday March 15 → "Friday, March 14, 2024"
- Always include the day name (Monday, Tuesday, etc.) in the 'when' field
- Set occurred_start/occurred_end to WHEN IT HAPPENED (not when mentioned)
For CONVERSATIONS (fact_kind="conversation"):
@@ -468,10 +471,12 @@ WHAT TO EXTRACT vs SKIP
last_error = None
# Build user message with metadata and chunk content in a clear format
# Format event_date with day of week for better temporal reasoning
event_date_formatted = event_date.strftime('%A, %B %d, %Y') # e.g., "Monday, June 10, 2024"
user_message = f"""Extract facts from the following text chunk.
Chunk: {chunk_index + 1}/{total_chunks}
Event Date: {event_date.isoformat()}
Event Date: {event_date_formatted} ({event_date.isoformat()})
Context: {context if context else 'none'}
Text:
@@ -228,7 +228,7 @@ async def retrieve_temporal(
start_date: datetime,
end_date: datetime,
budget: int,
semantic_threshold: float = 0.4
semantic_threshold: float = 0.1
) -> List[RetrievalResult]:
"""
Temporal retrieval with spreading activation.
@@ -287,6 +287,9 @@ async def retrieve_temporal(
query_emb_str, bank_id, fact_type, start_date, end_date, semantic_threshold
)
import logging
logger = logging.getLogger(__name__)
if not entry_points:
# Check if there are ANY memories with temporal metadata for this bank
total_with_dates = await conn.fetchval(
@@ -295,9 +298,28 @@ async def retrieve_temporal(
AND (occurred_start IS NOT NULL OR occurred_end IS NOT NULL OR mentioned_at IS NOT NULL)""",
bank_id, fact_type
)
import logging
logger = logging.getLogger(__name__)
logger.info(f"[TEMPORAL] No entry points found for {bank_id}/{fact_type} in range {start_date} to {end_date}. Total facts with dates: {total_with_dates}")
# Check how many have mentioned_at in the range
in_range = await conn.fetchval(
"""SELECT COUNT(*) FROM memory_units
WHERE bank_id = $1 AND fact_type = $2
AND mentioned_at IS NOT NULL AND mentioned_at BETWEEN $3 AND $4""",
bank_id, fact_type, start_date, end_date
)
# Check semantic similarity of those in range
sample = await conn.fetch(
"""SELECT id, text, mentioned_at, 1 - (embedding <=> $1::vector) AS similarity
FROM memory_units
WHERE bank_id = $2 AND fact_type = $3
AND mentioned_at IS NOT NULL AND mentioned_at BETWEEN $4 AND $5
AND embedding IS NOT NULL
ORDER BY mentioned_at DESC
LIMIT 5""",
query_emb_str, bank_id, fact_type, start_date, end_date
)
logger.info(f"[TEMPORAL] No entry points for {bank_id}/{fact_type} in {start_date} to {end_date}.")
logger.info(f"[TEMPORAL] Total with dates: {total_with_dates}, In date range: {in_range}")
for row in sample:
logger.info(f"[TEMPORAL] Sample: {row['text'][:60]}... mentioned_at={row['mentioned_at']} sim={row['similarity']:.3f}")
return []
# Calculate temporal scores for entry points
@@ -430,7 +452,7 @@ async def retrieve_parallel(
thinking_budget: int,
question_date: Optional[datetime] = None,
query_analyzer: Optional["QueryAnalyzer"] = None
) -> Tuple[List[RetrievalResult], List[RetrievalResult], List[RetrievalResult], Optional[List[RetrievalResult]], Dict[str, float]]:
) -> Tuple[List[RetrievalResult], List[RetrievalResult], List[RetrievalResult], Optional[List[RetrievalResult]], Dict[str, float], Optional[Tuple[datetime, datetime]]]:
"""
Run 3-way or 4-way parallel retrieval (adds temporal if detected).
@@ -445,10 +467,11 @@ async def retrieve_parallel(
query_analyzer: Query analyzer to use (defaults to TransformerQueryAnalyzer)
Returns:
Tuple of (semantic_results, bm25_results, graph_results, temporal_results, timings)
Tuple of (semantic_results, bm25_results, graph_results, temporal_results, timings, temporal_constraint)
Each results list contains RetrievalResult objects
temporal_results is None if no temporal constraint detected
timings is a dict with per-method latencies in seconds
temporal_constraint is the (start_date, end_date) tuple if detected, else None
"""
# Detect temporal constraint
from .temporal_extraction import extract_temporal_constraint
@@ -459,7 +482,6 @@ async def retrieve_parallel(
temporal_constraint = extract_temporal_constraint(
query_text, reference_date=question_date, analyzer=query_analyzer
)
logger.info(f"[TEMPORAL] Query: {query_text[:50]}... -> constraint={temporal_constraint}")
# Wrapper to track timing for each retrieval method
async def timed_retrieval(name: str, coro):
@@ -484,7 +506,7 @@ async def retrieve_parallel(
async with acquire_with_retry(pool) as conn:
return await retrieve_temporal(
conn, query_embedding_str, bank_id, fact_type,
start_date, end_date, budget=thinking_budget, semantic_threshold=0.4
start_date, end_date, budget=thinking_budget, semantic_threshold=0.1
)
# Run retrievals in parallel with timing
@@ -512,4 +534,4 @@ async def retrieve_parallel(
graph_results, _, timings["graph"] = results[2]
temporal_results = None
return semantic_results, bm25_results, graph_results, temporal_results, timings
return semantic_results, bm25_results, graph_results, temporal_results, timings, temporal_constraint
@@ -232,3 +232,54 @@ def test_query_analyzer_last_weekend(query_analyzer):
assert analysis.temporal_constraint.end_date.day == 12 # Sunday
def test_query_analyzer_couple_days_ago(query_analyzer):
"""Test extraction of 'a couple of days ago' colloquial expression."""
reference_date = datetime(2025, 1, 15, 12, 0, 0)
query = "I mentioned cooking something for my friend a couple of days ago. What was it?"
analysis = query_analyzer.analyze(query, reference_date)
print(f"\nQuery: '{query}'")
print(f"Reference date: {reference_date.strftime('%A, %Y-%m-%d')}")
print(f"Analysis: {analysis}")
assert analysis.temporal_constraint is not None, "Should extract temporal constraint for 'a couple of days ago'"
# Range should be 1-3 days ago: Jan 12-14
assert analysis.temporal_constraint.start_date.day == 12
assert analysis.temporal_constraint.end_date.day == 14
def test_query_analyzer_few_days_ago(query_analyzer):
"""Test extraction of 'a few days ago' colloquial expression."""
reference_date = datetime(2025, 1, 15, 12, 0, 0)
query = "What did I do a few days ago?"
analysis = query_analyzer.analyze(query, reference_date)
print(f"\nQuery: '{query}'")
print(f"Reference date: {reference_date.strftime('%A, %Y-%m-%d')}")
print(f"Analysis: {analysis}")
assert analysis.temporal_constraint is not None, "Should extract temporal constraint for 'a few days ago'"
# Range should be 2-5 days ago: Jan 10-13
assert analysis.temporal_constraint.start_date.day == 10
assert analysis.temporal_constraint.end_date.day == 13
def test_query_analyzer_couple_weeks_ago(query_analyzer):
"""Test extraction of 'a couple of weeks ago' colloquial expression."""
reference_date = datetime(2025, 1, 15, 12, 0, 0)
query = "a couple of weeks ago we discussed this"
analysis = query_analyzer.analyze(query, reference_date)
print(f"\nQuery: '{query}'")
print(f"Reference date: {reference_date.strftime('%A, %Y-%m-%d')}")
print(f"Analysis: {analysis}")
assert analysis.temporal_constraint is not None, "Should extract temporal constraint for 'a couple of weeks ago'"
# Range should be 1-3 weeks ago
assert analysis.temporal_constraint.start_date.month == 12 # Dec 25 (3 weeks before Jan 15)
assert analysis.temporal_constraint.end_date.month == 1 # Jan 8 (1 week before Jan 15)
@@ -5,9 +5,9 @@ export async function POST(request: NextRequest) {
try {
const body = await request.json();
const bankId = body.bank_id || body.agent_id || 'default';
const { query, types, fact_type, max_tokens, trace, budget, include } = body;
const { query, types, fact_type, max_tokens, trace, budget, include, query_timestamp } = body;
console.log('[Recall API] Request:', { bankId, query, types: types || fact_type, max_tokens, trace, budget });
console.log('[Recall API] Request:', { bankId, query, types: types || fact_type, max_tokens, trace, budget, query_timestamp });
console.log('[Recall API] Include options:', JSON.stringify(include, null, 2));
const response = await sdk.recallMemories({
@@ -20,6 +20,7 @@ export async function POST(request: NextRequest) {
trace,
budget: budget || 'mid',
include,
query_timestamp,
},
});
@@ -320,8 +320,8 @@ export function DataView({ factType }: DataViewProps) {
)}
{viewMode === 'table' && (
<div className="flex gap-4">
<div className={`transition-all ${selectedTableMemory ? 'w-2/3' : 'w-full'}`}>
<div>
<div className="w-full">
<div className="px-5 mb-4">
<Input
type="text"
@@ -358,7 +358,8 @@ export function DataView({ factType }: DataViewProps) {
<TableHead className="w-[80px]">ID</TableHead>
<TableHead>Text</TableHead>
<TableHead className="w-[150px]">Context</TableHead>
<TableHead className="w-[120px]">Occurred</TableHead>
<TableHead className="w-[100px]">Occurred</TableHead>
<TableHead className="w-[100px]">Mentioned</TableHead>
<TableHead className="w-[60px]">Actions</TableHead>
</TableRow>
</TableHeader>
@@ -367,6 +368,9 @@ export function DataView({ factType }: DataViewProps) {
const occurredDisplay = row.occurred_start
? new Date(row.occurred_start).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
: null;
const mentionedDisplay = row.mentioned_at
? new Date(row.mentioned_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
: null;
return (
<TableRow
@@ -407,6 +411,14 @@ export function DataView({ factType }: DataViewProps) {
</span>
) : '-'}
</TableCell>
<TableCell className="text-xs">
{mentionedDisplay ? (
<span className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
{mentionedDisplay}
</span>
) : '-'}
</TableCell>
<TableCell>
<Button
onClick={(e) => {
@@ -492,9 +504,9 @@ export function DataView({ factType }: DataViewProps) {
</div>
</div>
{/* Memory Detail Panel for Table View */}
{/* Memory Detail Panel for Table View - Fixed on Right */}
{selectedTableMemory && (
<div className="w-1/3 pr-5 pb-5">
<div className="fixed right-0 top-0 h-screen w-96 bg-background border-l border-border shadow-lg z-50 overflow-y-auto p-4">
<MemoryDetailPanel
memory={selectedTableMemory}
onClose={() => setSelectedTableMemory(null)}
@@ -0,0 +1,211 @@
'use client';
import { useState, useEffect } from 'react';
import { client } from '@/lib/api';
import { useBank } from '@/lib/bank-context';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
interface DocumentChunkModalProps {
type: 'document' | 'chunk';
id: string | null;
onClose: () => void;
}
export function DocumentChunkModal({ type, id, onClose }: DocumentChunkModalProps) {
const { currentBank } = useBank();
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
const loadData = async () => {
setLoading(true);
setError(null);
try {
if (type === 'document') {
if (!currentBank) {
setError('No bank selected');
return;
}
const doc = await client.getDocument(id, currentBank);
setData(doc);
} else {
const chunk = await client.getChunk(id);
setData(chunk);
}
} catch (err) {
console.error(`Error loading ${type}:`, err);
setError((err as Error).message);
} finally {
setLoading(false);
}
};
loadData();
}, [id, type, currentBank]);
const isOpen = id !== null;
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-3xl max-h-[80vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle>
{type === 'document' ? 'Document Details' : 'Chunk Details'}
</DialogTitle>
<DialogDescription>
{type === 'document'
? 'View the original document text and metadata'
: 'View the chunk text and metadata'}
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto">
{loading ? (
<div className="flex items-center justify-center py-20">
<div className="text-center">
<div className="text-4xl mb-2"></div>
<div className="text-sm text-muted-foreground">
Loading {type}...
</div>
</div>
</div>
) : error ? (
<div className="flex items-center justify-center py-20">
<div className="text-center text-destructive">
<div className="text-4xl mb-2"></div>
<div className="text-sm">Error: {error}</div>
</div>
</div>
) : data ? (
<div className="space-y-4">
{type === 'document' ? (
<>
<div className="space-y-3">
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Document ID
</div>
<div className="text-sm font-mono break-all">{data.id}</div>
</div>
{data.created_at && (
<div className="grid grid-cols-2 gap-3">
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Created
</div>
<div className="text-sm">
{new Date(data.created_at).toLocaleString()}
</div>
</div>
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Memory Units
</div>
<div className="text-sm">{data.memory_unit_count}</div>
</div>
</div>
)}
{data.original_text && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Text Length
</div>
<div className="text-sm">
{data.original_text.length.toLocaleString()} characters
</div>
</div>
)}
</div>
{data.original_text && (
<div>
<div className="text-sm font-bold text-foreground mb-2">
Original Text
</div>
<div className="p-4 bg-muted rounded-lg border border-border max-h-[300px] overflow-y-auto">
<pre className="text-sm whitespace-pre-wrap font-mono">
{data.original_text}
</pre>
</div>
</div>
)}
</>
) : (
<>
<div className="space-y-3">
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Chunk ID
</div>
<div className="text-sm font-mono break-all">
{data.chunk_id}
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Document ID
</div>
<div className="text-sm font-mono break-all">
{data.document_id}
</div>
</div>
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Chunk Index
</div>
<div className="text-sm">{data.chunk_index}</div>
</div>
</div>
{data.created_at && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Created
</div>
<div className="text-sm">
{new Date(data.created_at).toLocaleString()}
</div>
</div>
)}
{data.chunk_text && (
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-1">
Text Length
</div>
<div className="text-sm">
{data.chunk_text.length.toLocaleString()} characters
</div>
</div>
)}
</div>
{data.chunk_text && (
<div>
<div className="text-sm font-bold text-foreground mb-2">
Chunk Text
</div>
<div className="p-4 bg-muted rounded-lg border border-border max-h-[300px] overflow-y-auto">
<pre className="text-sm whitespace-pre-wrap font-mono">
{data.chunk_text}
</pre>
</div>
</div>
)}
</>
)}
</div>
) : null}
</div>
</DialogContent>
</Dialog>
);
}
@@ -560,7 +560,7 @@ export function SearchDebugView() {
</div>
{/* Parameters Grid */}
<div className="grid grid-cols-4 gap-4">
<div className="grid grid-cols-5 gap-4">
<div>
<label className="block text-sm font-bold mb-2 text-accent-foreground">Fact Types:</label>
<div className="flex flex-col gap-2">
@@ -615,6 +615,20 @@ export function SearchDebugView() {
/>
</div>
<div>
<label className="block text-sm font-bold mb-2 text-accent-foreground">Query Date:</label>
<Input
type="datetime-local"
value={pane.queryDate}
onChange={(e) =>
updatePane(pane.id, { queryDate: e.target.value })
}
className="w-full"
placeholder="Optional"
/>
<p className="text-xs text-muted-foreground mt-1">When is the query being asked</p>
</div>
<div>
<label className="block text-sm font-bold mb-2 text-accent-foreground">Include:</label>
<div className="flex flex-col gap-2">
+1
View File
@@ -52,6 +52,7 @@ export class ControlPlaneClient {
entities?: { max_tokens: number } | null;
chunks?: { max_tokens: number } | null;
};
query_timestamp?: string;
}) {
return this.fetchApi('/api/recall', {
method: 'POST',