fix regressions and bunch of issues

This commit is contained in:
Nicolò Boschi 2025-12-01 18:44:49 +01:00
parent e75c483479
commit f7cf33c610
254 changed files with 9072 additions and 2918584 deletions

4
.gitignore vendored
View file

@ -28,3 +28,7 @@ nltk_data/
logs/
.DS_Store
hindsight-dev/benchmarks/locomo/results/
hindsight-dev/benchmarks/longmemeval/results/

View file

@ -1,128 +0,0 @@
# Release Guide
## Release Process
### 1. Generate OpenAPI Spec
```bash
uv sync
cd hindsight-dev
uv run generate-openapi
cd ..
```
### 2. Generate API Clients
```bash
./scripts/generate-clients.sh
```
This regenerates Python and TypeScript clients from `openapi.json`.
**Note:** Your `pyproject.toml` and `package.json` are preserved - only code is regenerated.
### 3. Commit Everything
```bash
git add openapi.json hindsight-clients/
git commit -m "Update OpenAPI spec and regenerate clients"
```
### 4. Run Release Script
```bash
./scripts/release.sh 0.0.6
```
This will:
- Update version to `0.0.6` in **all** components (core, clients, CLI, UI, Helm)
- Commit changes
- Create and push tag `v0.0.6`
- Trigger GitHub Actions (builds Python package, Rust CLI, Docker images, Helm chart)
---
## After GitHub Actions Complete
### Publish Python Client to PyPI
```bash
cd hindsight-clients/python
uv build
uv publish
```
### Publish TypeScript Client to NPM
```bash
cd hindsight-clients/typescript
npm install
npm run build
npm publish --access public
```
---
## Pre-Release Checklist
- [ ] Tests passing: `cd hindsight-api && uv run pytest tests`
- [ ] No uncommitted changes: `git status`
- [ ] On `main` branch
---
## Versioning
**Semantic Versioning: `MAJOR.MINOR.PATCH`**
- **PATCH** (0.0.6): Bug fixes, no API changes
- **MINOR** (0.1.0): New features, backward compatible
- **MAJOR** (1.0.0): Breaking changes
**All components use the same version** - coordinated releases for simplicity.
---
## Troubleshooting
**Tag already exists:**
```bash
git tag -d v0.0.6
git push origin :refs/tags/v0.0.6
```
**Working directory not clean:**
```bash
git status
# Commit or stash changes first
```
**GitHub Actions failed:**
- Check: https://github.com/vectorize-io/hindsight/actions
- Re-run failed jobs or fix and release new patch version
**Rollback:**
```bash
git tag -d v0.0.6
git push origin :refs/tags/v0.0.6
git revert HEAD
git push
```
---
## Quick Reference
```bash
# Full release workflow
uv sync
cd hindsight-dev && uv run generate-openapi && cd ..
./scripts/generate-clients.sh
git add openapi.json hindsight-clients/
git commit -m "Update OpenAPI spec and regenerate clients"
./scripts/release.sh 0.0.6
# After GH Actions complete:
cd hindsight-clients/python && uv build && uv publish
cd ../typescript && npm run build && npm publish --access public
```

View file

@ -1,16 +0,0 @@
#!/bin/bash
# Rebuild Hindsight images from scratch
cd "$(dirname "$0")/standalone"
echo "🔨 Rebuilding Hindsight images (no cache)..."
echo ""
# Build with no cache to force complete rebuild
docker-compose build --no-cache
echo ""
echo "✅ Rebuild complete!"
echo ""
echo "To start Hindsight:"
echo " ./start.sh"

View file

@ -99,6 +99,11 @@ RUN chmod +x /app/start-all.sh
# Create data directory for pg0
RUN mkdir -p /app/data
# Install pg0 to /root/.hindsight/bin/pg0
RUN mkdir -p /root/.hindsight/bin /root/.local/bin && \
export PATH="/root/.hindsight/bin:/root/.local/bin:$PATH" && \
curl -fsSL https://raw.githubusercontent.com/vectorize-io/pg0/main/install.sh | bash
# Expose ports
EXPOSE 8888 3000

View file

@ -3,8 +3,6 @@ services:
build:
context: ../..
dockerfile: docker/standalone/Dockerfile
platforms:
- linux/amd64
platform: linux/amd64
ports:
- "3000:3000"

View file

@ -23,7 +23,7 @@ done
# Start Control Plane
echo "🎛️ Starting Control Plane..."
cd /app/control-plane
npm start &
node .next/standalone/server.js &
CP_PID=$!
echo ""

View file

@ -350,10 +350,6 @@ class ReflectIncludeOptions(BaseModel):
default=None,
description="Include facts that the answer is based on. Set to {} to enable, null to disable (default: disabled)."
)
entities: Optional[EntityIncludeOptions] = Field(
default=None,
description="Include entity observations. Set to {max_tokens: N} to enable, null to disable (default: disabled)."
)
class ReflectRequest(BaseModel):
@ -365,8 +361,7 @@ class ReflectRequest(BaseModel):
"context": "This is for a research paper on AI ethics",
"filters": [{"key": "source", "value": "slack", "match_unset": True}],
"include": {
"facts": {},
"entities": {"max_tokens": 500}
"facts": {}
}
}
})
@ -375,7 +370,7 @@ class ReflectRequest(BaseModel):
budget: Budget = Budget.LOW
context: Optional[str] = None
filters: Optional[List[MetadataFilter]] = Field(default=None, description="Filter by metadata. Multiple filters are ANDed together.")
include: ReflectIncludeOptions = Field(default_factory=ReflectIncludeOptions, description="Options for including additional data (both disabled by default)")
include: ReflectIncludeOptions = Field(default_factory=ReflectIncludeOptions, description="Options for including additional data (disabled by default)")
class OpinionItem(BaseModel):
@ -1026,12 +1021,6 @@ def _register_routes(app: FastAPI):
occurred_end=fact.occurred_end
))
# TODO: Handle entities inclusion when supported in reflect
# entities_response = None
# if request.include.entities is not None:
# max_entity_tokens = request.include.entities.max_tokens
# # ... fetch and format entities
return ReflectResponse(
text=core_result.text,
based_on=based_on_facts,
@ -1437,10 +1426,10 @@ This operation cannot be undone.
async with acquire_with_retry(pool) as conn:
operations = await conn.fetch(
"""
SELECT id, bank_id, task_type, items_count, document_id, created_at, status, error_message
SELECT operation_id, bank_id, operation_type, created_at, status, error_message, result_metadata
FROM async_operations
WHERE bank_id = $1
ORDER BY created_at ASC
ORDER BY created_at DESC
""",
bank_id
)
@ -1449,10 +1438,10 @@ This operation cannot be undone.
"bank_id": bank_id,
"operations": [
{
"id": str(row['id']),
"task_type": row['task_type'],
"items_count": row['items_count'],
"document_id": row['document_id'],
"id": str(row['operation_id']),
"task_type": row['operation_type'],
"items_count": row['result_metadata'].get('items_count', 0) if row['result_metadata'] else 0,
"document_id": row['result_metadata'].get('document_id') if row['result_metadata'] else None,
"created_at": row['created_at'].isoformat(),
"status": row['status'],
"error_message": row['error_message']

View file

@ -60,6 +60,7 @@ class SentenceTransformersEmbeddings(Embeddings):
"""
self.model_name = model_name
self._model = None
self._load_model()
def _load_model(self):
"""Lazy load and validate the SentenceTransformer model."""
@ -96,6 +97,5 @@ class SentenceTransformersEmbeddings(Embeddings):
Returns:
List of 384-dimensional embedding vectors
"""
self._load_model()
embeddings = self._model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
return [emb.tolist() for emb in embeddings]

View file

@ -85,7 +85,7 @@ class LLMConfig:
messages: List[Dict[str, str]],
response_format: Optional[Any] = None,
scope: str = "memory",
max_retries: int = 5,
max_retries: int = 10,
initial_backoff: float = 1.0,
max_backoff: float = 60.0,
skip_validation: bool = False,

View file

@ -1583,6 +1583,10 @@ class MemoryEngine:
# Delete all data for the bank
units_count = await conn.fetchval("SELECT COUNT(*) FROM memory_units WHERE bank_id = $1", bank_id)
entities_count = await conn.fetchval("SELECT COUNT(*) FROM entities WHERE bank_id = $1", bank_id)
documents_count = await conn.fetchval("SELECT COUNT(*) FROM documents WHERE bank_id = $1", bank_id)
# Delete documents (cascades to chunks)
await conn.execute("DELETE FROM documents WHERE bank_id = $1", bank_id)
# Delete memory units (cascades to unit_entities, memory_links)
await conn.execute("DELETE FROM memory_units WHERE bank_id = $1", bank_id)
@ -1592,7 +1596,8 @@ class MemoryEngine:
return {
"memory_units_deleted": units_count,
"entities_deleted": entities_count
"entities_deleted": entities_count,
"documents_deleted": documents_count
}
except Exception as e:
@ -1629,7 +1634,7 @@ class MemoryEngine:
where_clause = "WHERE " + " AND ".join(query_conditions) if query_conditions else ""
units = await conn.fetch(f"""
SELECT id, text, event_date, context, occurred_start, occurred_end, mentioned_at
SELECT id, text, event_date, context, occurred_start, occurred_end, mentioned_at, document_id
FROM memory_units
{where_clause}
ORDER BY mentioned_at DESC NULLS LAST, event_date DESC
@ -1745,14 +1750,15 @@ class MemoryEngine:
entities = entity_map.get(unit_id, [])
table_rows.append({
"id": str(unit_id)[:8] + "...",
"id": str(unit_id),
"text": row['text'],
"context": row['context'] if row['context'] else "N/A",
"occurred_start": row['occurred_start'].isoformat() if row['occurred_start'] else None,
"occurred_end": row['occurred_end'].isoformat() if row['occurred_end'] else None,
"mentioned_at": row['mentioned_at'].isoformat() if row['mentioned_at'] else None,
"date": row['event_date'].strftime("%Y-%m-%d %H:%M") if row['event_date'] else "N/A", # Deprecated, kept for backwards compatibility
"entities": ", ".join(entities) if entities else "None"
"entities": ", ".join(entities) if entities else "None",
"document_id": row['document_id']
})
return {
@ -1827,7 +1833,7 @@ class MemoryEngine:
query_params.append(offset)
units = await conn.fetch(f"""
SELECT id, text, event_date, context, fact_type
SELECT id, text, event_date, context, fact_type, mentioned_at, occurred_start, occurred_end
FROM memory_units
{where_clause}
ORDER BY mentioned_at DESC NULLS LAST, created_at DESC
@ -1868,6 +1874,9 @@ class MemoryEngine:
"context": row['context'] if row['context'] else "",
"date": row['event_date'].isoformat() if row['event_date'] else "",
"fact_type": row['fact_type'],
"mentioned_at": row['mentioned_at'].isoformat() if row['mentioned_at'] else None,
"occurred_start": row['occurred_start'].isoformat() if row['occurred_start'] else None,
"occurred_end": row['occurred_end'].isoformat() if row['occurred_end'] else None,
"entities": ", ".join(entities) if entities else ""
})

View file

@ -41,8 +41,9 @@ async def check_duplicates_batch(
# Group facts by event_date (rounded to 12-hour buckets) for efficient batching
time_buckets = defaultdict(list)
for idx, fact in enumerate(facts):
# Use occurred_start as the representative date
fact_date = fact.occurred_start
# Use occurred_start if available, otherwise use mentioned_at
# For deduplication purposes, we need a time reference
fact_date = fact.occurred_start if fact.occurred_start is not None else fact.mentioned_at
# Round to 12-hour bucket to group similar times
bucket_key = fact_date.replace(
hour=(fact_date.hour // 12) * 12,

View file

@ -47,8 +47,13 @@ async def process_entities_batch(
# Extract data for link_utils function
fact_texts = [fact.fact_text for fact in facts]
fact_dates = [fact.occurred_start for fact in facts]
entities_per_fact = [[entity.name for entity in (fact.entities or [])] for fact in facts]
# Use occurred_start if available, otherwise use mentioned_at for entity timestamps
fact_dates = [fact.occurred_start if fact.occurred_start is not None else fact.mentioned_at for fact in facts]
# Convert EntityRef objects to dict format expected by link_utils
entities_per_fact = [
[{'text': entity.name, 'type': 'CONCEPT'} for entity in (fact.entities or [])]
for fact in facts
]
# Use existing link_utils function for entity processing
entity_links = await link_utils.extract_entities_batch_optimized(

View file

@ -109,7 +109,7 @@ class ExtractedFact(BaseModel):
)
observations: Optional[str] = Field(
default=None,
description="Observations and inferences as a COMPLETE SENTENCE. Include subject + observed/inferred fact. Examples: 'Calvin traveled to Miami for the shoot', 'Gina won dance trophies in competitions', 'She knows programming from previous projects'"
description="Observations, inferences, and specific details/metrics as a COMPLETE SENTENCE. Include subject + observed fact. Use this to capture: background facts, achievements, metrics, personal records, skills. Examples: 'Calvin traveled to Miami for the shoot', 'Gina won dance trophies in competitions', 'She knows programming from previous projects', 'User's personal best 5K time is 25:50', 'Sarah has completed 15 marathons', 'He speaks three languages fluently'"
)
# Fact kind - optional hint for LLM thinking, not critical for extraction
@ -122,11 +122,11 @@ class ExtractedFact(BaseModel):
# Temporal fields - optional
occurred_start: Optional[str] = Field(
default=None,
description="Optional: ISO format timestamp for when event started. Only needed for specific events."
description="WHEN THE EVENT ACTUALLY HAPPENED (not when mentioned). ISO timestamp. For datable events only (fact_kind='event'). Examples: 'went to Tokyo last spring' on June 10 → occurred_start='2024-03-01' (spring start), 'accident yesterday' on March 15 → occurred_start='2024-03-14' (yesterday). Leave null for general info (fact_kind='conversation')."
)
occurred_end: Optional[str] = Field(
default=None,
description="Optional: ISO format timestamp for when event ended. Only needed for specific events."
description="WHEN THE EVENT ACTUALLY ENDED (not when mentioned). ISO timestamp. For datable events with duration (fact_kind='event'). Examples: 'went to Tokyo last spring' → occurred_end='2024-05-31' (spring end). Can be same as occurred_start for single-day events. Leave null for general info."
)
# Classification (CRITICAL - required)
@ -261,294 +261,142 @@ async def _extract_facts_from_chunk(
else:
fact_types_instruction = "Extract ONLY 'world' and 'assistant' type facts. DO NOT extract opinions - those are extracted separately."
prompt = f"""You are extracting comprehensive, narrative facts from conversations/document for an AI memory system.
prompt = f"""Extract comprehensive facts from user text for an AI memory system.
{fact_types_instruction}
## CONTEXT INFORMATION
- Context: {context if context else 'no additional context provided'}{agent_context}
## CONTEXT
- Context: {context if context else 'none'}{agent_context}
**TEMPORAL EXTRACTION **:
- **occurred_start/end** (OPTIONAL): Only extract these for specific events mentioned within the conversation
- Example: "I'm hosting a party next month" - extract when the party will happen (resolve to absolute dates using the reference date)
- Leave empty if no specific event timing is mentioned
- Use the reference date (event_date) to resolve relative time expressions to absolute ISO timestamps
SECTION 1: TEMPORAL HANDLING (CRITICAL)
## CORE PRINCIPLE: Extract ALL Meaningful Information Efficiently
### 1.1 DETECT TEMPORAL MARKERS
Watch for: "yesterday", "last week/month/year/summer", "ago", "tomorrow", "next", "happened", "occurred", past tense verbs ("went", "visited", "saw")
**GOAL**: Capture ALL meaningful information, but combine related exchanges efficiently. Don't create separate facts for questions - merge Q&A into single facts.
### 1.2 DUAL FACT CREATION (KEY RULE)
When text mentions a past/future event Create TWO facts:
1. MENTION FACT: "On [context date], it was mentioned that..." (occurred_start = context date)
2. EVENT FACT: "[Action] in [absolute date]" (occurred_start = actual event date)
Each fact should:
1. **CAPTURE ALL MEANINGFUL CONTENT** - Activities, projects, preferences, recommendations, encouragement WITH specific content
2. **BE SELF-CONTAINED** - Readable without the original text
3. **PRESERVE SPECIFIC CONTENT** - Capture WHAT was said, not just THAT something was said
4. **COMBINE Q&A** - A question and its answer = ONE fact, not two separate facts
### 1.3 ABSOLUTE DATE CONVERSION
ALWAYS convert relative absolute in factual_core text:
- "yesterday" "on [date-1]"
- "last week" "around [specific week]"
- "last summer" "in summer [year] (June-August [year])"
- "next month" "in [month name] [year]"
## Q&A HANDLING - CRITICAL!
### 1.4 occurred_start/end FIELDS ⚠️ CRITICAL
### WHEN TO COMBINE (simple informational questions):
**WHAT THEY REPRESENT:**
- occurred_start/end = WHEN THE EVENT ACTUALLY HAPPENED (NOT when it was mentioned!)
- These answer: "When did this event occur in reality?"
** BAD (2 separate facts):**
- "James asks what projects John is working on"
- "John is working on a website for a local small business"
**WHEN TO SET THEM:**
SET for datable events (fact_kind="event"):
- "went to Tokyo last spring" occurred_start = March 1, 2024 (spring started)
- "accident yesterday" occurred_start = context date - 1 day
- "party next Saturday" occurred_start = next Saturday's date
** GOOD (1 combined fact):**
- "John is working on a website for a local small business; it's his first professional project outside of class"
LEAVE NULL for general info (fact_kind="conversation"):
- "loves coffee" no occurred dates (timeless preference)
- "works as engineer" no occurred dates (ongoing state)
- "is expanding business" no occurred dates (ongoing activity)
### WHEN TO SPLIT (user requests/instructions to assistant):
**KEY DISTINCTION:**
- occurred_start/end: When the event happened/will happen
- mentioned_at: When this was said/written (set automatically to context date)
- These are DIFFERENT! Example: On June 10, saying "went to Tokyo in March" occurred_start=March, mentioned_at=June 10
**CRITICAL**: When user asks assistant to DO something, extract BOTH facts separately!
**FORMAT:** ISO timestamps "2024-06-15T00:00:00Z"
** GOOD (2 separate BANK facts):**
1. "User requested a children's book about dinosaurs with image placeholders in '::title:: == ::description::' format"
2. "I wrote a children's book titled 'The Amazing Adventures of Dinosaurs' with chapters about T-Rex, Pterodactyl, Plesiosaur, and Triceratops, including image descriptions"
### 1.5 EXAMPLES - STUDY THESE CAREFULLY
** BAD (missing user request):**
- Only extracting: "I wrote a children's book about dinosaurs..."
**Example 1: "yesterday" temporal detection**
Input (Context: March 15, 2024): "Hey Taylor! The volunteers were amazing yesterday. But something unexpected happened - a vehicle accident near the center. Everyone was okay though."
**Rule**: If user says "write...", "create...", "help me...", "explain...", etc. Extract user's request AND assistant's response as SEPARATE bank facts!
Output (3 facts):
1. factual_core: "On March 15, 2024, Alex told Taylor that the volunteers were amazing"
occurred_start: "2024-03-15T00:00:00Z", entities: ["Alex", "Taylor"]
## WHAT TO SKIP (only these!)
2. factual_core: "On March 15, 2024, Alex mentioned that something unexpected happened the previous day - a vehicle accident"
occurred_start: "2024-03-15T00:00:00Z", entities: ["Alex"]
- **Pure filler with no content** - "Always happy to help", "Sounds good", "Thanks!"
- **Greetings** - "Hey!", "What's up?"
- **Standalone simple questions that are answered** - merge informational Q&A, but DON'T skip user requests!
3. factual_core: "On March 14, 2024, a vehicle accident occurred near the center, but everyone was okay"
occurred_start: "2024-03-14T00:00:00Z" THE ACTUAL EVENT DATE (yesterday from March 15)
## WHAT TO ALWAYS EXTRACT
**Example 2: "last spring" temporal detection**
Input (Context: June 10, 2024): "Casey went to Tokyo last spring. They had an incredible time visiting temples and trying authentic ramen."
- **USER REQUESTS** (CRITICAL!): "User requested a children's book about dinosaurs", "User asked for help with debugging"
- **ASSISTANT ACTIONS**: "I wrote a story", "I recommended meditation", "I explained the concept"
- Specific encouragement WITH content: "James says hiccups are normal, use them to learn and grow, push through"
- Reactions that reveal preferences: "John says the art is awesome, takes him back to reading fantasy books"
- Recommendations: "John recommends 'The Name of the Wind' - great novel with awesome writing"
- Plans/intentions: "James will check out 'The Name of the Wind'"
- All activities, projects, purchases, events with details
Output (2 facts):
1. factual_core: "On June 10, 2024, it was mentioned that Casey went to Tokyo the previous spring"
occurred_start: "2024-06-10T00:00:00Z", entities: ["Casey", "Tokyo"]
## ESSENTIAL DETAILS TO PRESERVE - NEVER LOSE THESE
2. factual_core: "Casey went to Tokyo in spring 2024 (March-May 2024) and visited temples and tried authentic ramen"
occurred_start: "2024-03-01T00:00:00Z", occurred_end: "2024-05-31T23:59:59Z" THE ACTUAL EVENT DATES
emotional_significance: "Casey had an incredible time in Tokyo"
entities: ["Casey", "Tokyo"]
When extracting facts, you MUST preserve:
SECTION 2: EXTRACTION RULES
1. **ALL PARTICIPANTS** - Who said/did what
2. **INDIVIDUAL PREFERENCES** - Each person's specific likes/favorites! "Jon's favorite is contemporary because it's expressive" - DO NOT LOSE THIS!
3. **FULL REASONING** - Why decisions were made, motivations, explanations
4. **TEMPORAL CONTEXT - CRITICAL** - ALWAYS convert relative time references to SPECIFIC ABSOLUTE dates in the fact text!
- "last week" (doc date Aug 23) "around August 16, 2023" (NOT just "in August 2023"!)
- "last month" (doc date Aug 2023) "in July 2023"
- "yesterday" (doc date Aug 19) "on August 18, 2023"
- "next week" (doc date Aug 19) "around August 26, 2023"
- "three days ago" (doc date Aug 19) "on August 16, 2023"
- "last year" "in 2022"
- BE SPECIFIC! "last week" is NOT "in August" - calculate the actual week!
5. **VISUAL/MEDIA ELEMENTS** - Photos, images, videos shared
6. **MODIFIERS** - "new", "first", "old", "favorite" (critical context)
7. **POSSESSIVE RELATIONSHIPS** - "their kids" "Person's kids"
8. **BIOGRAPHICAL DETAILS** - Origins, locations, jobs, family background
9. **SOCIAL DYNAMICS** - Nicknames, how people address each other, relationships
### 2.1 WHAT TO EXTRACT
User requests to assistant + assistant actions (extract separately)
Preferences, recommendations, plans, activities, encouragement (with actual content)
Possessions, achievements, metrics, skills, background facts
## STRUCTURED FACT DIMENSIONS - CRITICAL ⚠️
### 2.2 WHAT TO SKIP
Greetings, filler ("thanks", "cool"), structural statements
Each fact MUST be extracted into structured dimensions. This ensures no important context is lost.
### 2.3 Q&A HANDLING
- Combine simple informational Q&A into one fact
- Split user requests to assistant into two facts (request + response)
**CRITICAL FORMATTING RULE**: Each dimension MUST be a complete, grammatically correct sentence that includes the subject and can stand alone. These dimensions will be combined with " - " separators, so they must read naturally together.
SECTION 3: STRUCTURED DIMENSIONS
### Required field:
- **factual_core**: ACTUAL FACTS - capture WHAT was said, not just THAT something was said!
- BAD: "Jon received encouragement from Gina" (loses what Gina actually said)
- GOOD: "Gina said Jon is the perfect mentor with positivity and determination; his studio will be a hit"
- BAD: "Jon supports Gina" (generic)
- GOOD: "Gina found the perfect spot for her store; Jon says her hard work is paying off"
- Preserve: compliments, assessments, descriptions, predictions, key phrases
### 3.1 REQUIRED FIELD
- **factual_core**: Capture WHAT was said, not just THAT something was said. Complete sentence.
### Optional fields (include when present in text):
- **emotional_significance**: Emotions, feelings, personal meaning, AND qualitative descriptors - COMPLETE SENTENCE with subject
- BAD: "felt thrilled" (fragment, missing subject)
- GOOD: "Sarah felt thrilled about the opportunity"
- BAD: "was her favorite memory" (vague subject)
- GOOD: "This was her favorite memory from childhood"
- More examples: "The experience was magical for everyone involved", "John found the loss devastating", "She considers this her proudest moment"
- Captures: emotions, intensity, personal significance, AND experiential descriptors ("magical", "wonderful", "amazing", "thrilling", "beautiful")
### 3.2 OPTIONAL FIELDS (use when present in text)
- **emotional_significance**: Emotions, feelings, qualitative descriptors. Complete sentence with subject.
- **reasoning_motivation**: Why it happened, intentions, goals. Complete sentence with subject.
- **preferences_opinions**: Likes, dislikes, beliefs, values. Complete sentence with subject. Use for: "ideal", "favorite", "dream", "perfect"
- **sensory_details**: Visual, auditory, physical descriptions. Complete sentence. USE EXACT WORDS from text!
- **observations**: Background facts, possessions, achievements, metrics, skills. Complete sentence with subject.
- **reasoning_motivation**: WHY it happened, intentions, goals, causes - COMPLETE SENTENCE with subject
- BAD: "because she wanted to celebrate" (fragment, no subject)
- GOOD: "She did this because she wanted to celebrate with friends"
- More examples: "He wrote the book to cope with grief", "She was motivated by curiosity about the topic", "They moved there to be closer to family"
- Captures: reasons, intentions, goals, causal explanations
### 3.3 FORMATTING RULE
Each dimension MUST be a complete, grammatically correct sentence with subject that can stand alone.
- **preferences_opinions**: Likes, dislikes, beliefs, values, ideals - COMPLETE SENTENCE with subject
- BAD: "loves coffee" (fragment)
- GOOD: "Sarah loves coffee and drinks it every morning"
- BAD: "prefers remote work" (fragment)
- GOOD: "He prefers working remotely over office work"
- More examples: "Jon's ideal dance studio would be located by the water", "Jon's favorite dance style is contemporary because it's expressive", "She thinks AI is transformative technology"
- Captures: preferences, opinions, beliefs, judgments, ideals, dreams
- PREFERENCE INDICATORS: "ideal", "favorite", "dream", "perfect", "love", "hate", "prefer" MUST capture in this dimension!
- CRITICAL: Never lose individual preferences! Always include who has the preference!
SECTION 4: FACT CLASSIFICATION
- **sensory_details**: Visual, auditory, physical descriptions AND all descriptive adjectives - COMPLETE SENTENCE with subject - USE EXACT WORDS!
- BAD: "bright orange hair" (fragment)
- GOOD: "She has bright orange hair"
- BAD: "so graceful" (fragment)
- GOOD: "The dancer moved so gracefully across the stage"
- More examples: "The music was very loud", "The water was freezing cold", "The beach was awesome", "The movie had epic visuals"
- Captures: colors, sounds, textures, temperatures, appearances, AND adjectives describing people/things/performances
- CRITICAL: Use the EXACT adjectives from the text! If they said "awesome" don't write "amazing". If they said "epic" don't write "perfect"!
### 4.1 fact_kind (temporal nature)
- **conversation**: General info, ongoing activities (no occurred dates)
- **event**: Specific datable occurrence (MUST set occurred_start/end)
- **other**: Catch-all
- **observations**: Things that can be inferred/deduced from the conversation - COMPLETE SENTENCE with subject
- BAD: "traveled to Miami" (fragment)
- GOOD: "Calvin traveled to Miami for the photo shoot"
- BAD: "won dance trophies" (fragment)
- GOOD: "Gina won dance trophies in past competitions"
- More examples: "She knows programming from previous projects", "They own a house in the suburbs", "He has experience with public speaking"
- TRAVEL: "doing the shoot in Miami" "Calvin traveled to Miami for the shoot"
- POSSESSION: "my trophy" "She won the trophy"
- CAPABILITIES: "she coded it" "She knows programming"
### 4.2 fact_type (subject matter)
- **world**: Everything NOT involving assistant (user background, other people, events)
- **assistant**: Interactions BY or TO assistant (requests, recommendations, actions in THIS conversation)
### Example extraction:
Rule: If it would exist without this conversation world. If only exists because of this conversation assistant.
**Input**: "I used to compete in dance competitions - my fav memory was when my team won first place at regionals at age fifteen. It was an awesome feeling of accomplishment!"
SECTION 5: ENTITIES & CAUSALITY
**Output**:
```
factual_core: "Gina's team won first place at a regional dance competition when she was 15"
emotional_significance: "This was Gina's favorite memory; she felt an awesome sense of accomplishment"
reasoning_motivation: null
preferences_opinions: null
sensory_details: null
```
### 5.1 ENTITIES
Extract: People names, organizations, specific places, products
Skip: Generic relations (mom, friend), pronouns, common nouns
**Combined result**: "Gina's team won first place at a regional dance competition when she was 15 - This was Gina's favorite memory; she felt an awesome sense of accomplishment"
### 5.2 CAUSAL RELATIONS
Link facts when explicit causation: causes, caused_by, enables, prevents"""
### CRITICAL: Never strip away dimensions!
- BAD: Only extracting factual_core and ignoring emotional context
- GOOD: Capturing ALL dimensions present in the text
- BAD: Using fragments like "felt happy" or "loves pizza"
- GOOD: Using complete sentences like "She felt happy about the news" or "John loves pizza and orders it weekly"
## TEMPORAL CLASSIFICATION (fact_kind field) - About WHEN/TIMING
**WARNING**: Do NOT confuse fact_kind with fact_type (see below)! These are DIFFERENT fields!
### fact_kind determines if occurred dates are set:
**`conversation`** - General info, activities, preferences, ongoing things
- NO occurred_start/end (leave null)
- Examples: "Jon is expanding his studio", "Jon loves dance", "Gina's ideal studio is by water"
**`event`** - Specific datable occurrence (competition, wedding, meeting, trip, loss, start/end of something)
- MUST set occurred_start/end
- Ask: "Is this a SPECIFIC EVENT with a DATE?"
- Examples: "Dance competition on May 15", "Lost job in January 2023", "Wedding next Saturday"
**`other`** - Anything else that doesn't fit above
- NO occurred_start/end (leave null)
- Catch-all to not lose information
### Rules:
1. **ALWAYS include dates in fact text** - "in January 2023", "on May 15, 2024"
2. **Only 'event' gets occurred dates** - conversation and other = null
3. **SPLIT events from conversation facts** - "Jon is expanding his studio (conversation) and hosting a competition next month (event)" 2 separate facts!
## CAUSAL RELATIONSHIPS
When splitting related facts, link them with causal_relations:
- **causes**: This fact causes the target
- **caused_by**: This fact was caused by target
- **enables/prevents**: This fact enables/prevents the target
Only link when there's explicit or clear implicit causation ("because", "so", "therefore").
## FACT TYPE CLASSIFICATION - The Simple Rule
**WARNING**: Do NOT confuse fact_type with fact_kind (see above)! These are DIFFERENT fields!
- fact_kind = temporal nature (conversation/event/other)
- fact_type = who/what this is about (world/assistant)
### The Rule: Everything NOT involving the assistant = 'world'
- **'world'**: Facts about people, places, events, things that exist independently of assistant interactions
- **User's background/experience**: "User worked as marketing specialist at startup", "User has 5 years of Python experience"
- **User's skills/knowledge**: "User has used Trello", "User is familiar with Kanban methodology", "User knows React"
- **User's preferences/interests**: "User prefers async communication", "User is interested in exploring project management tools"
- **Other people's lives**: "Sarah got promoted", "John traveled to Paris", "Mom retired last year"
- **Events and facts**: "The meeting was cancelled", "The project launched in 2023"
- **RULE**: If it would still be true even if this conversation never happened **world**
- **'assistant'**: Interactions BY or TO the assistant (what happened in THIS conversation)
- **User's questions/requests TO assistant**: "User asked about ClickUp features", "User requested comparison between tools", "User wanted to know strengths and weaknesses"
- **Assistant's actions/responses**: "I recommended trying meditation", "I explained the difference between Trello and ClickUp", "I suggested exploring alternatives"
- **Conversational events**: "User thanked me for the suggestion", "I clarified the technical details"
- Use "user" or their name for user's questions/requests
- Use FIRST PERSON ("I") for assistant's actions
- **RULE**: If this only exists because of this conversation with the assistant **assistant**
**CRITICAL EXAMPLES**:
- "User worked at startup" **world** (would be true even without this conversation)
- "User asked me about ClickUp" **assistant** (only exists because of this conversation)
- "User has experience with Trello" **world** (independent fact about user)
- "User wanted to explore options" Could be either:
- **world** if it's a general preference: "User is interested in exploring project management alternatives"
- **assistant** if it's what they expressed in this conversation: "User asked me to help explore other options"
**Real Example**:
User says: "I've used Trello in my previous role as a marketing specialist at a small startup and I'm familiar with its features. But I'm interested in exploring other options as well. Could you tell me more about ClickUp?"
Extract these facts:
1. **world**: "User worked as marketing specialist at small startup"
2. **world**: "User has used Trello in previous role"
3. **world**: "User is familiar with Trello features"
4. **world**: "User is interested in exploring project management alternatives"
5. **assistant**: "User asked me about ClickUp and how it differs from Trello"
**Speaker attribution**: If context says "Your name: Marcus", extract 'assistant' facts from both "Marcus:" and "Assistant:" lines.
## WHAT TO SKIP
- Greetings, filler words, pure reactions ("wow", "cool")
- Structural statements ("let's get started", "see you next time")
- Calls to action ("subscribe", "follow")
## EXAMPLE: SPLITTING CONVERSATION VS EVENT FACTS
**Input (conversation date: April 3, 2023):**
"I'm expanding my dance studio's social media presence and offering workshops to local schools. I'm also hosting a dance competition next month to showcase local talent. The dancers are so excited!"
**Output (2 facts - conversation + event):**
**Fact 1 (kind=conversation - ongoing activities, no occurred dates):**
```
fact_kind: "conversation"
factual_core: "Jon is expanding his dance studio's social media presence in April 2023; offering workshops and classes to local schools and centers; seeing progress and dancers are excited"
emotional_significance: "excited and proud of progress"
preferences_opinions: "Jon loves giving dancers a place to express themselves"
observations: "Jon owns/runs a dance studio"
occurred_start: null conversation kind = no occurred dates
occurred_end: null
```
**Fact 2 (kind=event - specific datable occurrence):**
```
fact_kind: "event"
factual_core: "Jon will host a dance competition in May 2023 to showcase local talent and bring attention to his studio"
emotional_significance: "excited about the event"
occurred_start: "2023-05-01T00:00:00Z" event kind = HAS occurred dates
occurred_end: "2023-05-31T23:59:59Z"
```
** BAD:** Combining both into one fact with occurred=May (makes ongoing activities look like they happened in May!)
## TEXT TO EXTRACT FROM:
{chunk}
## CRITICAL REMINDERS:
1. **NEVER MISS USER REQUESTS** - If user asks assistant to do something ("write...", "create...", "help me..."), extract BOTH the request AND the response as separate BANK facts!
2. **BANK FACT PERSPECTIVE** - Use "I" for assistant actions ("I recommended", "I wrote"), use "user" or their name for user actions ("User requested", "Marcus said")
3. **COMBINE SIMPLE Q&A** - Merge simple informational questions with answers. But don't merge user requests - extract them separately!
4. **CAPTURE ALL MEANINGFUL CONTENT** - Activities, encouragement (with specific words!), recommendations, reactions, preferences
5. **CONVERT RELATIVE DATES TO SPECIFIC DATES** - "last week" "around August 16" (NOT "in August"!), "yesterday" "on August 18". Be precise!
6. **CAPTURE WHAT WAS SAID** - "Gina said Jon is perfect mentor with determination" NOT "Jon received encouragement". Preserve the actual content!
7. **FACT_KIND DETERMINES OCCURRED DATES** - Only 'event' gets occurred_start/end. 'conversation' and 'other' = null
8. **CAPTURE PREFERENCES** - "ideal", "favorite", "love" preferences_opinions
9. **CAPTURE EXACT ADJECTIVES** - Use the EXACT words! "awesome" not "amazing", "epic" not "perfect" sensory_details
10. **CAPTURE OBSERVATIONS** - "shooting in Miami" observations: "traveled to Miami". Infer travel, achievements, capabilities!"""
import logging
from openai import BadRequestError
@ -559,19 +407,25 @@ occurred_end: "2023-05-31T23:59:59Z"
max_retries = 2
last_error = None
# inject all the chunk metadata for better reasoning
chunk_data = json.dumps({
"chunk_index": chunk_index,
"total_chunks": total_chunks,
"event_date": event_date.isoformat(),
"context": context,
"chunk_content": chunk
})
for attempt in range(max_retries):
try:
# Get raw JSON response without strict Pydantic validation
# We'll handle the data leniently to be resilient to LLM weirdness
extraction_response_json = await llm_config.call(
messages=[
{
"role": "system",
"content": "Extract ALL meaningful content. NEVER MISS USER REQUESTS - if user asks assistant to do something ('write...', 'create...', 'help me...'), extract BOTH request AND response as separate BANK facts! COMBINE simple informational Q&A. BANK facts: use 'I' for assistant actions ('I recommended'), use 'user'/name for user actions ('User requested', 'Marcus said'). CONVERT RELATIVE DATES TO SPECIFIC DATES ('last week''around Aug 16' NOT 'in August'!). factual_core = WHAT was said, not THAT something was said! fact_kind: 'conversation'/'event'/'other'. Only 'event' gets occurred dates. Optional fields: include 'entities', 'causal_relations', 'occurred_start', 'occurred_end', 'emotional_significance', 'reasoning_motivation', 'preferences_opinions', 'sensory_details', 'observations' only if they have meaningful values (can omit if not applicable)."
"content": prompt
},
{
"role": "user",
"content": prompt
"content": chunk_data
}
],
response_format=FactExtractionResponse,
@ -596,7 +450,7 @@ occurred_end: "2023-05-31T23:59:59Z"
if not raw_facts:
logger.warning(
f"LLM response missing 'facts' field or returned empty list. "
f"Keys: {list(extraction_response_json.keys())}"
f"Response: {extraction_response_json}"
)
for i, llm_fact in enumerate(raw_facts):
@ -653,6 +507,9 @@ occurred_end: "2023-05-31T23:59:59Z"
'sensory_details', 'observations']:
value = get_value(field)
if value:
# Handle case where LLM returns list instead of string
if isinstance(value, list):
value = '; '.join(str(v) for v in value)
fact_data[field] = value
dimension_parts.append(value)

View file

@ -37,6 +37,7 @@ async def insert_facts_batch(
# Prepare data for batch insert
fact_texts = []
embeddings = []
event_dates = []
occurred_starts = []
occurred_ends = []
mentioned_ats = []
@ -52,6 +53,9 @@ async def insert_facts_batch(
fact_texts.append(fact.fact_text)
# Convert embedding to string for asyncpg vector type
embeddings.append(str(fact.embedding))
# event_date: Use occurred_start if available, otherwise use mentioned_at
# This maintains backward compatibility while handling None occurred_start
event_dates.append(fact.occurred_start if fact.occurred_start is not None else fact.mentioned_at)
occurred_starts.append(fact.occurred_start)
occurred_ends.append(fact.occurred_end)
mentioned_ats.append(fact.mentioned_at)
@ -65,7 +69,6 @@ async def insert_facts_batch(
document_ids.append(document_id)
# Batch insert all facts
# Note: event_date is set to occurred_start for backward compatibility
results = await conn.fetch(
"""
INSERT INTO memory_units (bank_id, text, embedding, event_date, occurred_start, occurred_end, mentioned_at,
@ -79,7 +82,7 @@ async def insert_facts_batch(
bank_id,
fact_texts,
embeddings,
occurred_starts, # event_date (for backward compatibility)
event_dates, # event_date: occurred_start if available, else mentioned_at
occurred_starts,
occurred_ends,
mentioned_ats,

View file

@ -102,8 +102,8 @@ class ProcessedFact:
embedding: List[float]
# Temporal data
occurred_start: datetime
occurred_end: datetime
occurred_start: Optional[datetime]
occurred_end: Optional[datetime]
mentioned_at: datetime
# Context and metadata
@ -146,9 +146,9 @@ class ProcessedFact:
"""
from datetime import datetime, timezone
# Use occurred dates if available, otherwise use mentioned_at
occurred_start = extracted_fact.occurred_start or extracted_fact.mentioned_at
occurred_end = extracted_fact.occurred_end or extracted_fact.mentioned_at
# Use occurred dates only if explicitly provided by LLM
occurred_start = extracted_fact.occurred_start
occurred_end = extracted_fact.occurred_end
mentioned_at = extracted_fact.mentioned_at or datetime.now(timezone.utc)
# Convert entity strings to EntityRef objects

View file

@ -141,10 +141,16 @@ class EmbeddedPostgres:
Downloads and installs the binary if not already present.
"""
if self.is_installed():
logger.info(f"pg0 already installed at {self.binary_path}")
return
logger.info("Installing pg0 CLI...")
# Log platform information
binary_name = get_platform_binary_name()
logger.info(f"Detected platform: system={platform.system()}, machine={platform.machine()}")
logger.info(f"Will download binary: {binary_name}")
# Create install directory
self.install_dir.mkdir(parents=True, exist_ok=True)

View file

@ -0,0 +1,69 @@
"""Test to verify mentioned_at uses event_date, not now()"""
import asyncio
from datetime import datetime, timezone, timedelta
from hindsight_api.engine.memory_engine import MemoryEngine
async def test_mentioned_at_uses_event_date():
"""Verify that mentioned_at is set to event_date, not now()"""
# Use a date that's clearly not "now"
past_date = datetime(2020, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
memory = MemoryEngine()
await memory.initialize()
try:
bank_id = "test_mentioned_at_debug"
# Store with explicit past event_date
unit_ids = await memory.retain_async(
bank_id=bank_id,
content="Alex went to the store.",
context="test",
event_date=past_date
)
print(f"\n✅ Stored {len(unit_ids)} units")
# Recall and check mentioned_at
result = await memory.recall_async(
bank_id=bank_id,
query="store",
max_tokens=500
)
print(f"✅ Found {len(result.results)} facts")
for i, fact in enumerate(result.results, 1):
print(f"\nFact {i}:")
print(f" Text: {fact.text[:80]}...")
print(f" mentioned_at: {fact.mentioned_at}")
print(f" occurred_start: {fact.occurred_start}")
# Parse mentioned_at
if isinstance(fact.mentioned_at, str):
mentioned_dt = datetime.fromisoformat(fact.mentioned_at.replace('Z', '+00:00'))
else:
mentioned_dt = fact.mentioned_at
# Check if mentioned_at matches our event_date
time_diff = abs((mentioned_dt - past_date).total_seconds())
if time_diff < 60:
print(f" ✅ mentioned_at correctly set to event_date")
else:
print(f" ❌ mentioned_at is {mentioned_dt}, expected {past_date}")
print(f" Time difference: {time_diff} seconds")
# Check if it's close to now()
now_diff = abs((mentioned_dt - datetime.now(timezone.utc)).total_seconds())
if now_diff < 60:
print(f" ⚠️ mentioned_at is using now() instead of event_date!")
await memory.delete_bank(bank_id)
finally:
await memory.close()
if __name__ == "__main__":
asyncio.run(test_mentioned_at_uses_event_date())

View file

@ -2,9 +2,12 @@
Test retain function and chunk storage.
"""
import pytest
import logging
from datetime import datetime, timezone
from hindsight_api.engine.memory_engine import Budget
logger = logging.getLogger(__name__)
@pytest.mark.asyncio
async def test_retain_with_chunks(memory):
@ -415,6 +418,95 @@ async def test_mentioned_at_vs_occurred(memory):
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_occurred_dates_not_defaulted(memory):
"""
Test that occurred_start and occurred_end are NOT defaulted to mentioned_at.
This is a regression test for a bug where occurred dates were incorrectly
defaulting to mentioned_at when the LLM didn't provide them.
Scenario: Store a fact where occurred dates are not applicable (current observation)
- mentioned_at should be set (to event_date or now())
- occurred_start and occurred_end should be None (not defaulted to mentioned_at)
"""
bank_id = f"test_occurred_not_defaulted_{datetime.now(timezone.utc).timestamp()}"
try:
# Store a current observation where occurred dates don't make sense
# Use present tense to avoid LLM extracting past dates
event_date = datetime(2024, 2, 10, 15, 30, tzinfo=timezone.utc)
unit_ids = await memory.retain_async(
bank_id=bank_id,
content="Alice likes coffee. The weather is sunny today.",
context="current observations",
event_date=event_date
)
assert len(unit_ids) > 0, "Should create memory unit"
# Recall and check that occurred dates are None
result = await memory.recall_async(
bank_id=bank_id,
query="What does Alice like?",
budget=Budget.LOW,
max_tokens=500,
fact_type=["world", "opinion"]
)
assert len(result.results) > 0, "Should recall the fact"
fact = result.results[0]
# mentioned_at should be set
assert fact.mentioned_at is not None, "mentioned_at should be set"
# Parse mentioned_at
if isinstance(fact.mentioned_at, str):
mentioned_dt = datetime.fromisoformat(fact.mentioned_at.replace('Z', '+00:00'))
else:
mentioned_dt = fact.mentioned_at
# Verify it matches event_date
time_diff = abs((event_date - mentioned_dt).total_seconds())
assert time_diff < 60, f"mentioned_at should match event_date, but diff is {time_diff}s"
# CRITICAL: occurred_start and occurred_end should be None
# They should NOT default to mentioned_at
if fact.occurred_start is not None:
# If occurred_start is set, it means the LLM extracted it
# In this case, log it but don't fail (LLM behavior can vary)
print(f"⚠ LLM extracted occurred_start: {fact.occurred_start}")
print(f" This test expects None for present-tense observations")
else:
print(f"✓ occurred_start is correctly None (not defaulted to mentioned_at)")
if fact.occurred_end is not None:
print(f"⚠ LLM extracted occurred_end: {fact.occurred_end}")
print(f" This test expects None for present-tense observations")
else:
print(f"✓ occurred_end is correctly None (not defaulted to mentioned_at)")
# At least verify they're not equal to mentioned_at if they are set
if fact.occurred_start is not None:
if isinstance(fact.occurred_start, str):
occurred_start_dt = datetime.fromisoformat(fact.occurred_start.replace('Z', '+00:00'))
else:
occurred_start_dt = fact.occurred_start
# If they're equal, it suggests the old defaulting bug
if occurred_start_dt == mentioned_dt:
raise AssertionError(
f"occurred_start should NOT be defaulted to mentioned_at! "
f"occurred_start={occurred_start_dt}, mentioned_at={mentioned_dt}"
)
print(f"✓ Test passed: occurred dates are not incorrectly defaulted to mentioned_at")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_mentioned_at_from_context_string(memory):
"""
@ -1107,3 +1199,399 @@ async def test_chunks_truncation_behavior(memory):
finally:
await memory.delete_bank(bank_id)
# ============================================================
# Memory Links Tests
# ============================================================
@pytest.mark.asyncio
async def test_temporal_links_creation(memory):
"""
Test that temporal links are created between facts with nearby event dates.
Temporal links connect facts that occurred close in time (within 24 hours).
"""
bank_id = f"test_temporal_links_{datetime.now(timezone.utc).timestamp()}"
try:
# Store facts with nearby timestamps (within 24 hours)
base_date = datetime(2024, 1, 15, 10, 0, 0, tzinfo=timezone.utc)
# Fact 1 at 10:00 AM
unit_ids_1 = await memory.retain_async(
bank_id=bank_id,
content="Alice started working on the authentication module.",
context="daily standup",
event_date=base_date
)
# Fact 2 at 2:00 PM same day (4 hours later)
unit_ids_2 = await memory.retain_async(
bank_id=bank_id,
content="Bob reviewed the API design document.",
context="daily standup",
event_date=base_date.replace(hour=14)
)
# Fact 3 at 9:00 AM next day (23 hours later)
unit_ids_3 = await memory.retain_async(
bank_id=bank_id,
content="Charlie deployed the new database schema.",
context="daily standup",
event_date=base_date.replace(day=16, hour=9)
)
assert len(unit_ids_1) > 0 and len(unit_ids_2) > 0 and len(unit_ids_3) > 0
logger.info(f"Created {len(unit_ids_1) + len(unit_ids_2) + len(unit_ids_3)} facts")
# Query the memory_links table to verify temporal links exist
async with memory._pool.acquire() as conn:
# Get all temporal links for these units
all_unit_ids = unit_ids_1 + unit_ids_2 + unit_ids_3
temporal_links = await conn.fetch(
"""
SELECT from_unit_id, to_unit_id, link_type, weight
FROM memory_links
WHERE from_unit_id::text = ANY($1)
AND link_type = 'temporal'
ORDER BY weight DESC
""",
all_unit_ids
)
logger.info(f"Found {len(temporal_links)} temporal links")
# Should have temporal links between the facts
assert len(temporal_links) > 0, "Should have created temporal links between facts with nearby dates"
# Verify link properties
for link in temporal_links:
from_id = str(link['from_unit_id'])
to_id = str(link['to_unit_id'])
logger.info(f" Link: {from_id[:8]}... -> {to_id[:8]}... (weight: {link['weight']:.2f})")
assert link['link_type'] == 'temporal', "Link type should be 'temporal'"
assert 0.0 <= link['weight'] <= 1.0, "Weight should be between 0 and 1"
logger.info("Temporal links created successfully with proper weights")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_semantic_links_creation(memory):
"""
Test that semantic links are created between facts with similar content.
Semantic links connect facts that are semantically similar based on embeddings.
"""
bank_id = f"test_semantic_links_{datetime.now(timezone.utc).timestamp()}"
try:
# Store facts with similar semantic content
unit_ids_1 = await memory.retain_async(
bank_id=bank_id,
content="Alice is an expert in Python programming and has built many web applications.",
context="team skills"
)
# Similar content - should create semantic link
unit_ids_2 = await memory.retain_async(
bank_id=bank_id,
content="Bob is proficient in Python development and specializes in building APIs.",
context="team skills"
)
# Different content - less likely to create strong semantic link
unit_ids_3 = await memory.retain_async(
bank_id=bank_id,
content="The quarterly sales meeting is scheduled for next Tuesday at 3 PM.",
context="calendar events"
)
assert len(unit_ids_1) > 0 and len(unit_ids_2) > 0 and len(unit_ids_3) > 0
logger.info(f"Created {len(unit_ids_1) + len(unit_ids_2) + len(unit_ids_3)} facts")
# Query the memory_links table to verify semantic links exist
async with memory._pool.acquire() as conn:
all_unit_ids = unit_ids_1 + unit_ids_2 + unit_ids_3
semantic_links = await conn.fetch(
"""
SELECT from_unit_id, to_unit_id, link_type, weight
FROM memory_links
WHERE from_unit_id::text = ANY($1)
AND link_type = 'semantic'
ORDER BY weight DESC
""",
all_unit_ids
)
logger.info(f"Found {len(semantic_links)} semantic links")
# Should have semantic links between similar facts
assert len(semantic_links) > 0, "Should have created semantic links between similar facts"
# Verify link properties
for link in semantic_links:
from_id = str(link['from_unit_id'])
to_id = str(link['to_unit_id'])
logger.info(f" Link: {from_id[:8]}... -> {to_id[:8]}... (weight: {link['weight']:.3f})")
assert link['link_type'] == 'semantic', "Link type should be 'semantic'"
assert 0.0 <= link['weight'] <= 1.0, "Weight should be between 0 and 1"
# Semantic links typically have weight >= 0.7 (threshold)
assert link['weight'] >= 0.7, f"Semantic links should have weight >= 0.7, got {link['weight']}"
logger.info("Semantic links created successfully between similar content")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_entity_links_creation(memory):
"""
Test that entity links are created between facts that mention the same entities.
Entity links connect facts that reference the same person, place, or concept.
This is core functionality and should work consistently.
"""
bank_id = f"test_entity_links_{datetime.now(timezone.utc).timestamp()}"
try:
# Store facts that mention the same entities
unit_ids_1 = await memory.retain_async(
bank_id=bank_id,
content="Alice joined Google as a software engineer in 2020.",
context="career history"
)
# Mentions same entity (Alice) - should create entity link
unit_ids_2 = await memory.retain_async(
bank_id=bank_id,
content="Alice led the development of the new authentication system.",
context="project updates"
)
# Mentions same entity (Google) - should create entity link
unit_ids_3 = await memory.retain_async(
bank_id=bank_id,
content="Google announced new cloud services at their annual conference.",
context="tech news"
)
# Different entities - no entity link expected
unit_ids_4 = await memory.retain_async(
bank_id=bank_id,
content="Bob works at Meta on machine learning infrastructure.",
context="career history"
)
assert len(unit_ids_1) > 0 and len(unit_ids_2) > 0 and len(unit_ids_3) > 0 and len(unit_ids_4) > 0
logger.info(f"Created {len(unit_ids_1) + len(unit_ids_2) + len(unit_ids_3) + len(unit_ids_4)} facts")
# Query the memory_links table to verify entity links exist
async with memory._pool.acquire() as conn:
all_unit_ids = unit_ids_1 + unit_ids_2 + unit_ids_3 + unit_ids_4
entity_links = await conn.fetch(
"""
SELECT from_unit_id, to_unit_id, link_type, weight, entity_id
FROM memory_links
WHERE from_unit_id::text = ANY($1)
AND link_type = 'entity'
ORDER BY from_unit_id, to_unit_id
""",
all_unit_ids
)
logger.info(f"Found {len(entity_links)} entity links")
# Entity extraction is core functionality and should work
assert len(entity_links) > 0, "Should have created entity links between facts with shared entities (Alice, Google)"
# Verify link properties
entities_seen = set()
for link in entity_links:
entity_id = link['entity_id']
entities_seen.add(str(entity_id))
from_id = str(link['from_unit_id'])
to_id = str(link['to_unit_id'])
logger.info(f" Link: {from_id[:8]}... -> {to_id[:8]}... via entity {str(entity_id)[:8]}...")
assert link['link_type'] == 'entity', "Link type should be 'entity'"
assert link['weight'] == 1.0, "Entity links should have weight 1.0"
assert entity_id is not None, "Entity links must reference an entity_id"
logger.info(f"Entity links created successfully for {len(entities_seen)} unique entities")
# Verify bidirectional links (entity links should be bidirectional)
link_pairs = set()
for link in entity_links:
from_id = str(link['from_unit_id'])
to_id = str(link['to_unit_id'])
entity_id = str(link['entity_id'])
link_pairs.add((from_id, to_id, entity_id))
# Check that for each (A -> B) link, there's a (B -> A) link with same entity
for from_id, to_id, entity_id in link_pairs:
reverse_exists = (to_id, from_id, entity_id) in link_pairs
assert reverse_exists, f"Entity links should be bidirectional: missing reverse link for {from_id[:8]} -> {to_id[:8]}"
logger.info("Entity links are properly bidirectional")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_causal_links_creation(memory):
"""
Test that causal links are created between facts with causal relationships.
Causal links connect facts where one causes, enables, or prevents another.
Note: This depends on LLM extracting causal relationships, which may be non-deterministic.
"""
bank_id = f"test_causal_links_{datetime.now(timezone.utc).timestamp()}"
try:
# Store content with explicit causal relationships
# Using clear cause-and-effect language to maximize LLM detection
content = """
Alice completed the authentication module on Monday. Because Alice finished the auth module,
Bob was able to start integrating it with the API on Tuesday. Bob's API integration enabled
Charlie to begin testing the complete user flow on Wednesday. The successful testing caused
the team to schedule the production deployment for Friday.
"""
unit_ids = await memory.retain_async(
bank_id=bank_id,
content=content,
context="project timeline"
)
assert len(unit_ids) > 0, "Should have created facts"
logger.info(f"Created {len(unit_ids)} facts from causal content")
# Query the memory_links table to check for causal links
async with memory._pool.acquire() as conn:
causal_links = await conn.fetch(
"""
SELECT from_unit_id, to_unit_id, link_type, weight
FROM memory_links
WHERE from_unit_id::text = ANY($1)
AND link_type IN ('causes', 'caused_by', 'enables', 'prevents')
ORDER BY link_type, weight DESC
""",
unit_ids
)
logger.info(f"Found {len(causal_links)} causal links")
if len(causal_links) > 0:
# Verify link properties
causal_types = {}
for link in causal_links:
link_type = link['link_type']
causal_types[link_type] = causal_types.get(link_type, 0) + 1
from_id = str(link['from_unit_id'])
to_id = str(link['to_unit_id'])
logger.info(f" Link: {from_id[:8]}... -> {to_id[:8]}... ({link_type}, weight: {link['weight']:.2f})")
assert link['link_type'] in ['causes', 'caused_by', 'enables', 'prevents'], \
f"Causal link type must be valid, got '{link['link_type']}'"
assert 0.0 <= link['weight'] <= 1.0, "Weight should be between 0 and 1"
logger.info("Causal links created successfully:")
for link_type, count in causal_types.items():
logger.info(f" - {link_type}: {count} links")
else:
logger.warning("No causal links detected (LLM may not have extracted causal relationships)")
logger.info(" This is expected as causal extraction depends on LLM interpretation")
# This test passes even if no causal links are found, since causal extraction
# is non-deterministic and depends on LLM behavior
logger.info("Test completed (causal link extraction is LLM-dependent)")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_all_link_types_together(memory):
"""
Integration test: Verify all link types can be created in a single retain operation.
Tests that temporal, semantic, entity, and potentially causal links are all
created when appropriate conditions are met.
"""
bank_id = f"test_all_links_{datetime.now(timezone.utc).timestamp()}"
try:
# Store multiple related facts that should trigger all link types
base_date = datetime(2024, 1, 15, 10, 0, 0, tzinfo=timezone.utc)
# Fact 1: Alice at time T
unit_ids_1 = await memory.retain_async(
bank_id=bank_id,
content="Alice completed the Python backend service for the authentication system.",
context="sprint review",
event_date=base_date
)
# Fact 2: Related to Alice, similar topic (Python), close in time
unit_ids_2 = await memory.retain_async(
bank_id=bank_id,
content="Alice optimized the Python code and improved the authentication performance by 40%.",
context="sprint review",
event_date=base_date.replace(hour=14) # Same day, 4 hours later
)
# Fact 3: Related to Alice, different topic but same entity
unit_ids_3 = await memory.retain_async(
bank_id=bank_id,
content="Alice presented the security architecture at the team meeting.",
context="team meeting",
event_date=base_date.replace(day=16) # Next day
)
assert len(unit_ids_1) > 0 and len(unit_ids_2) > 0 and len(unit_ids_3) > 0
logger.info(f"Created {len(unit_ids_1) + len(unit_ids_2) + len(unit_ids_3)} facts")
# Query for all link types
async with memory._pool.acquire() as conn:
all_unit_ids = unit_ids_1 + unit_ids_2 + unit_ids_3
all_links = await conn.fetch(
"""
SELECT link_type, COUNT(*) as count
FROM memory_links
WHERE from_unit_id::text = ANY($1)
GROUP BY link_type
ORDER BY link_type
""",
all_unit_ids
)
logger.info("Link types created:")
link_types_found = {}
for row in all_links:
link_type = row['link_type']
count = row['count']
link_types_found[link_type] = count
logger.info(f" - {link_type}: {count} links")
# Should have temporal, semantic, and entity links
assert 'temporal' in link_types_found, "Should have temporal links (facts with nearby dates)"
assert 'semantic' in link_types_found, "Should have semantic links (similar content about Python/auth)"
assert 'entity' in link_types_found, "Should have entity links (all mention Alice)"
logger.info(f"Successfully created {len(link_types_found)} different link types")
logger.info("All major link types (temporal, semantic, entity) are working correctly")
finally:
await memory.delete_bank(bank_id)

View file

@ -73,7 +73,7 @@ pub fn get(
Ok(doc) => {
if output_format == OutputFormat::Pretty {
ui::print_info(&format!("Document: {}", doc.id));
println!(" Agent ID: {}", doc.agent_id);
println!(" Bank ID: {}", doc.bank_id);
println!(" Created: {}", doc.created_at);
println!(" Updated: {}", doc.updated_at);
println!(" Memory Units: {}", doc.memory_unit_count);

View file

@ -25,14 +25,14 @@ pub fn list(
if output_format == OutputFormat::Pretty {
ui::print_section_header(&format!("Entities for Bank: {}", bank_id));
if response.entities.is_empty() {
if response.items.is_empty() {
ui::print_warning("No entities found");
return Ok(());
}
println!("Total entities: {}\n", response.entities.len());
println!("Total entities: {}\n", response.items.len());
for entity in &response.entities {
for entity in &response.items {
println!("ID: {}", entity.id);
println!(" Name: {}", entity.canonical_name);
println!(" Mentions: {}", entity.mention_count);

File diff suppressed because it is too large Load diff

View file

@ -8,8 +8,8 @@ use crate::config;
use crate::output::{self, OutputFormat};
use crate::ui;
// Import Budget type from generated client
use hindsight_client::types::Budget;
// Import types from generated client
use hindsight_client::types::{Budget, ChunkIncludeOptions, IncludeOptions};
// Helper function to parse budget string to Budget enum
fn parse_budget(budget: &str) -> Budget {
@ -28,6 +28,8 @@ pub fn recall(
budget: String,
max_tokens: i64,
trace: bool,
include_chunks: bool,
chunk_max_tokens: i64,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
@ -37,6 +39,18 @@ pub fn recall(
None
};
// Build include options if chunks are requested
let include = if include_chunks {
Some(IncludeOptions {
chunks: Some(ChunkIncludeOptions {
max_tokens: chunk_max_tokens,
}),
entities: None,
})
} else {
None
};
let request = RecallRequest {
query,
types: if fact_type.is_empty() { None } else { Some(fact_type) },
@ -45,7 +59,7 @@ pub fn recall(
trace,
query_timestamp: None,
filters: None,
include: None,
include,
};
let response = client.recall(agent_id, &request, verbose);
@ -57,7 +71,7 @@ pub fn recall(
match response {
Ok(result) => {
if output_format == OutputFormat::Pretty {
ui::print_search_results(&result, trace);
ui::print_search_results(&result, trace, include_chunks);
} else {
output::print_output(&result, output_format)?;
}

View file

@ -160,6 +160,14 @@ enum MemoryCommands {
/// Show trace information
#[arg(long)]
trace: bool,
/// Include chunks in results
#[arg(long)]
include_chunks: bool,
/// Maximum tokens for chunks (only used with --include-chunks)
#[arg(long, default_value = "8192")]
chunk_max_tokens: i64,
},
/// Generate answers using bank identity (reflect/reasoning)
@ -379,8 +387,8 @@ fn run() -> Result<()> {
},
Commands::Memory(memory_cmd) => match memory_cmd {
MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace } => {
commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, verbose, output_format)
MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens } => {
commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, verbose, output_format)
}
MemoryCommands::Reflect { bank_id, query, budget, context } => {
commands::memory::reflect(&client, &bank_id, query, budget, context, verbose, output_format)

View file

@ -1,5 +1,6 @@
use crate::api::{BankProfileResponse, RecallResult, RecallResponse, ReflectResponse};
use colored::*;
use hindsight_client::types::ChunkData;
use indicatif::{ProgressBar, ProgressStyle};
use std::io::{self, Write};
@ -60,7 +61,29 @@ pub fn print_fact(fact: &RecallResult, show_activation: bool) {
println!();
}
pub fn print_search_results(response: &RecallResponse, show_trace: bool) {
pub fn print_chunk(chunk: &ChunkData) {
println!(" {}", "─── Source Chunk ───".bright_blue());
// Split text into lines and indent each line
for line in chunk.text.lines() {
println!(" {}", line.bright_white());
}
if chunk.truncated {
println!(" {}", "[Truncated due to token limit]".bright_yellow());
}
println!(" {}: {} | {}: {}",
"Chunk ID".bright_black(),
chunk.id.bright_black(),
"Index".bright_black(),
chunk.chunk_index.to_string().bright_black()
);
println!();
}
pub fn print_search_results(response: &RecallResponse, show_trace: bool, show_chunks: bool) {
let results = &response.results;
print_section_header(&format!("Search Results ({})", results.len()));
@ -70,6 +93,17 @@ pub fn print_search_results(response: &RecallResponse, show_trace: bool) {
for (i, fact) in results.iter().enumerate() {
println!("{}", format!(" Result #{}", i + 1).bright_black());
print_fact(fact, true);
// Show chunk if available and requested
if show_chunks {
if let Some(chunk_id) = &fact.chunk_id {
if let Some(chunks) = &response.chunks {
if let Some(chunk) = chunks.get(chunk_id) {
print_chunk(chunk);
}
}
}
}
}
}

View file

@ -1,6 +1,7 @@
hindsight_client_api/__init__.py
hindsight_client_api/api/__init__.py
hindsight_client_api/api/default_api.py
hindsight_client_api/api/monitoring_api.py
hindsight_client_api/api_client.py
hindsight_client_api/api_response.py
hindsight_client_api/configuration.py
@ -10,6 +11,8 @@ hindsight_client_api/docs/BankListItem.md
hindsight_client_api/docs/BankListResponse.md
hindsight_client_api/docs/BankProfileResponse.md
hindsight_client_api/docs/Budget.md
hindsight_client_api/docs/ChunkData.md
hindsight_client_api/docs/ChunkIncludeOptions.md
hindsight_client_api/docs/CreateBankRequest.md
hindsight_client_api/docs/DefaultApi.md
hindsight_client_api/docs/DeleteResponse.md
@ -27,6 +30,7 @@ hindsight_client_api/docs/ListDocumentsResponse.md
hindsight_client_api/docs/ListMemoryUnitsResponse.md
hindsight_client_api/docs/MemoryItem.md
hindsight_client_api/docs/MetadataFilter.md
hindsight_client_api/docs/MonitoringApi.md
hindsight_client_api/docs/PersonalityTraits.md
hindsight_client_api/docs/RecallRequest.md
hindsight_client_api/docs/RecallResponse.md
@ -48,6 +52,8 @@ hindsight_client_api/models/bank_list_item.py
hindsight_client_api/models/bank_list_response.py
hindsight_client_api/models/bank_profile_response.py
hindsight_client_api/models/budget.py
hindsight_client_api/models/chunk_data.py
hindsight_client_api/models/chunk_include_options.py
hindsight_client_api/models/create_bank_request.py
hindsight_client_api/models/delete_response.py
hindsight_client_api/models/document_response.py
@ -85,6 +91,8 @@ hindsight_client_api/test/test_bank_list_item.py
hindsight_client_api/test/test_bank_list_response.py
hindsight_client_api/test/test_bank_profile_response.py
hindsight_client_api/test/test_budget.py
hindsight_client_api/test/test_chunk_data.py
hindsight_client_api/test/test_chunk_include_options.py
hindsight_client_api/test/test_create_bank_request.py
hindsight_client_api/test/test_default_api.py
hindsight_client_api/test/test_delete_response.py
@ -102,6 +110,7 @@ hindsight_client_api/test/test_list_documents_response.py
hindsight_client_api/test/test_list_memory_units_response.py
hindsight_client_api/test/test_memory_item.py
hindsight_client_api/test/test_metadata_filter.py
hindsight_client_api/test/test_monitoring_api.py
hindsight_client_api/test/test_personality_traits.py
hindsight_client_api/test/test_recall_request.py
hindsight_client_api/test/test_recall_response.py

View file

@ -1 +0,0 @@
# Hindisight python client

View file

@ -18,6 +18,7 @@ __version__ = "0.0.7"
# Define package exports
__all__ = [
"MonitoringApi",
"DefaultApi",
"ApiResponse",
"ApiClient",
@ -34,6 +35,8 @@ __all__ = [
"BankListResponse",
"BankProfileResponse",
"Budget",
"ChunkData",
"ChunkIncludeOptions",
"CreateBankRequest",
"DeleteResponse",
"DocumentResponse",
@ -66,6 +69,7 @@ __all__ = [
]
# import apis into sdk package
from hindsight_client_api.api.monitoring_api import MonitoringApi as MonitoringApi
from hindsight_client_api.api.default_api import DefaultApi as DefaultApi
# import ApiClient
@ -86,6 +90,8 @@ from hindsight_client_api.models.bank_list_item import BankListItem as BankListI
from hindsight_client_api.models.bank_list_response import BankListResponse as BankListResponse
from hindsight_client_api.models.bank_profile_response import BankProfileResponse as BankProfileResponse
from hindsight_client_api.models.budget import Budget as Budget
from hindsight_client_api.models.chunk_data import ChunkData as ChunkData
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions as ChunkIncludeOptions
from hindsight_client_api.models.create_bank_request import CreateBankRequest as CreateBankRequest
from hindsight_client_api.models.delete_response import DeleteResponse as DeleteResponse
from hindsight_client_api.models.document_response import DocumentResponse as DocumentResponse

View file

@ -1,5 +1,6 @@
# flake8: noqa
# import apis into api package
from hindsight_client_api.api.monitoring_api import MonitoringApi
from hindsight_client_api.api.default_api import DefaultApi

View file

@ -3699,7 +3699,7 @@ class DefaultApi:
) -> ListMemoryUnitsResponse:
"""List memory units
List memory units with pagination and optional full-text search. Supports filtering by type.
List memory units with pagination and optional full-text search. Supports filtering by type. Results are sorted by most recent first (mentioned_at DESC, then created_at DESC).
:param bank_id: (required)
:type bank_id: str
@ -3783,7 +3783,7 @@ class DefaultApi:
) -> ApiResponse[ListMemoryUnitsResponse]:
"""List memory units
List memory units with pagination and optional full-text search. Supports filtering by type.
List memory units with pagination and optional full-text search. Supports filtering by type. Results are sorted by most recent first (mentioned_at DESC, then created_at DESC).
:param bank_id: (required)
:type bank_id: str
@ -3867,7 +3867,7 @@ class DefaultApi:
) -> RESTResponseType:
"""List memory units
List memory units with pagination and optional full-text search. Supports filtering by type.
List memory units with pagination and optional full-text search. Supports filtering by type. Results are sorted by most recent first (mentioned_at DESC, then created_at DESC).
:param bank_id: (required)
:type bank_id: str

View file

@ -0,0 +1,281 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import warnings
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
from typing import Any, Dict, List, Optional, Tuple, Union
from typing_extensions import Annotated
from typing import Any
from hindsight_client_api.api_client import ApiClient, RequestSerialized
from hindsight_client_api.api_response import ApiResponse
from hindsight_client_api.rest import RESTResponseType
class MonitoringApi:
"""NOTE: This class is auto generated by OpenAPI Generator
Ref: https://openapi-generator.tech
Do not edit the class manually.
"""
def __init__(self, api_client=None) -> None:
if api_client is None:
api_client = ApiClient.get_default()
self.api_client = api_client
@validate_call
async def metrics_endpoint_metrics_get(
self,
_request_timeout: Union[
None,
Annotated[StrictFloat, Field(gt=0)],
Tuple[
Annotated[StrictFloat, Field(gt=0)],
Annotated[StrictFloat, Field(gt=0)]
]
] = None,
_request_auth: Optional[Dict[StrictStr, Any]] = None,
_content_type: Optional[StrictStr] = None,
_headers: Optional[Dict[StrictStr, Any]] = None,
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> object:
"""Prometheus metrics endpoint
Exports metrics in Prometheus format for scraping
:param _request_timeout: timeout setting for this request. If one
number provided, it will be total request
timeout. It can also be a pair (tuple) of
(connection, read) timeouts.
:type _request_timeout: int, tuple(int, int), optional
:param _request_auth: set to override the auth_settings for an a single
request; this effectively ignores the
authentication in the spec for a single request.
:type _request_auth: dict, optional
:param _content_type: force content-type for the request.
:type _content_type: str, Optional
:param _headers: set to override the headers for a single
request; this effectively ignores the headers
in the spec for a single request.
:type _headers: dict, optional
:param _host_index: set to override the host_index for a single
request; this effectively ignores the host_index
in the spec for a single request.
:type _host_index: int, optional
:return: Returns the result object.
""" # noqa: E501
_param = self._metrics_endpoint_metrics_get_serialize(
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "object",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
await response_data.read()
return self.api_client.response_deserialize(
response_data=response_data,
response_types_map=_response_types_map,
).data
@validate_call
async def metrics_endpoint_metrics_get_with_http_info(
self,
_request_timeout: Union[
None,
Annotated[StrictFloat, Field(gt=0)],
Tuple[
Annotated[StrictFloat, Field(gt=0)],
Annotated[StrictFloat, Field(gt=0)]
]
] = None,
_request_auth: Optional[Dict[StrictStr, Any]] = None,
_content_type: Optional[StrictStr] = None,
_headers: Optional[Dict[StrictStr, Any]] = None,
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> ApiResponse[object]:
"""Prometheus metrics endpoint
Exports metrics in Prometheus format for scraping
:param _request_timeout: timeout setting for this request. If one
number provided, it will be total request
timeout. It can also be a pair (tuple) of
(connection, read) timeouts.
:type _request_timeout: int, tuple(int, int), optional
:param _request_auth: set to override the auth_settings for an a single
request; this effectively ignores the
authentication in the spec for a single request.
:type _request_auth: dict, optional
:param _content_type: force content-type for the request.
:type _content_type: str, Optional
:param _headers: set to override the headers for a single
request; this effectively ignores the headers
in the spec for a single request.
:type _headers: dict, optional
:param _host_index: set to override the host_index for a single
request; this effectively ignores the host_index
in the spec for a single request.
:type _host_index: int, optional
:return: Returns the result object.
""" # noqa: E501
_param = self._metrics_endpoint_metrics_get_serialize(
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "object",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
await response_data.read()
return self.api_client.response_deserialize(
response_data=response_data,
response_types_map=_response_types_map,
)
@validate_call
async def metrics_endpoint_metrics_get_without_preload_content(
self,
_request_timeout: Union[
None,
Annotated[StrictFloat, Field(gt=0)],
Tuple[
Annotated[StrictFloat, Field(gt=0)],
Annotated[StrictFloat, Field(gt=0)]
]
] = None,
_request_auth: Optional[Dict[StrictStr, Any]] = None,
_content_type: Optional[StrictStr] = None,
_headers: Optional[Dict[StrictStr, Any]] = None,
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> RESTResponseType:
"""Prometheus metrics endpoint
Exports metrics in Prometheus format for scraping
:param _request_timeout: timeout setting for this request. If one
number provided, it will be total request
timeout. It can also be a pair (tuple) of
(connection, read) timeouts.
:type _request_timeout: int, tuple(int, int), optional
:param _request_auth: set to override the auth_settings for an a single
request; this effectively ignores the
authentication in the spec for a single request.
:type _request_auth: dict, optional
:param _content_type: force content-type for the request.
:type _content_type: str, Optional
:param _headers: set to override the headers for a single
request; this effectively ignores the headers
in the spec for a single request.
:type _headers: dict, optional
:param _host_index: set to override the host_index for a single
request; this effectively ignores the host_index
in the spec for a single request.
:type _host_index: int, optional
:return: Returns the result object.
""" # noqa: E501
_param = self._metrics_endpoint_metrics_get_serialize(
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "object",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
return response_data.response
def _metrics_endpoint_metrics_get_serialize(
self,
_request_auth,
_content_type,
_headers,
_host_index,
) -> RequestSerialized:
_host = None
_collection_formats: Dict[str, str] = {
}
_path_params: Dict[str, str] = {}
_query_params: List[Tuple[str, str]] = []
_header_params: Dict[str, Optional[str]] = _headers or {}
_form_params: List[Tuple[str, str]] = []
_files: Dict[
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
] = {}
_body_params: Optional[bytes] = None
# process the path parameters
# process the query parameters
# process the header parameters
# process the form parameters
# process the body parameter
# set the HTTP header `Accept`
if 'Accept' not in _header_params:
_header_params['Accept'] = self.api_client.select_header_accept(
[
'application/json'
]
)
# authentication setting
_auth_settings: List[str] = [
]
return self.api_client.param_serialize(
method='GET',
resource_path='/metrics',
path_params=_path_params,
query_params=_query_params,
header_params=_header_params,
body=_body_params,
post_params=_form_params,
files=_files,
auth_settings=_auth_settings,
collection_formats=_collection_formats,
_host=_host,
_request_auth=_request_auth
)

View file

@ -0,0 +1,33 @@
# ChunkData
Chunk data for a single chunk.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | |
**text** | **str** | |
**chunk_index** | **int** | |
**truncated** | **bool** | Whether the chunk text was truncated due to token limits | [optional] [default to False]
## Example
```python
from hindsight_client_api.models.chunk_data import ChunkData
# TODO update the JSON string below
json = "{}"
# create an instance of ChunkData from a JSON string
chunk_data_instance = ChunkData.from_json(json)
# print the JSON string representation of the object
print(ChunkData.to_json())
# convert the object into a dict
chunk_data_dict = chunk_data_instance.to_dict()
# create an instance of ChunkData from a dict
chunk_data_from_dict = ChunkData.from_dict(chunk_data_dict)
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -0,0 +1,30 @@
# ChunkIncludeOptions
Options for including chunks in recall results.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**max_tokens** | **int** | Maximum tokens for chunks (chunks may be truncated) | [optional] [default to 8192]
## Example
```python
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions
# TODO update the JSON string below
json = "{}"
# create an instance of ChunkIncludeOptions from a JSON string
chunk_include_options_instance = ChunkIncludeOptions.from_json(json)
# print the JSON string representation of the object
print(ChunkIncludeOptions.to_json())
# convert the object into a dict
chunk_include_options_dict = chunk_include_options_instance.to_dict()
# create an instance of ChunkIncludeOptions from a dict
chunk_include_options_from_dict = ChunkIncludeOptions.from_dict(chunk_include_options_dict)
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -953,7 +953,7 @@ No authorization required
List memory units
List memory units with pagination and optional full-text search. Supports filtering by type.
List memory units with pagination and optional full-text search. Supports filtering by type. Results are sorted by most recent first (mentioned_at DESC, then created_at DESC).
### Example

View file

@ -7,7 +7,7 @@ Response model for get document endpoint.
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | |
**agent_id** | **str** | |
**bank_id** | **str** | |
**original_text** | **str** | |
**content_hash** | **str** | |
**created_at** | **str** | |

View file

@ -6,7 +6,7 @@ Response model for entity list endpoint.
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**entities** | [**List[EntityListItem]**](EntityListItem.md) | |
**items** | [**List[EntityListItem]**](EntityListItem.md) | |
## Example

View file

@ -7,6 +7,7 @@ Options for including additional data in recall results.
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**entities** | [**EntityIncludeOptions**](EntityIncludeOptions.md) | | [optional]
**chunks** | [**ChunkIncludeOptions**](ChunkIncludeOptions.md) | | [optional]
## Example

View file

@ -0,0 +1,72 @@
# hindsight_client_api.MonitoringApi
All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**metrics_endpoint_metrics_get**](MonitoringApi.md#metrics_endpoint_metrics_get) | **GET** /metrics | Prometheus metrics endpoint
# **metrics_endpoint_metrics_get**
> object metrics_endpoint_metrics_get()
Prometheus metrics endpoint
Exports metrics in Prometheus format for scraping
### Example
```python
import hindsight_client_api
from hindsight_client_api.rest import ApiException
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = hindsight_client_api.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
async with hindsight_client_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = hindsight_client_api.MonitoringApi(api_client)
try:
# Prometheus metrics endpoint
api_response = await api_instance.metrics_endpoint_metrics_get()
print("The response of MonitoringApi->metrics_endpoint_metrics_get:\n")
pprint(api_response)
except Exception as e:
print("Exception when calling MonitoringApi->metrics_endpoint_metrics_get: %s\n" % e)
```
### Parameters
This endpoint does not need any parameter.
### Return type
**object**
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Successful Response | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View file

@ -9,6 +9,7 @@ Name | Type | Description | Notes
**results** | [**List[RecallResult]**](RecallResult.md) | |
**trace** | **Dict[str, object]** | | [optional]
**entities** | [**Dict[str, EntityStateResponse]**](EntityStateResponse.md) | | [optional]
**chunks** | [**Dict[str, ChunkData]**](ChunkData.md) | | [optional]
## Example

View file

@ -16,6 +16,7 @@ Name | Type | Description | Notes
**mentioned_at** | **str** | | [optional]
**document_id** | **str** | | [optional]
**metadata** | **Dict[str, str]** | | [optional]
**chunk_id** | **str** | | [optional]
## Example

View file

@ -19,6 +19,8 @@ from hindsight_client_api.models.bank_list_item import BankListItem
from hindsight_client_api.models.bank_list_response import BankListResponse
from hindsight_client_api.models.bank_profile_response import BankProfileResponse
from hindsight_client_api.models.budget import Budget
from hindsight_client_api.models.chunk_data import ChunkData
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions
from hindsight_client_api.models.create_bank_request import CreateBankRequest
from hindsight_client_api.models.delete_response import DeleteResponse
from hindsight_client_api.models.document_response import DocumentResponse

View file

@ -0,0 +1,93 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
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 typing import Optional, Set
from typing_extensions import Self
class ChunkData(BaseModel):
"""
Chunk data for a single chunk.
""" # noqa: E501
id: StrictStr
text: StrictStr
chunk_index: StrictInt
truncated: Optional[StrictBool] = Field(default=False, description="Whether the chunk text was truncated due to token limits")
__properties: ClassVar[List[str]] = ["id", "text", "chunk_index", "truncated"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of ChunkData from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of ChunkData from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"id": obj.get("id"),
"text": obj.get("text"),
"chunk_index": obj.get("chunk_index"),
"truncated": obj.get("truncated") if obj.get("truncated") is not None else False
})
return _obj

View file

@ -0,0 +1,87 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictInt
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self
class ChunkIncludeOptions(BaseModel):
"""
Options for including chunks in recall results.
""" # noqa: E501
max_tokens: Optional[StrictInt] = Field(default=8192, description="Maximum tokens for chunks (chunks may be truncated)")
__properties: ClassVar[List[str]] = ["max_tokens"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of ChunkIncludeOptions from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of ChunkIncludeOptions from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"max_tokens": obj.get("max_tokens") if obj.get("max_tokens") is not None else 8192
})
return _obj

View file

@ -27,13 +27,13 @@ class DocumentResponse(BaseModel):
Response model for get document endpoint.
""" # noqa: E501
id: StrictStr
agent_id: StrictStr
bank_id: StrictStr
original_text: StrictStr
content_hash: Optional[StrictStr]
created_at: StrictStr
updated_at: StrictStr
memory_unit_count: StrictInt
__properties: ClassVar[List[str]] = ["id", "agent_id", "original_text", "content_hash", "created_at", "updated_at", "memory_unit_count"]
__properties: ClassVar[List[str]] = ["id", "bank_id", "original_text", "content_hash", "created_at", "updated_at", "memory_unit_count"]
model_config = ConfigDict(
populate_by_name=True,
@ -92,7 +92,7 @@ class DocumentResponse(BaseModel):
_obj = cls.model_validate({
"id": obj.get("id"),
"agent_id": obj.get("agent_id"),
"bank_id": obj.get("bank_id"),
"original_text": obj.get("original_text"),
"content_hash": obj.get("content_hash"),
"created_at": obj.get("created_at"),

View file

@ -27,8 +27,8 @@ class EntityListResponse(BaseModel):
"""
Response model for entity list endpoint.
""" # noqa: E501
entities: List[EntityListItem]
__properties: ClassVar[List[str]] = ["entities"]
items: List[EntityListItem]
__properties: ClassVar[List[str]] = ["items"]
model_config = ConfigDict(
populate_by_name=True,
@ -69,13 +69,13 @@ class EntityListResponse(BaseModel):
exclude=excluded_fields,
exclude_none=True,
)
# override the default output from pydantic by calling `to_dict()` of each item in entities (list)
# override the default output from pydantic by calling `to_dict()` of each item in items (list)
_items = []
if self.entities:
for _item_entities in self.entities:
if _item_entities:
_items.append(_item_entities.to_dict())
_dict['entities'] = _items
if self.items:
for _item_items in self.items:
if _item_items:
_items.append(_item_items.to_dict())
_dict['items'] = _items
return _dict
@classmethod
@ -88,7 +88,7 @@ class EntityListResponse(BaseModel):
return cls.model_validate(obj)
_obj = cls.model_validate({
"entities": [EntityListItem.from_dict(_item) for _item in obj["entities"]] if obj.get("entities") is not None else None
"items": [EntityListItem.from_dict(_item) for _item in obj["items"]] if obj.get("items") is not None else None
})
return _obj

View file

@ -19,6 +19,7 @@ import json
from pydantic import BaseModel, ConfigDict
from typing import Any, ClassVar, Dict, List, Optional
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions
from hindsight_client_api.models.entity_include_options import EntityIncludeOptions
from typing import Optional, Set
from typing_extensions import Self
@ -28,7 +29,8 @@ class IncludeOptions(BaseModel):
Options for including additional data in recall results.
""" # noqa: E501
entities: Optional[EntityIncludeOptions] = None
__properties: ClassVar[List[str]] = ["entities"]
chunks: Optional[ChunkIncludeOptions] = None
__properties: ClassVar[List[str]] = ["entities", "chunks"]
model_config = ConfigDict(
populate_by_name=True,
@ -72,11 +74,19 @@ class IncludeOptions(BaseModel):
# override the default output from pydantic by calling `to_dict()` of entities
if self.entities:
_dict['entities'] = self.entities.to_dict()
# override the default output from pydantic by calling `to_dict()` of chunks
if self.chunks:
_dict['chunks'] = self.chunks.to_dict()
# set to None if entities (nullable) is None
# and model_fields_set contains the field
if self.entities is None and "entities" in self.model_fields_set:
_dict['entities'] = None
# set to None if chunks (nullable) is None
# and model_fields_set contains the field
if self.chunks is None and "chunks" in self.model_fields_set:
_dict['chunks'] = None
return _dict
@classmethod
@ -89,7 +99,8 @@ class IncludeOptions(BaseModel):
return cls.model_validate(obj)
_obj = cls.model_validate({
"entities": EntityIncludeOptions.from_dict(obj["entities"]) if obj.get("entities") is not None else None
"entities": EntityIncludeOptions.from_dict(obj["entities"]) if obj.get("entities") is not None else None,
"chunks": ChunkIncludeOptions.from_dict(obj["chunks"]) if obj.get("chunks") is not None else None
})
return _obj

View file

@ -19,6 +19,7 @@ import json
from pydantic import BaseModel, ConfigDict
from typing import Any, ClassVar, Dict, List, Optional
from hindsight_client_api.models.chunk_data import ChunkData
from hindsight_client_api.models.entity_state_response import EntityStateResponse
from hindsight_client_api.models.recall_result import RecallResult
from typing import Optional, Set
@ -31,7 +32,8 @@ class RecallResponse(BaseModel):
results: List[RecallResult]
trace: Optional[Dict[str, Any]] = None
entities: Optional[Dict[str, EntityStateResponse]] = None
__properties: ClassVar[List[str]] = ["results", "trace", "entities"]
chunks: Optional[Dict[str, ChunkData]] = None
__properties: ClassVar[List[str]] = ["results", "trace", "entities", "chunks"]
model_config = ConfigDict(
populate_by_name=True,
@ -86,6 +88,13 @@ class RecallResponse(BaseModel):
if self.entities[_key_entities]:
_field_dict[_key_entities] = self.entities[_key_entities].to_dict()
_dict['entities'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of each value in chunks (dict)
_field_dict = {}
if self.chunks:
for _key_chunks in self.chunks:
if self.chunks[_key_chunks]:
_field_dict[_key_chunks] = self.chunks[_key_chunks].to_dict()
_dict['chunks'] = _field_dict
# set to None if trace (nullable) is None
# and model_fields_set contains the field
if self.trace is None and "trace" in self.model_fields_set:
@ -96,6 +105,11 @@ class RecallResponse(BaseModel):
if self.entities is None and "entities" in self.model_fields_set:
_dict['entities'] = None
# set to None if chunks (nullable) is None
# and model_fields_set contains the field
if self.chunks is None and "chunks" in self.model_fields_set:
_dict['chunks'] = None
return _dict
@classmethod
@ -115,6 +129,12 @@ class RecallResponse(BaseModel):
for _k, _v in obj["entities"].items()
)
if obj.get("entities") is not None
else None,
"chunks": dict(
(_k, ChunkData.from_dict(_v))
for _k, _v in obj["chunks"].items()
)
if obj.get("chunks") is not None
else None
})
return _obj

View file

@ -36,7 +36,8 @@ class RecallResult(BaseModel):
mentioned_at: Optional[StrictStr] = None
document_id: Optional[StrictStr] = None
metadata: Optional[Dict[str, StrictStr]] = None
__properties: ClassVar[List[str]] = ["id", "text", "type", "entities", "context", "occurred_start", "occurred_end", "mentioned_at", "document_id", "metadata"]
chunk_id: Optional[StrictStr] = None
__properties: ClassVar[List[str]] = ["id", "text", "type", "entities", "context", "occurred_start", "occurred_end", "mentioned_at", "document_id", "metadata", "chunk_id"]
model_config = ConfigDict(
populate_by_name=True,
@ -117,6 +118,11 @@ class RecallResult(BaseModel):
if self.metadata is None and "metadata" in self.model_fields_set:
_dict['metadata'] = None
# set to None if chunk_id (nullable) is None
# and model_fields_set contains the field
if self.chunk_id is None and "chunk_id" in self.model_fields_set:
_dict['chunk_id'] = None
return _dict
@classmethod
@ -138,7 +144,8 @@ class RecallResult(BaseModel):
"occurred_end": obj.get("occurred_end"),
"mentioned_at": obj.get("mentioned_at"),
"document_id": obj.get("document_id"),
"metadata": obj.get("metadata")
"metadata": obj.get("metadata"),
"chunk_id": obj.get("chunk_id")
})
return _obj

View file

@ -0,0 +1,57 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import unittest
from hindsight_client_api.models.chunk_data import ChunkData
class TestChunkData(unittest.TestCase):
"""ChunkData unit test stubs"""
def setUp(self):
pass
def tearDown(self):
pass
def make_instance(self, include_optional) -> ChunkData:
"""Test ChunkData
include_optional is a boolean, when False only required
params are included, when True both required and
optional params are included """
# uncomment below to create an instance of `ChunkData`
"""
model = ChunkData()
if include_optional:
return ChunkData(
id = '',
text = '',
chunk_index = 56,
truncated = True
)
else:
return ChunkData(
id = '',
text = '',
chunk_index = 56,
)
"""
def testChunkData(self):
"""Test ChunkData"""
# inst_req_only = self.make_instance(include_optional=False)
# inst_req_and_optional = self.make_instance(include_optional=True)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,51 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import unittest
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions
class TestChunkIncludeOptions(unittest.TestCase):
"""ChunkIncludeOptions unit test stubs"""
def setUp(self):
pass
def tearDown(self):
pass
def make_instance(self, include_optional) -> ChunkIncludeOptions:
"""Test ChunkIncludeOptions
include_optional is a boolean, when False only required
params are included, when True both required and
optional params are included """
# uncomment below to create an instance of `ChunkIncludeOptions`
"""
model = ChunkIncludeOptions()
if include_optional:
return ChunkIncludeOptions(
max_tokens = 56
)
else:
return ChunkIncludeOptions(
)
"""
def testChunkIncludeOptions(self):
"""Test ChunkIncludeOptions"""
# inst_req_only = self.make_instance(include_optional=False)
# inst_req_and_optional = self.make_instance(include_optional=True)
if __name__ == '__main__':
unittest.main()

View file

@ -36,7 +36,7 @@ class TestDocumentResponse(unittest.TestCase):
if include_optional:
return DocumentResponse(
id = '',
agent_id = '',
bank_id = '',
original_text = '',
content_hash = '',
created_at = '',
@ -46,7 +46,7 @@ class TestDocumentResponse(unittest.TestCase):
else:
return DocumentResponse(
id = '',
agent_id = '',
bank_id = '',
original_text = '',
content_hash = '',
created_at = '',

View file

@ -40,7 +40,9 @@ class TestEntityDetailResponse(unittest.TestCase):
mention_count = 56,
first_seen = '',
last_seen = '',
metadata = { },
metadata = {
'key' : null
},
observations = [
hindsight_client_api.models.entity_observation_response.EntityObservationResponse(
text = '',

View file

@ -40,7 +40,9 @@ class TestEntityListItem(unittest.TestCase):
mention_count = 56,
first_seen = '',
last_seen = '',
metadata = { }
metadata = {
'key' : null
}
)
else:
return EntityListItem(

View file

@ -35,13 +35,13 @@ class TestEntityListResponse(unittest.TestCase):
model = EntityListResponse()
if include_optional:
return EntityListResponse(
entities = [
items = [
{canonical_name=John, first_seen=2024-01-15T10:30:00Z, id=123e4567-e89b-12d3-a456-426614174000, last_seen=2024-02-01T14:00:00Z, mention_count=15}
]
)
else:
return EntityListResponse(
entities = [
items = [
{canonical_name=John, first_seen=2024-01-15T10:30:00Z, id=123e4567-e89b-12d3-a456-426614174000, last_seen=2024-02-01T14:00:00Z, mention_count=15}
],
)

View file

@ -36,26 +36,38 @@ class TestGraphDataResponse(unittest.TestCase):
if include_optional:
return GraphDataResponse(
nodes = [
{ }
{
'key' : null
}
],
edges = [
{ }
{
'key' : null
}
],
table_rows = [
{ }
{
'key' : null
}
],
total_units = 56
)
else:
return GraphDataResponse(
nodes = [
{ }
{
'key' : null
}
],
edges = [
{ }
{
'key' : null
}
],
table_rows = [
{ }
{
'key' : null
}
],
total_units = 56,
)

View file

@ -36,6 +36,8 @@ class TestIncludeOptions(unittest.TestCase):
if include_optional:
return IncludeOptions(
entities = hindsight_client_api.models.entity_include_options.EntityIncludeOptions(
max_tokens = 56, ),
chunks = hindsight_client_api.models.chunk_include_options.ChunkIncludeOptions(
max_tokens = 56, )
)
else:

View file

@ -36,7 +36,9 @@ class TestListDocumentsResponse(unittest.TestCase):
if include_optional:
return ListDocumentsResponse(
items = [
{ }
{
'key' : null
}
],
total = 56,
limit = 56,
@ -45,7 +47,9 @@ class TestListDocumentsResponse(unittest.TestCase):
else:
return ListDocumentsResponse(
items = [
{ }
{
'key' : null
}
],
total = 56,
limit = 56,

View file

@ -36,7 +36,9 @@ class TestListMemoryUnitsResponse(unittest.TestCase):
if include_optional:
return ListMemoryUnitsResponse(
items = [
{ }
{
'key' : null
}
],
total = 56,
limit = 56,
@ -45,7 +47,9 @@ class TestListMemoryUnitsResponse(unittest.TestCase):
else:
return ListMemoryUnitsResponse(
items = [
{ }
{
'key' : null
}
],
total = 56,
limit = 56,

View file

@ -0,0 +1,38 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import unittest
from hindsight_client_api.api.monitoring_api import MonitoringApi
class TestMonitoringApi(unittest.IsolatedAsyncioTestCase):
"""MonitoringApi unit test stubs"""
async def asyncSetUp(self) -> None:
self.api = MonitoringApi()
async def asyncTearDown(self) -> None:
await self.api.api_client.close()
async def test_metrics_endpoint_metrics_get(self) -> None:
"""Test case for metrics_endpoint_metrics_get
Prometheus metrics endpoint
"""
pass
if __name__ == '__main__':
unittest.main()

View file

@ -48,6 +48,8 @@ class TestRecallRequest(unittest.TestCase):
],
include = hindsight_client_api.models.include_options.IncludeOptions(
entities = hindsight_client_api.models.entity_include_options.EntityIncludeOptions(
max_tokens = 56, ),
chunks = hindsight_client_api.models.chunk_include_options.ChunkIncludeOptions(
max_tokens = 56, ), )
)
else:

View file

@ -36,9 +36,11 @@ class TestRecallResponse(unittest.TestCase):
if include_optional:
return RecallResponse(
results = [
{context=work info, document_id=session_abc123, entities=[Alice, Google], id=123e4567-e89b-12d3-a456-426614174000, mentioned_at=2024-01-15T10:30:00Z, metadata={source=slack}, occurred_end=2024-01-15T10:30:00Z, occurred_start=2024-01-15T10:30:00Z, text=Alice works at Google on the AI team, type=world}
{chunk_id=456e7890-e12b-34d5-a678-901234567890, context=work info, document_id=session_abc123, entities=[Alice, Google], id=123e4567-e89b-12d3-a456-426614174000, mentioned_at=2024-01-15T10:30:00Z, metadata={source=slack}, occurred_end=2024-01-15T10:30:00Z, occurred_start=2024-01-15T10:30:00Z, text=Alice works at Google on the AI team, type=world}
],
trace = { },
trace = {
'key' : null
},
entities = {
'key' : hindsight_client_api.models.entity_state_response.EntityStateResponse(
entity_id = '',
@ -48,12 +50,19 @@ class TestRecallResponse(unittest.TestCase):
text = '',
mentioned_at = '', )
], )
},
chunks = {
'key' : hindsight_client_api.models.chunk_data.ChunkData(
id = '',
text = '',
chunk_index = 56,
truncated = True, )
}
)
else:
return RecallResponse(
results = [
{context=work info, document_id=session_abc123, entities=[Alice, Google], id=123e4567-e89b-12d3-a456-426614174000, mentioned_at=2024-01-15T10:30:00Z, metadata={source=slack}, occurred_end=2024-01-15T10:30:00Z, occurred_start=2024-01-15T10:30:00Z, text=Alice works at Google on the AI team, type=world}
{chunk_id=456e7890-e12b-34d5-a678-901234567890, context=work info, document_id=session_abc123, entities=[Alice, Google], id=123e4567-e89b-12d3-a456-426614174000, mentioned_at=2024-01-15T10:30:00Z, metadata={source=slack}, occurred_end=2024-01-15T10:30:00Z, occurred_start=2024-01-15T10:30:00Z, text=Alice works at Google on the AI team, type=world}
],
)
"""

View file

@ -48,7 +48,8 @@ class TestRecallResult(unittest.TestCase):
document_id = '',
metadata = {
'key' : ''
}
},
chunk_id = ''
)
else:
return RecallResult(

View file

@ -86,9 +86,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cc"
version = "1.2.47"
version = "1.2.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
dependencies = [
"find-msvc-tools",
"shlex",
@ -641,9 +641,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.82"
version = "0.3.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
dependencies = [
"once_cell",
"wasm-bindgen",
@ -1081,9 +1081,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.13.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
dependencies = [
"zeroize",
]
@ -1572,9 +1572,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
dependencies = [
"pin-project-lite",
"tracing-core",
@ -1716,9 +1716,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.105"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
dependencies = [
"cfg-if",
"once_cell",
@ -1729,9 +1729,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.55"
version = "0.4.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
dependencies = [
"cfg-if",
"js-sys",
@ -1742,9 +1742,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.105"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1752,9 +1752,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.105"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [
"bumpalo",
"proc-macro2",
@ -1765,9 +1765,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.105"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
dependencies = [
"unicode-ident",
]
@ -1787,9 +1787,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.82"
version = "0.3.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
dependencies = [
"js-sys",
"wasm-bindgen",

View file

@ -1 +1 @@
{"rustc_fingerprint":12740812217871447607,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.91.1 (ed61e7d7e 2025-11-07)\nbinary: rustc\ncommit-hash: ed61e7d7e242494fb7057f2657300d9e77bb4fcb\ncommit-date: 2025-11-07\nhost: aarch64-apple-darwin\nrelease: 1.91.1\nLLVM version: 21.1.2\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/nicoloboschi/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":12740812217871447607,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/nicoloboschi/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.91.1 (ed61e7d7e 2025-11-07)\nbinary: rustc\ncommit-hash: ed61e7d7e242494fb7057f2657300d9e77bb4fcb\ncommit-date: 2025-11-07\nhost: aarch64-apple-darwin\nrelease: 1.91.1\nLLVM version: 21.1.2\n","stderr":""}},"successes":{}}

View file

@ -0,0 +1 @@
32eef61671486063

View file

@ -1 +1 @@
{"rustc":16243257175721966122,"features":"[]","declared_features":"[\"stream\", \"unstable\"]","target":15216351499943135959,"profile":5627820096486484124,"path":3294765106753457380,"deps":[[1074848931188612602,"atomic_waker",false,4889363751394578966],[1345404220202658316,"fnv",false,12262759022379011476],[2620434475832828286,"http",false,9979032511492736550],[6240934600354534560,"indexmap",false,14247778757879965049],[6355489020061627772,"bytes",false,15546652703430663087],[7013762810557009322,"futures_sink",false,6950808712564450941],[7620660491849607393,"futures_core",false,13556608982339264378],[7720834239451334583,"tokio",false,814226396053303386],[8606274917505247608,"tracing",false,16881066759846826124],[14180297684929992518,"tokio_util",false,17441115838095451012],[14767213526276824509,"slab",false,16871163405192104457]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/h2-923e5387638d1bd9/dep-lib-h2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":16243257175721966122,"features":"[]","declared_features":"[\"stream\", \"unstable\"]","target":15216351499943135959,"profile":5627820096486484124,"path":3294765106753457380,"deps":[[1074848931188612602,"atomic_waker",false,4889363751394578966],[1345404220202658316,"fnv",false,12262759022379011476],[2620434475832828286,"http",false,9979032511492736550],[6240934600354534560,"indexmap",false,14247778757879965049],[6355489020061627772,"bytes",false,15546652703430663087],[7013762810557009322,"futures_sink",false,6950808712564450941],[7620660491849607393,"futures_core",false,13556608982339264378],[7720834239451334583,"tokio",false,814226396053303386],[13455815276518097497,"tracing",false,6512599870775761065],[14180297684929992518,"tokio_util",false,17441115838095451012],[14767213526276824509,"slab",false,16871163405192104457]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/h2-860e5416284abc9d/dep-lib-h2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -0,0 +1 @@
{"rustc":16243257175721966122,"features":"[]","declared_features":"[]","target":6828276606420267087,"profile":2040997289075261528,"path":10763286916239946207,"deps":[[350039288653093011,"build_script_build",false,4231486804270383637],[503842845364652431,"chrono",false,11144948474405894614],[1046219396048762255,"progenitor_client",false,13036815077551201483],[2620434475832828286,"http",false,9979032511492736550],[5404511084185685755,"url",false,17079058419592311478],[5802782114936492624,"reqwest",false,16326245460864990945],[7720834239451334583,"tokio",false,814226396053303386],[8008191657135824715,"thiserror",false,1675330904495433212],[12832915883349295919,"serde_json",false,11663418101978700483],[13548984313718623784,"serde",false,17261882564294632758]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hindsight-client-26b0bc308cced3ae/dep-lib-hindsight_client","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":16243257175721966122,"features":"[]","declared_features":"[]","target":6828276606420267087,"profile":2040997289075261528,"path":10763286916239946207,"deps":[[350039288653093011,"build_script_build",false,2443880033246489740],[503842845364652431,"chrono",false,11144948474405894614],[1046219396048762255,"progenitor_client",false,16451196449178175391],[2620434475832828286,"http",false,9979032511492736550],[5404511084185685755,"url",false,17079058419592311478],[5802782114936492624,"reqwest",false,1592833881977325371],[7720834239451334583,"tokio",false,814226396053303386],[8008191657135824715,"thiserror",false,1675330904495433212],[12832915883349295919,"serde_json",false,11663418101978700483],[13548984313718623784,"serde",false,17261882564294632758]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hindsight-client-4329cb0e29911e3c/dep-lib-hindsight_client","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -0,0 +1 @@
{"rustc":16243257175721966122,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[350039288653093011,"build_script_build",false,7490509127350218047]],"local":[{"RerunIfChanged":{"output":"release/build/hindsight-client-45e816fa8febabac/output","paths":["/Users/nicoloboschi/dev/memory-poc/openapi.json"]}}],"rustflags":[],"config":0,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":16243257175721966122,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[350039288653093011,"build_script_build",false,14160702128762829566]],"local":[{"RerunIfChanged":{"output":"release/build/hindsight-client-9933d827d3e3d55a/output","paths":["/Users/nicoloboschi/dev/memory-poc/openapi.json"]}}],"rustflags":[],"config":0,"compile_kind":0}

View file

@ -1 +1 @@
{"rustc":16243257175721966122,"features":"[]","declared_features":"[]","target":5408242616063297496,"profile":1369601567987815722,"path":13767053534773805487,"deps":[[7988640081342112296,"syn",false,1809862803220272063],[9423015880379144908,"prettyplease",false,5158570001680563136],[9738901266855342370,"progenitor",false,7200400063597451810],[12832915883349295919,"serde_json",false,7203318985267246464],[16847286912798951732,"openapiv3",false,15609397813544834071]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hindsight-client-e0ea9bb30ad7f8a5/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":16243257175721966122,"features":"[]","declared_features":"[]","target":5408242616063297496,"profile":1369601567987815722,"path":13767053534773805487,"deps":[[7988640081342112296,"syn",false,1809862803220272063],[9423015880379144908,"prettyplease",false,5158570001680563136],[9738901266855342370,"progenitor",false,5156049257603408733],[12832915883349295919,"serde_json",false,7203318985267246464],[16847286912798951732,"openapiv3",false,15609397813544834071]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hindsight-client-b1a7aa8cd48fa221/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -0,0 +1 @@
ea6ea6cd23c4b79b

View file

@ -1 +1 @@
{"rustc":16243257175721966122,"features":"[\"client\", \"default\", \"http1\", \"http2\"]","declared_features":"[\"capi\", \"client\", \"default\", \"ffi\", \"full\", \"http1\", \"http2\", \"nightly\", \"server\", \"tracing\"]","target":9574292076208557625,"profile":5592815138508651293,"path":3727995574998783824,"deps":[[1074848931188612602,"atomic_waker",false,4889363751394578966],[1569313478171189446,"want",false,10044339025105331758],[1615478164327904835,"pin_utils",false,14683532508558912330],[1811549171721445101,"futures_channel",false,7366921400749317677],[1906322745568073236,"pin_project_lite",false,2890517474304306842],[2620434475832828286,"http",false,9979032511492736550],[3666196340704888985,"smallvec",false,2762008496713994936],[4133939468654419887,"h2",false,7767169915745739513],[6163892036024256188,"httparse",false,862024765873499899],[6355489020061627772,"bytes",false,15546652703430663087],[7620660491849607393,"futures_core",false,13556608982339264378],[7695812897323945497,"itoa",false,1828906794629363435],[7720834239451334583,"tokio",false,814226396053303386],[14084095096285906100,"http_body",false,6661408876623437285]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hyper-68b92baf42be0922/dep-lib-hyper","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":16243257175721966122,"features":"[\"client\", \"default\", \"http1\", \"http2\"]","declared_features":"[\"capi\", \"client\", \"default\", \"ffi\", \"full\", \"http1\", \"http2\", \"nightly\", \"server\", \"tracing\"]","target":9574292076208557625,"profile":5592815138508651293,"path":3727995574998783824,"deps":[[1074848931188612602,"atomic_waker",false,4889363751394578966],[1569313478171189446,"want",false,10044339025105331758],[1615478164327904835,"pin_utils",false,14683532508558912330],[1811549171721445101,"futures_channel",false,7366921400749317677],[1906322745568073236,"pin_project_lite",false,2890517474304306842],[2620434475832828286,"http",false,9979032511492736550],[3666196340704888985,"smallvec",false,2762008496713994936],[4133939468654419887,"h2",false,7160803058072874546],[6163892036024256188,"httparse",false,862024765873499899],[6355489020061627772,"bytes",false,15546652703430663087],[7620660491849607393,"futures_core",false,13556608982339264378],[7695812897323945497,"itoa",false,1828906794629363435],[7720834239451334583,"tokio",false,814226396053303386],[14084095096285906100,"http_body",false,6661408876623437285]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hyper-0c7c521e5ae1d609/dep-lib-hyper","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -0,0 +1 @@
{"rustc":16243257175721966122,"features":"[]","declared_features":"[\"alpn\", \"vendored\"]","target":11005878871305885301,"profile":2040997289075261528,"path":5681533078161436566,"deps":[[554721338292256162,"hyper_util",false,17873901682716089071],[784494742817713399,"tower_service",false,7356265403547447364],[4160778395972110362,"hyper",false,11220652654670016234],[6355489020061627772,"bytes",false,15546652703430663087],[7720834239451334583,"tokio",false,814226396053303386],[12186126227181294540,"tokio_native_tls",false,7383892010931007826],[16785601910559813697,"native_tls",false,18042093116010566256],[16900715236047033623,"http_body_util",false,7221940639537536546]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hyper-tls-34b77110d6698171/dep-lib-hyper_tls","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -1 +0,0 @@
{"rustc":16243257175721966122,"features":"[]","declared_features":"[\"alpn\", \"vendored\"]","target":11005878871305885301,"profile":2040997289075261528,"path":5681533078161436566,"deps":[[554721338292256162,"hyper_util",false,10324504803424377305],[784494742817713399,"tower_service",false,7356265403547447364],[4160778395972110362,"hyper",false,7467897318181879986],[6355489020061627772,"bytes",false,15546652703430663087],[7720834239451334583,"tokio",false,814226396053303386],[12186126227181294540,"tokio_native_tls",false,7383892010931007826],[16785601910559813697,"native_tls",false,18042093116010566256],[16900715236047033623,"http_body_util",false,7221940639537536546]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hyper-tls-59dc4b2da9834c15/dep-lib-hyper_tls","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

View file

@ -1 +1 @@
{"rustc":16243257175721966122,"features":"[\"client\", \"client-legacy\", \"client-proxy\", \"client-proxy-system\", \"default\", \"http1\", \"http2\", \"tokio\"]","declared_features":"[\"__internal_happy_eyeballs_tests\", \"client\", \"client-legacy\", \"client-proxy\", \"client-proxy-system\", \"default\", \"full\", \"http1\", \"http2\", \"server\", \"server-auto\", \"server-graceful\", \"service\", \"tokio\", \"tracing\"]","target":11100538814903412163,"profile":2040997289075261528,"path":3239747996768537754,"deps":[[95042085696191081,"ipnet",false,10405193789034471686],[784494742817713399,"tower_service",false,7356265403547447364],[985115344064483054,"system_configuration",false,4059817047406425165],[1811549171721445101,"futures_channel",false,7366921400749317677],[1906322745568073236,"pin_project_lite",false,2890517474304306842],[2620434475832828286,"http",false,9979032511492736550],[4160778395972110362,"hyper",false,7467897318181879986],[6355489020061627772,"bytes",false,15546652703430663087],[6803352382179706244,"percent_encoding",false,3530911331212444045],[7620660491849607393,"futures_core",false,13556608982339264378],[7720834239451334583,"tokio",false,814226396053303386],[8606274917505247608,"tracing",false,16881066759846826124],[10629569228670356391,"futures_util",false,13661049276535558845],[11499138078358568213,"libc",false,17790664046185964660],[11667313607130374549,"socket2",false,156739536956761713],[13077212702700853852,"base64",false,8599405015201889959],[14084095096285906100,"http_body",false,6661408876623437285]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hyper-util-2cb0a97ae1a39f9f/dep-lib-hyper_util","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
{"rustc":16243257175721966122,"features":"[\"client\", \"client-legacy\", \"client-proxy\", \"client-proxy-system\", \"default\", \"http1\", \"http2\", \"tokio\"]","declared_features":"[\"__internal_happy_eyeballs_tests\", \"client\", \"client-legacy\", \"client-proxy\", \"client-proxy-system\", \"default\", \"full\", \"http1\", \"http2\", \"server\", \"server-auto\", \"server-graceful\", \"service\", \"tokio\", \"tracing\"]","target":11100538814903412163,"profile":2040997289075261528,"path":3239747996768537754,"deps":[[95042085696191081,"ipnet",false,10405193789034471686],[784494742817713399,"tower_service",false,7356265403547447364],[985115344064483054,"system_configuration",false,4059817047406425165],[1811549171721445101,"futures_channel",false,7366921400749317677],[1906322745568073236,"pin_project_lite",false,2890517474304306842],[2620434475832828286,"http",false,9979032511492736550],[4160778395972110362,"hyper",false,11220652654670016234],[6355489020061627772,"bytes",false,15546652703430663087],[6803352382179706244,"percent_encoding",false,3530911331212444045],[7620660491849607393,"futures_core",false,13556608982339264378],[7720834239451334583,"tokio",false,814226396053303386],[10629569228670356391,"futures_util",false,13661049276535558845],[11499138078358568213,"libc",false,17790664046185964660],[11667313607130374549,"socket2",false,156739536956761713],[13077212702700853852,"base64",false,8599405015201889959],[13455815276518097497,"tracing",false,6512599870775761065],[14084095096285906100,"http_body",false,6661408876623437285]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/hyper-util-78bd33f05bd2355b/dep-lib-hyper_util","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}

Some files were not shown because too many files have changed in this diff Show more