diff --git a/hindsight-dev/hindsight_dev/sync_cookbook.py b/hindsight-dev/hindsight_dev/sync_cookbook.py index 362bd4c3..52f13a27 100644 --- a/hindsight-dev/hindsight_dev/sync_cookbook.py +++ b/hindsight-dev/hindsight_dev/sync_cookbook.py @@ -70,37 +70,137 @@ def extract_title_from_notebook(notebook_path: Path) -> str: def extract_description_from_notebook(notebook_path: Path) -> str | None: - """Extract first paragraph after title from notebook.""" + """Extract description from notebook metadata.""" try: content = json.loads(notebook_path.read_text()) - for cell in content.get("cells", []): - if cell.get("cell_type") == "markdown": - source = cell.get("source", []) - if isinstance(source, list): - source = "".join(source) - - lines = source.split("\n") - found_title = False - description = [] - - for line in lines: - if line.startswith("#"): - found_title = True - continue - if found_title and line.strip(): - if line.startswith("#"): - break - description.append(line.strip()) - if line.strip().endswith("."): - break - - if description: - return " ".join(description)[:200] + metadata = content.get("metadata", {}) + description = metadata.get("description", "") + if description: + return description[:200] except Exception: pass return None +def extract_tags_from_notebook(notebook_path: Path) -> list[str]: + """Extract tags from notebook metadata. + + Supports both array format and structured object format. + """ + try: + content = json.loads(notebook_path.read_text()) + metadata = content.get("metadata", {}) + tags = metadata.get("tags", []) + + # Array format: ["Python", "Client"] + if isinstance(tags, list): + return tags + + # Object format: { "language": "Python", "sdk": "Client", "topic": "Learning" } + if isinstance(tags, dict): + result = [] + for key in ["language", "sdk", "topic"]: + if key in tags and tags[key]: + result.append(tags[key]) + return result + except Exception: + pass + return [] + + +def extract_description_from_readme(readme_path: Path) -> str | None: + """Extract description from frontmatter in README.""" + try: + content = readme_path.read_text() + # Check for frontmatter + if content.startswith("---"): + end_idx = content.find("---", 3) + if end_idx > 0: + frontmatter = content[3:end_idx] + # Look for description: line + for line in frontmatter.split("\n"): + if line.strip().startswith("description:"): + desc = line.split("description:", 1)[1].strip() + # Remove quotes if present + desc = desc.strip('"').strip("'") + return desc[:200] + except Exception: + pass + return None + + +def extract_tags_from_readme(readme_path: Path) -> list[str]: + """Extract tags from frontmatter in README if present. + + Supports multiple formats: + - Array: tags: ["Python", "Client"] + - Structured YAML: tags:\n language: "Python"\n sdk: "Client" + - Object literal: tags: { language: "Python", sdk: "Client" } + """ + try: + content = readme_path.read_text() + # Check for frontmatter + if content.startswith("---"): + end_idx = content.find("---", 3) + if end_idx > 0: + frontmatter = content[3:end_idx] + lines = frontmatter.split("\n") + + # Look for tags: line + for i, line in enumerate(lines): + if line.strip().startswith("tags:"): + tags_str = line.split("tags:", 1)[1].strip() + + # Inline array format: tags: ["Python", "Client"] + if tags_str.startswith("["): + tags_str = tags_str.strip("[]") + return [t.strip().strip('"').strip("'") for t in tags_str.split(",")] + + # JavaScript object literal format: tags: { language: "Python", sdk: "Client", topic: "Learning" } + if tags_str.startswith("{"): + tags = [] + # Extract the entire object literal (might span multiple lines) + obj_str = tags_str + if "}" not in obj_str: + # Multi-line object - collect remaining lines + for j in range(i + 1, len(lines)): + obj_str += " " + lines[j].strip() + if "}" in lines[j]: + break + + # Parse the object literal + obj_str = obj_str.strip("{}") + # Split by comma and extract key-value pairs + for pair in obj_str.split(","): + if ":" in pair: + key, value = pair.split(":", 1) + value = value.strip().strip('"').strip("'") + if value: + tags.append(value) + return tags + + # Structured YAML format: + # tags: + # language: "Python" + # sdk: "Client" + if not tags_str or tags_str == "": + # Parse structured tags from following lines + tags = [] + for j in range(i + 1, len(lines)): + next_line = lines[j].strip() + if not next_line or not next_line.startswith(("language:", "sdk:", "topic:")): + break + # Extract value + if ":" in next_line: + value = next_line.split(":", 1)[1].strip().strip('"').strip("'") + if value: + tags.append(value) + return tags + except Exception: + pass + return [] + + def extract_title_from_readme(readme_path: Path) -> str | None: """Extract title from README's first # heading.""" try: @@ -187,12 +287,16 @@ def process_notebooks(cookbook_dir: Path, recipes_dir: Path) -> list[dict]: slug = slugify(notebook_path.name) title = extract_title_from_notebook(notebook_path) description = extract_description_from_notebook(notebook_path) + tags = extract_tags_from_notebook(notebook_path) print(f" Processing: {notebook_path.name} → {slug}.md") # Convert notebook to markdown md_content = convert_notebook_to_markdown(notebook_path) + # Strip any existing frontmatter from converted notebook + md_content = strip_frontmatter(md_content) + # Create recipe page with frontmatter notebook_url = f"https://github.com/vectorize-io/hindsight-cookbook/blob/main/notebooks/{notebook_path.name}" @@ -225,6 +329,7 @@ This recipe is available as an interactive Jupyter notebook. "slug": slug, "title": title, "description": description, + "tags": tags, "id": f"cookbook/recipes/{slug}", } ) @@ -232,6 +337,15 @@ This recipe is available as an interactive Jupyter notebook. return recipes +def strip_frontmatter(content: str) -> str: + """Remove frontmatter from markdown content.""" + if content.startswith("---"): + end_idx = content.find("---", 3) + if end_idx > 0: + return content[end_idx + 3 :].lstrip() + return content + + def process_applications(cookbook_dir: Path, apps_dir: Path) -> list[dict]: """Process application directories with README.md.""" apps = [] @@ -252,11 +366,14 @@ def process_applications(cookbook_dir: Path, apps_dir: Path) -> list[dict]: slug = entry.name title = extract_title_from_readme(readme_path) or " ".join(word.capitalize() for word in slug.split("-")) + description = extract_description_from_readme(readme_path) + tags = extract_tags_from_readme(readme_path) print(f" Processing app: {entry.name} → {slug}.md") - # Read README content + # Read README content and strip existing frontmatter readme_content = readme_path.read_text() + readme_content = strip_frontmatter(readme_content) # Create application page with frontmatter app_url = f"https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/{entry.name}" @@ -289,6 +406,8 @@ This is a complete, runnable application demonstrating Hindsight integration. { "slug": slug, "title": title, + "description": description, + "tags": tags, "id": f"cookbook/applications/{slug}", } ) @@ -297,61 +416,19 @@ This is a complete, runnable application demonstrating Hindsight integration. def update_sidebars(recipes: list[dict], apps: list[dict], sidebars_file: Path): - """Update sidebars.ts with new recipe and app entries.""" + """Update sidebars.ts - keep it simple with just the index.""" content = sidebars_file.read_text() - # Build recipe items - recipe_item_list = [] - for r in recipes: - label = r["title"].replace("'", "\\'") - recipe_item_list.append( - f""" {{ - type: 'doc', - id: '{r["id"]}', - label: '{label}', - }}""" - ) - recipe_items = ",\n".join(recipe_item_list) - - # Build app items - app_item_list = [] - for a in apps: - label = a["title"].replace("'", "\\'") - app_item_list.append( - f""" {{ - type: 'doc', - id: '{a["id"]}', - label: '{label}', - }}""" - ) - app_items = ",\n".join(app_item_list) - - new_cookbook_sidebar = f"""cookbookSidebar: [ - {{ + # Simple sidebar with just the cookbook index + new_cookbook_sidebar = """cookbookSidebar: [ + { type: 'doc', id: 'cookbook/index', - label: 'Overview', - }}, - {{ - type: 'category', - label: 'Recipes', - collapsible: false, - items: [ -{recipe_items} - ], - }}, - {{ - type: 'category', - label: 'Applications', - collapsible: false, - items: [ -{app_items} - ], - }}, + label: 'Cookbook', + }, ]""" - # Replace existing cookbookSidebar - match the full sidebar array including nested structures - # We need to match balanced brackets + # Replace existing cookbookSidebar start = content.find("cookbookSidebar:") if start == -1: raise ValueError("cookbookSidebar not found in sidebars.ts") @@ -405,31 +482,113 @@ def clean_description(desc: str) -> str: return desc +def convert_tags_to_structured(tags: list[str]) -> dict[str, str]: + """Convert list of tags to structured format. + + New format has 2 tags: + - sdk: Package name (detected from tag values) + - topic: anything else (Learning, Quick Start, etc.) + + If sdk tag starts with '@vectorize-io', it's Node.js. + Otherwise assumes Python. + """ + structured = {} + topic_tags = {"Learning", "Quick Start", "Recommendation", "Chat"} + + for tag in tags: + # Check if it's a topic tag + if tag in topic_tags: + structured["topic"] = tag + # Check if it's already a package name (contains @ or -) + elif "@" in tag or (tag and not tag[0].isupper()): + structured["sdk"] = tag + else: + # Legacy tag values - map to new format + # For now, treat everything else as SDK/package identifier + structured["sdk"] = tag + + return structured + + def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path): """Update cookbook/index.mdx with recipe and app carousels.""" - # Build recipe items for the carousel + # Build recipe items for the carousel with descriptions and tags recipe_items = [] for r in recipes: title = r["title"].replace('"', '\\"') - recipe_items.append(f' {{ title: "{title}", href: "/cookbook/recipes/{r["slug"]}" }}') + description = r.get("description", "") + if description: + description = clean_description(description).replace('"', '\\"') + tags = r.get("tags", []) + + item = f' {{\n title: "{title}",\n href: "/cookbook/recipes/{r["slug"]}"' + if description: + item += f',\n description: "{description}"' + if tags: + # Convert tags list to structured format + structured_tags = convert_tags_to_structured(tags) + tags_parts = [] + if "language" in structured_tags: + tags_parts.append(f'language: "{structured_tags["language"]}"') + if "sdk" in structured_tags: + tags_parts.append(f'sdk: "{structured_tags["sdk"]}"') + if "topic" in structured_tags: + tags_parts.append(f'topic: "{structured_tags["topic"]}"') + if tags_parts: + item += f",\n tags: {{ {', '.join(tags_parts)} }}" + item += "\n }" + recipe_items.append(item) + recipes_json = ",\n".join(recipe_items) # Build app items for the carousel app_items = [] for a in apps: title = a["title"].replace('"', '\\"') - app_items.append(f' {{ title: "{title}", href: "/cookbook/applications/{a["slug"]}" }}') + description = a.get("description", "") + if description: + description = clean_description(description).replace('"', '\\"') + tags = a.get("tags", []) + + item = f' {{\n title: "{title}",\n href: "/cookbook/applications/{a["slug"]}"' + if description: + item += f',\n description: "{description}"' + if tags: + # Convert tags list to structured format + structured_tags = convert_tags_to_structured(tags) + tags_parts = [] + if "language" in structured_tags: + tags_parts.append(f'language: "{structured_tags["language"]}"') + if "sdk" in structured_tags: + tags_parts.append(f'sdk: "{structured_tags["sdk"]}"') + if "topic" in structured_tags: + tags_parts.append(f'topic: "{structured_tags["topic"]}"') + if tags_parts: + item += f",\n tags: {{ {', '.join(tags_parts)} }}" + item += "\n }" + app_items.append(item) + apps_json = ",\n".join(app_items) content = f"""--- sidebar_position: 1 +hide_table_of_contents: true +pagination_next: null +pagination_prev: null +custom_edit_url: null +sidebar_class_name: hidden-sidebar --- import RecipeCarousel from '@site/src/components/RecipeCarousel'; +
+ # Cookbook -Practical patterns, recipes, and complete applications for building with Hindsight. +Learn how to build with Hindsight through practical examples: + +- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases +- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration + +
""" index_path = docs_dir / "index.mdx" @@ -457,6 +618,63 @@ Practical patterns, recipes, and complete applications for building with Hindsig print("Updated cookbook/index.mdx") +def extract_existing_entries(docs_dir: Path) -> tuple[list[dict], list[dict]]: + """Extract existing recipe and app entries before syncing. + + This allows us to preserve manually added entries that aren't in the cookbook repo. + Returns entries with their content stored in memory. + """ + existing_recipes = [] + existing_apps = [] + + recipes_dir = docs_dir / "recipes" + apps_dir = docs_dir / "applications" + + # Scan existing recipes + if recipes_dir.exists(): + for md_file in recipes_dir.glob("*.md"): + slug = md_file.stem + # Read file content + content = md_file.read_text() + # Try to extract title from first heading + title_match = re.search(r"^#\s+(.+)$", content, re.MULTILINE) + title = ( + title_match.group(1).strip() if title_match else " ".join(word.capitalize() for word in slug.split("-")) + ) + + existing_recipes.append( + { + "slug": slug, + "title": title, + "id": f"cookbook/recipes/{slug}", + "content": content, # Store content in memory + } + ) + + # Scan existing apps + if apps_dir.exists(): + for md_file in apps_dir.glob("*.md"): + slug = md_file.stem + # Read file content + content = md_file.read_text() + # Try to extract title from first heading + title_match = re.search(r"^#\s+(.+)$", content, re.MULTILINE) + title = ( + title_match.group(1).strip() if title_match else " ".join(word.capitalize() for word in slug.split("-")) + ) + + existing_apps.append( + { + "slug": slug, + "title": title, + "id": f"cookbook/applications/{slug}", + "content": content, # Store content in memory + } + ) + + return existing_recipes, existing_apps + + def main(): """Main entry point.""" print("Syncing hindsight-cookbook...\n") @@ -466,11 +684,16 @@ def main(): recipes_dir = docs_dir / "recipes" apps_dir = docs_dir / "applications" + # Extract existing entries before we delete anything + print("Scanning for existing manual entries...") + existing_recipes, existing_apps = extract_existing_entries(docs_dir) + print(f" Found {len(existing_recipes)} existing recipes, {len(existing_apps)} existing apps") + # Create temp directory and clone with tempfile.TemporaryDirectory() as tmpdir: cookbook_dir = Path(tmpdir) / "cookbook" - print(f"Cloning {COOKBOOK_REPO}...") + print(f"\nCloning {COOKBOOK_REPO}...") subprocess.run( ["git", "clone", "--depth", "1", COOKBOOK_REPO, str(cookbook_dir)], capture_output=True, @@ -494,12 +717,53 @@ def main(): print("\nProcessing applications...") apps = process_applications(cookbook_dir, apps_dir) - # Update sidebars.ts and index - if recipes or apps: - update_sidebars(recipes, apps, sidebars_file) - update_cookbook_index(recipes, apps, docs_dir) + # Restore manually added entries that aren't in the cookbook repo + print("\nRestoring manual entries...") + synced_recipe_slugs = {r["slug"] for r in recipes} + synced_app_slugs = {a["slug"] for a in apps} - print(f"\nDone! Generated {len(recipes)} recipes and {len(apps)} applications") + manual_recipes = [] + for entry in existing_recipes: + if entry["slug"] not in synced_recipe_slugs: + # This was a manual entry - restore it + dest_path = recipes_dir / f"{entry['slug']}.md" + dest_path.write_text(entry["content"]) + manual_recipes.append( + { + "slug": entry["slug"], + "title": entry["title"], + "id": entry["id"], + } + ) + print(f" Restored recipe: {entry['slug']}") + + manual_apps = [] + for entry in existing_apps: + if entry["slug"] not in synced_app_slugs: + # This was a manual entry - restore it + dest_path = apps_dir / f"{entry['slug']}.md" + dest_path.write_text(entry["content"]) + manual_apps.append( + { + "slug": entry["slug"], + "title": entry["title"], + "id": entry["id"], + } + ) + print(f" Restored app: {entry['slug']}") + + # Combine synced and manual entries + all_recipes = recipes + manual_recipes + all_apps = apps + manual_apps + + # Update sidebars.ts and index + if all_recipes or all_apps: + update_sidebars(all_recipes, all_apps, sidebars_file) + update_cookbook_index(all_recipes, all_apps, docs_dir) + + print( + f"\nDone! Generated {len(recipes)} recipes ({len(manual_recipes)} manual) and {len(apps)} apps ({len(manual_apps)} manual)" + ) if __name__ == "__main__": diff --git a/hindsight-docs/docs/cookbook/applications/taste-ai.md b/hindsight-docs/docs/cookbook/applications/taste-ai.md new file mode 100644 index 00000000..f5c9888d --- /dev/null +++ b/hindsight-docs/docs/cookbook/applications/taste-ai.md @@ -0,0 +1,122 @@ +--- +sidebar_position: 8 +--- + +# Hindsight AI SDK - Personal Chef + + +:::info Complete Application +This is a complete, runnable application demonstrating Hindsight integration. +[**View source on GitHub →**](https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/taste-ai) +::: + + +A personal food assistant demonstrating three key Hindsight integrations using the [Vercel AI SDK v6](https://sdk.vercel.ai/docs). + +## Architecture: Single Bank with User Tags + +This demo uses a **single Hindsight bank** (`taste-ai`) for all users, with each user's data tagged using `user:${username}`. + +```typescript +// All users share the same bank +const BANK_ID = 'taste-ai'; + +// Each memory is tagged with the user +await hindsightTools.retain.execute({ + bankId: BANK_ID, + content: userData, + tags: [`user:${username}`], +}); +``` + +This architecture enables: +- **Per-user queries**: Filter by `user:alice` to get personalized results +- **Aggregated insights**: Query across all users to find popular recipes or common dietary patterns +- **Simplified management**: One bank to maintain instead of per-user banks + +## Three Hindsight Integrations + +### 1. Meal Suggestions with Memory Recall & Reflection + +Uses `recall` and `reflect` tools with AI SDK's agent-based approach to gather personalized context. + +```typescript +const contextResult = await generateText({ + model: llmModel, + tools: { + recall: hindsightTools.recall, + reflect: hindsightTools.reflect, + }, + toolChoice: 'auto', + prompt: `You are gathering context for personalized ${mealType} recipe suggestions. + +Use the recall tool to search for the user's food preferences, dislikes, and recent meals. +Then use the reflect tool to analyze their dietary patterns and restrictions. + +After gathering context, summarize their preferences and recent eating patterns.`, +}); +``` + +The AI agent autonomously: +- Searches memory for cuisine preferences and dietary restrictions +- Analyzes recent protein consumption for variety +- Identifies foods to avoid + +### 2. Goal Progress Tracking with Mental Models + +Uses mental models to automatically maintain updated insights about user progress. + +```typescript +// Create a mental model that auto-refreshes after new meals +await hindsightTools.createMentalModel.execute({ + bankId: BANK_ID, + mentalModelId: getMentalModelId(username, 'goals'), + name: `${username}'s Goal Progress`, + sourceQuery: `Analyze ${username}'s dietary goals and eating patterns. + Describe their progress towards their stated goals (weight loss, muscle gain, etc.).`, + tags: [`user:${username}`], + autoRefresh: true, // Refreshes automatically after consolidation +}); + +// Query the mental model for current insights +const result = await hindsightTools.queryMentalModel.execute({ + bankId: BANK_ID, + mentalModelId: mentalModelId, +}); +``` + +Mental models automatically: +- Track progress towards dietary goals +- Update after each new meal is logged +- Provide fresh insights without manual refresh + +### 3. Language Enforcement with Directives + +Uses directives to ensure all responses match user's language preference. + +```typescript +await hindsightClient.createDirective(BANK_ID, { + name: `${username}'s Language Preference`, + content: `Always respond in ${language}. All suggestions must be in ${language}.`, + priority: 100, + tags: [`user:${username}`, 'directive:language'], +}); +``` + +Directives are automatically injected when mental models generate insights, ensuring consistent language across all interactions. + +## Running the Demo + +```bash +npm install +npm run dev +``` + +**Requirements:** +- Hindsight server running at `http://localhost:8888` (or set `HINDSIGHT_URL`) +- Node.js 18+ + +## Learn More + +- [Hindsight AI SDK on npm](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) +- [AI SDK Documentation](https://sdk.vercel.ai/docs) diff --git a/hindsight-docs/docs/cookbook/index.mdx b/hindsight-docs/docs/cookbook/index.mdx index 4d6d367e..cb22bd4d 100644 --- a/hindsight-docs/docs/cookbook/index.mdx +++ b/hindsight-docs/docs/cookbook/index.mdx @@ -1,39 +1,147 @@ --- sidebar_position: 1 +hide_table_of_contents: true +pagination_next: null +pagination_prev: null +custom_edit_url: null +sidebar_class_name: hidden-sidebar --- import RecipeCarousel from '@site/src/components/RecipeCarousel'; +
+ # Cookbook -Practical patterns, recipes, and complete applications for building with Hindsight. +Learn how to build with Hindsight through practical examples: + +- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases +- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration + +
diff --git a/hindsight-docs/docs/sdks/integrations/ai-sdk.md b/hindsight-docs/docs/sdks/integrations/ai-sdk.md new file mode 100644 index 00000000..c538fc24 --- /dev/null +++ b/hindsight-docs/docs/sdks/integrations/ai-sdk.md @@ -0,0 +1,366 @@ +--- +sidebar_position: 4 +--- + +# Vercel AI SDK + +Official Hindsight integration for the [Vercel AI SDK](https://ai-sdk.dev). + +## Features + +- **7 Memory Tools**: Core memory operations (retain, recall, reflect), mental models (create, query), documents (get), and directives (create) +- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent` +- **Multi-User Support**: Dynamic bank IDs per tool call for multi-user/multi-tenant scenarios +- **Full Parameter Support**: Complete access to all Hindsight API parameters +- **Type-Safe**: Full TypeScript support with Zod schemas for validation + +## Installation + +```bash +npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod +``` + +## Quick Start + +### 1. Set up your Hindsight client + +```typescript +import { HindsightClient } from '@vectorize-io/hindsight-client'; + +const hindsightClient = new HindsightClient({ + apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000', +}); +``` + +### 2. Create Hindsight tools + +```typescript +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; + +const tools = createHindsightTools({ + client: hindsightClient, +}); +``` + +### 3. Use with AI SDK + +```typescript +import { generateText } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +const result = await generateText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + prompt: 'Remember that Alice loves hiking and prefers spicy food', +}); + +console.log(result.text); +``` + +## Memory Tools + +The integration provides seven tools that the AI model can use to manage memory: + +### `retain` - Store Information + +The model calls this tool to store information for future recall. + +**Parameters:** +- `bankId` (required): Memory bank ID (usually the user ID) +- `content` (required): Content to store +- `documentId` (optional): Document ID for grouping/upserting related memories +- `timestamp` (optional): ISO timestamp for when the memory occurred +- `context` (optional): Additional context about the memory +- `metadata` (optional): Key-value metadata for filtering + +**Example tool call:** +```typescript +{ + bankId: "user-123", + content: "Alice loves hiking and goes to Yosemite every summer", + context: "User preferences", + timestamp: "2024-01-15T10:30:00Z" +} +``` + +**Returns:** +```typescript +{ + success: true, + itemsCount: 1 +} +``` + +### `recall` - Search Memories + +The model calls this tool to search for relevant information in memory. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `query` (required): What to search for +- `types` (optional): Filter by fact types (`['world', 'experience', 'opinion']`) +- `maxTokens` (optional): Maximum tokens to return (default: 4096) +- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'` +- `queryTimestamp` (optional): Query from a specific time (ISO format) +- `includeEntities` (optional): Include entity observations +- `includeChunks` (optional): Include raw document chunks + +**Example tool call:** +```typescript +{ + bankId: "user-123", + query: "What does Alice like to do outdoors?", + types: ["world", "experience"], + maxTokens: 2048, + budget: "mid" +} +``` + +**Returns:** +```typescript +{ + results: [ + { + id: "mem-123", + text: "Alice loves hiking", + type: "world", + entities: ["Alice"], + context: "User preferences", + occurred_start: "2024-01-15T10:30:00Z", + document_id: "doc-456", + metadata: { source: "chat" } + } + ], + entities: { + "Alice": { + canonical_name: "Alice", + mention_count: 15, + observations: [...] + } + } +} +``` + +### `reflect` - Synthesize Insights + +The model calls this tool to analyze memories and generate contextual insights. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `query` (required): Question to reflect on +- `context` (optional): Additional context for reflection +- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'` + +**Example tool call:** +```typescript +{ + bankId: "user-123", + query: "What outdoor activities does Alice enjoy?", + context: "Planning a weekend trip", + budget: "mid" +} +``` + +**Returns:** +```typescript +{ + text: "Alice is an avid hiker who particularly enjoys visiting Yosemite National Park during summer months. She has expressed strong preferences for mountain trails over beach activities.", + basedOn: [ + { + id: "mem-123", + text: "Alice loves hiking", + type: "world", + context: "User preferences", + occurred_start: "2024-01-15T10:30:00Z" + } + ] +} +``` + +### `createMentalModel` - Create Knowledge Consolidation + +The model calls this tool to create a mental model that automatically consolidates memories into structured knowledge. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `mentalModelId` (optional): Custom ID for the mental model (auto-generated if not provided) +- `name` (optional): Name for the mental model +- `sourceQuery` (optional): Query defining which memories to consolidate +- `tags` (optional): Tags for organizing mental models +- `maxTokens` (optional): Maximum tokens for the content +- `autoRefresh` (optional): Auto-refresh after new consolidations (default: false) + +**Example tool call:** +```typescript +{ + bankId: "user-123", + name: "User Preferences", + sourceQuery: "What are the user's preferences?", + tags: ["preferences"], + autoRefresh: true +} +``` + +**Returns:** +```typescript +{ + mentalModelId: "mm-456", + createdAt: "2024-01-15T10:30:00Z" +} +``` + +### `queryMentalModel` - Retrieve Consolidated Knowledge + +The model calls this tool to retrieve synthesized insights from an existing mental model. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `mentalModelId` (required): ID of the mental model to query + +**Example tool call:** +```typescript +{ + bankId: "user-123", + mentalModelId: "mm-456" +} +``` + +**Returns:** +```typescript +{ + content: "The user prefers outdoor activities, particularly hiking. They enjoy mountain trails and visit Yosemite regularly during summer.", + name: "User Preferences", + updatedAt: "2024-01-20T15:45:00Z" +} +``` + +### `getDocument` - Retrieve Stored Document + +The model calls this tool to retrieve a stored document by its ID. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `documentId` (required): ID of the document to retrieve + +**Example tool call:** +```typescript +{ + bankId: "user-123", + documentId: "doc-789" +} +``` + +**Returns:** +```typescript +{ + originalText: "User profile: Alice, Software Engineer, loves hiking...", + id: "doc-789", + createdAt: "2024-01-10T09:00:00Z", + updatedAt: "2024-01-15T14:30:00Z" +} +``` + +### `createDirective` - Create Behavioral Rule + +The model calls this tool to create a directive—a hard rule injected into prompts during reflect operations. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `name` (required): Human-readable name for the directive +- `content` (required): The directive text to inject +- `priority` (optional): Higher priority directives are injected first (default: 0) +- `isActive` (optional): Whether this directive is active (default: true) +- `tags` (optional): Tags for filtering (e.g., user-specific directives) + +**Example tool call:** +```typescript +{ + bankId: "user-123", + name: "Response Format", + content: "Always provide responses in bullet-point format", + priority: 10, + tags: ["formatting"] +} +``` + +**Returns:** +```typescript +{ + id: "dir-321", + name: "Response Format", + content: "Always provide responses in bullet-point format", + tags: ["formatting"], + createdAt: "2024-01-15T10:30:00Z" +} +``` + +## Usage Examples + +### Using with `generateText` + +```typescript +import { HindsightClient } from '@vectorize-io/hindsight-client'; +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; +import { generateText } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +const hindsightClient = new HindsightClient({ + apiUrl: 'http://localhost:8000', +}); + +const tools = createHindsightTools({ client: hindsightClient }); + +const result = await generateText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + system: `You are a helpful assistant with long-term memory. Use the recall tool to check for relevant memories before responding.`, + prompt: 'Remember that Alice loves hiking and prefers spicy food', +}); + +console.log(result.text); +``` + +### Using with `streamText` + +```typescript +import { streamText } from 'ai'; + +const result = streamText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + system: `You have persistent memory. Use retain to store important information and recall to retrieve it.`, + prompt: 'What do you know about Alice?', +}); + +for await (const chunk of result.textStream) { + process.stdout.write(chunk); +} +``` + +### Using with `ToolLoopAgent` + +```typescript +import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai'; + +const agent = new ToolLoopAgent({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + instructions: `You are a personal assistant with long-term memory. Always check recall before responding and use retain to store important information.`, + stopWhen: stepCountIs(10), +}); + +const result = await agent.generate({ + prompt: 'What did I say I wanted to work on this week?', +}); +``` + +### Multi-User Support + +```typescript +const result = await generateText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + system: `You are a helpful assistant. The user's ID is: ${userId}. Always pass this as the bankId parameter to memory tools.`, + prompt: 'Remember that I prefer dark mode', +}); +``` diff --git a/hindsight-docs/sidebars.ts b/hindsight-docs/sidebars.ts index b6964bbd..acb43598 100644 --- a/hindsight-docs/sidebars.ts +++ b/hindsight-docs/sidebars.ts @@ -173,7 +173,7 @@ const sidebars: SidebarsConfig = { { type: 'doc', id: 'sdks/embed', - label: 'Embedded SDK', + label: 'Embedded Python', }, ], }, @@ -197,6 +197,11 @@ const sidebars: SidebarsConfig = { id: 'sdks/integrations/openclaw', label: 'OpenClaw', }, + { + type: 'doc', + id: 'sdks/integrations/ai-sdk', + label: 'Vercel AI SDK', + }, { type: 'doc', id: 'sdks/integrations/skills', @@ -209,111 +214,7 @@ const sidebars: SidebarsConfig = { { type: 'doc', id: 'cookbook/index', - label: 'Overview', - }, - { - type: 'category', - label: 'Recipes', - collapsible: false, - items: [ - { - type: 'doc', - id: 'cookbook/recipes/quickstart', - label: 'Hindsight Quickstart', - }, - { - type: 'doc', - id: 'cookbook/recipes/per-user-memory', - label: 'Per-User Memory', - }, - { - type: 'doc', - id: 'cookbook/recipes/support-agent-shared-knowledge', - label: 'Support Agent with Shared Knowledge', - }, - { - type: 'doc', - id: 'cookbook/recipes/litellm-memory-demo', - label: 'Memory with LiteLLM', - }, - { - type: 'doc', - id: 'cookbook/recipes/tool-learning-demo', - label: 'Routing Tool Learning', - }, - { - type: 'doc', - id: 'cookbook/recipes/fitness_tracker', - label: 'Fitness Coach with Hindsight Memory', - }, - { - type: 'doc', - id: 'cookbook/recipes/healthcare_assistant', - label: 'Healthcare Assistant with Hindsight Memory', - }, - { - type: 'doc', - id: 'cookbook/recipes/movie_recommendation', - label: 'Movie Recommendation Assistant with Hindsight Memory', - }, - { - type: 'doc', - id: 'cookbook/recipes/personal_assistant', - label: 'Personal AI Assistant with Hindsight Memory', - }, - { - type: 'doc', - id: 'cookbook/recipes/personalized_search', - label: 'Personalized Search Agent with Hindsight Memory', - }, - { - type: 'doc', - id: 'cookbook/recipes/study_buddy', - label: 'Study Buddy with Hindsight Memory', - } - ], - }, - { - type: 'category', - label: 'Applications', - collapsible: false, - items: [ - { - type: 'doc', - id: 'cookbook/applications/chat-memory', - label: 'Chat Memory App', - }, - { - type: 'doc', - id: 'cookbook/applications/deliveryman-demo', - label: 'Deliveryman Demo', - }, - { - type: 'doc', - id: 'cookbook/applications/hindsight-litellm-demo', - label: 'Memory Approaches Comparison Demo', - }, - { - type: 'doc', - id: 'cookbook/applications/hindsight-tool-learning-demo', - label: 'Tool Learning Demo', - }, - { - type: 'doc', - id: 'cookbook/applications/openai-fitness-coach', - label: 'OpenAI Agent + Hindsight Memory Integration', - }, - { - type: 'doc', - id: 'cookbook/applications/sanity-blog-memory', - label: 'Sanity CMS Blog Memory', - }, - { - type: 'doc', - id: 'cookbook/applications/stancetracker', - label: 'Stance Tracker', - } - ], + label: 'Cookbook', }, ], }; diff --git a/hindsight-docs/src/components/RecipeCarousel.module.css b/hindsight-docs/src/components/RecipeCarousel.module.css index 59d4f951..24525250 100644 --- a/hindsight-docs/src/components/RecipeCarousel.module.css +++ b/hindsight-docs/src/components/RecipeCarousel.module.css @@ -1,61 +1,181 @@ .carouselSection { - margin: 2rem 0; + margin: 3rem 0; } .sectionTitle { - font-size: 1.5rem; - margin-bottom: 1rem; + font-size: 1.75rem; + margin-bottom: 1.5rem; font-weight: 600; + color: var(--ifm-font-color-base); } .carousel { - /* Grid layout instead of horizontal scroll */ + /* Grid layout */ } .carouselTrack { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 1rem; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 1.25rem; } .card { - padding: 1rem 1.25rem; - border-radius: 8px; - border: 1px solid var(--ifm-color-emphasis-300); - background: var(--ifm-background-surface-color); + padding: 1.5rem; + border-radius: 12px; + border: 2px solid var(--card-border, var(--ifm-color-emphasis-300)); text-decoration: none; color: inherit; display: flex; - align-items: center; + flex-direction: column; justify-content: space-between; - gap: 0.75rem; + gap: 1rem; transition: all 0.2s ease; + position: relative; + overflow: hidden; + min-height: 200px; } -.card:hover { - text-decoration: none; - border-color: var(--ifm-color-primary); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +/* Alternating style: Odd cards = white/solid, Even cards = colored gradient */ + +/* ODD CARDS - White/Solid background */ +.card:nth-child(odd) { + background: #ffffff; +} + +.card:nth-child(odd):hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +/* EVEN CARDS - Colored gradients (cycle through 4 colors) */ +.card:nth-child(4n+2) { + background: linear-gradient(135deg, rgba(0, 116, 217, 0.08) 0%, rgba(0, 146, 150, 0.08) 100%); +} + +.card:nth-child(4n+4) { + background: linear-gradient(135deg, rgba(99, 102, 241, 0.08) 0%, rgba(168, 85, 247, 0.08) 100%); +} + +.card:nth-child(4n+6) { + background: linear-gradient(135deg, rgba(16, 185, 129, 0.08) 0%, rgba(5, 150, 105, 0.08) 100%); +} + +.card:nth-child(4n+8) { + background: linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(217, 119, 6, 0.08) 100%); +} + +.card:nth-child(even):hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + transform: translateY(-2px); +} + +/* ============================================ + DARK MODE + ============================================ */ + +/* ODD CARDS - Dark solid background */ +[data-theme='dark'] .card:nth-child(odd) { + background: var(--ifm-background-surface-color); } [data-theme='dark'] .card { - background: var(--ifm-background-color); - border-color: var(--ifm-color-emphasis-400); + border-color: var(--card-border-dark, var(--ifm-color-emphasis-300)); } -[data-theme='dark'] .card:hover { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - border-color: var(--ifm-color-primary); +[data-theme='dark'] .card:nth-child(odd):hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6); +} + +/* EVEN CARDS - Colored gradients (more vibrant in dark mode) */ +[data-theme='dark'] .card:nth-child(4n+2) { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(20, 184, 166, 0.15) 100%); +} + +[data-theme='dark'] .card:nth-child(4n+4) { + background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(217, 70, 239, 0.15) 100%); +} + +[data-theme='dark'] .card:nth-child(4n+6) { + background: linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(132, 204, 22, 0.15) 100%); +} + +[data-theme='dark'] .card:nth-child(4n+8) { + background: linear-gradient(135deg, rgba(251, 146, 60, 0.15) 0%, rgba(239, 68, 68, 0.15) 100%); +} + +[data-theme='dark'] .card:nth-child(even):hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.8); +} + +.cardContent { + display: flex; + flex-direction: column; + gap: 0.65rem; + flex-grow: 1; +} + +.cardFooter { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: auto; } .cardTitle { - font-size: 0.95rem; - font-weight: 500; + font-size: 1.05rem; + font-weight: 600; color: var(--ifm-font-color-base); + line-height: 1.4; +} + +.cardDescription { + font-size: 0.9rem; + color: var(--ifm-color-emphasis-800); + margin: 0; + line-height: 1.6; +} + +[data-theme='dark'] .cardDescription { + color: var(--ifm-color-emphasis-700); +} + +.cardTags { + display: flex; + flex-wrap: nowrap; + gap: 0.75rem; + align-items: center; + flex: 1; +} + +.tag { + font-size: 0.72rem; + padding: 0.35rem 0.75rem; + border-radius: 6px; + background: var(--tag-bg); + color: var(--tag-text); + font-weight: 600; + border: 1px solid var(--tag-border); + white-space: nowrap; + flex-shrink: 0; +} + +[data-theme='dark'] .tag { + background: var(--tag-bg-dark); + color: var(--tag-text-dark); + border-color: var(--tag-border-dark); } .cardLink { color: var(--ifm-color-primary); - font-weight: 500; + font-weight: 600; flex-shrink: 0; + font-size: 1.25rem; + line-height: 1; + opacity: 0.7; + transition: opacity 0.2s ease; + margin-left: 1rem; +} + +.card:hover .cardLink { + opacity: 1; } diff --git a/hindsight-docs/src/components/RecipeCarousel.tsx b/hindsight-docs/src/components/RecipeCarousel.tsx index f31161cf..59ab7307 100644 --- a/hindsight-docs/src/components/RecipeCarousel.tsx +++ b/hindsight-docs/src/components/RecipeCarousel.tsx @@ -5,6 +5,11 @@ import styles from './RecipeCarousel.module.css'; export interface RecipeCard { title: string; href: string; + tags?: { + sdk?: string; // Package name: "hindsight-python", "hindsight-nodejs", "litellm-python", "ai-sdk", etc. + topic?: string; // "Learning", "Quick Start", "Recommendation", "Chat" + }; + description?: string; } interface RecipeCarouselProps { @@ -12,18 +17,148 @@ interface RecipeCarouselProps { items: RecipeCard[]; } +// Language icons using inline SVG data URIs +const LANGUAGE_ICONS: Record = { + Python: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%233776ab' d='M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z'/%3E%3C/svg%3E", + 'Node.js': "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23339933' d='M11.998 0c-.27 0-.54.07-.772.202L2.428 5.05C1.983 5.321 1.7 5.802 1.7 6.32v11.36c0 .518.283 1 .728 1.27l2.375 1.371c.64.321 1.094.32 1.468.32 1.203 0 1.89-.73 1.89-1.996V7.362c0-.146-.117-.264-.262-.264H7.11c-.146 0-.263.118-.263.264v11.283c0 .876-.906 1.753-2.38 1.01L2.103 18.28c-.046-.026-.073-.08-.073-.132V6.754c0-.051.027-.106.073-.132l8.798-5.08c.044-.026.102-.026.145 0l8.798 5.08c.046.026.074.081.074.132v11.394c0 .051-.028.106-.074.132l-8.798 5.08c-.043.026-.101.026-.144 0l-2.248-1.336c-.064-.037-.144-.04-.21-.011-.55.307-.658.373-1.177.45-.12.019-.301.06.073.276l2.93 1.738c.23.133.49.202.772.202s.542-.069.772-.202l8.798-5.08c.476-.27.772-.772.772-1.27V6.32c0-.518-.296-.999-.772-1.27L12.77.202C12.538.07 12.268 0 11.998 0zm2.657 6.343c-2.432 0-2.945.953-2.945 2.146 0 .145.117.263.263.263h.788c.131 0 .24-.095.261-.221.177-.718.708-1.08 1.633-1.08.738 0 1.177.168 1.177.803 0 .325-.128.567-.678.73l-1.69.419c-.899.223-1.47.756-1.47 1.636 0 1.076.905 1.715 2.423 1.715 1.704 0 2.55-.593 2.656-1.866.006-.073-.018-.144-.066-.197-.047-.053-.114-.083-.186-.083h-.791c-.123 0-.23.089-.258.207-.286.644-.98.849-1.817.849-.65 0-1.16-.207-1.16-.725 0-.325.144-.424.903-.609l1.476-.367c.898-.223 1.462-.72 1.462-1.613 0-1.12-.937-1.787-2.574-1.787z'/%3E%3C/svg%3E", + TypeScript: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%233178c6' d='M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111.472.074.914.187 1.323.34v2.458c-.444-.223-.935-.39-1.473-.501-.539-.111-1.09-.167-1.655-.167-.562 0-1.011.062-1.349.187-.338.124-.507.335-.507.632 0 .234.095.42.285.558.19.138.503.275.94.411l1.503.434c.915.262 1.577.609 1.984 1.04.408.432.612.998.612 1.699 0 .915-.35 1.638-1.05 2.168-.7.53-1.667.795-2.9.795-.591 0-1.178-.051-1.76-.153-.582-.102-1.13-.258-1.645-.468v-2.503c.544.287 1.09.507 1.637.66.546.153 1.084.23 1.613.23.609 0 1.071-.073 1.386-.219.315-.146.472-.369.472-.669 0-.262-.106-.471-.318-.628-.212-.157-.551-.306-1.017-.447l-1.42-.395c-.877-.234-1.515-.563-1.916-.985-.4-.422-.6-.98-.6-1.673 0-.857.348-1.545 1.044-2.063.696-.518 1.633-.777 2.811-.777zm-13.6 1.77H8.45l-.031 4.18c0 .754-.13 1.314-.39 1.68-.26.367-.65.55-1.168.55-.286 0-.56-.037-.822-.11-.262-.074-.506-.173-.733-.297v1.818c.319.111.665.187 1.038.228.373.04.736.06 1.089.06.924 0 1.623-.247 2.097-.74.474-.494.711-1.254.711-2.28V11.52z'/%3E%3C/svg%3E", +}; + +// Get language icon based on package name +function getPackageIcon(packageName: string): string | undefined { + // If it starts with @vectorize-io, it's Node.js + if (packageName.startsWith('@vectorize-io')) { + return LANGUAGE_ICONS['Node.js']; + } + // Otherwise assume Python + return LANGUAGE_ICONS.Python; +} + +// Generate color scheme from tag text using hash +function getTagColor(tag: string): any { + // Hash function to get consistent color from string + let hash = 0; + for (let i = 0; i < tag.length; i++) { + hash = tag.charCodeAt(i) + ((hash << 5) - hash); + } + + // 12 vibrant color palettes with better contrast + const palettes = [ + { h: 340, s: 75, l: 50 }, // Pink + { h: 291, s: 65, l: 45 }, // Purple + { h: 262, s: 55, l: 48 }, // Deep Purple + { h: 231, s: 50, l: 50 }, // Indigo + { h: 207, s: 80, l: 50 }, // Blue + { h: 199, s: 85, l: 45 }, // Light Blue + { h: 187, s: 70, l: 45 }, // Cyan + { h: 174, s: 70, l: 50 }, // Teal + { h: 142, s: 65, l: 45 }, // Green + { h: 88, s: 55, l: 48 }, // Light Green + { h: 38, s: 85, l: 50 }, // Orange + { h: 14, s: 85, l: 50 }, // Deep Orange + ]; + + const palette = palettes[Math.abs(hash) % palettes.length]; + const { h, s, l } = palette; + + return { + // Light mode: subtle background, darker text for contrast + bg: `hsla(${h}, ${s}%, ${l}%, 0.15)`, + text: `hsl(${h}, ${Math.min(s + 10, 90)}%, ${Math.max(l - 25, 25)}%)`, + border: `hsla(${h}, ${s}%, ${l}%, 0.35)`, + // Dark mode: more vibrant background, lighter text + bgDark: `hsla(${h}, ${Math.max(s - 10, 50)}%, ${l}%, 0.25)`, + textDark: `hsl(${h}, ${Math.max(s - 15, 40)}%, ${Math.min(l + 35, 85)}%)`, + borderDark: `hsla(${h}, ${s}%, ${l}%, 0.4)`, + }; +} + export default function RecipeCarousel({ title, items }: RecipeCarouselProps): React.ReactElement { + // Generate ID from title for anchor links + const sectionId = title.toLowerCase().replace(/\s+/g, '-'); + return ( -
+

{title}

- {items.map((item, index) => ( - - {item.title} - - - ))} + {items.map((item, index) => { + // Get topic color for card border + const topicColors = item.tags?.topic ? getTagColor(item.tags.topic) : null; + + return ( + +
+ {item.title} + {item.description && ( +

{item.description}

+ )} +
+
+ {item.tags && ( +
+ {item.tags.sdk && (() => { + const colors = getTagColor(item.tags.sdk); + const icon = getPackageIcon(item.tags.sdk); + return ( + + {icon && ( + + )} + {item.tags.sdk} + + ); + })()} + {item.tags.topic && (() => { + const colors = getTagColor(item.tags.topic); + return ( + + {item.tags.topic} + + ); + })()} +
+ )} + +
+ + ); + })}
diff --git a/hindsight-docs/src/css/custom.css b/hindsight-docs/src/css/custom.css index 1ee17bd4..62072095 100644 --- a/hindsight-docs/src/css/custom.css +++ b/hindsight-docs/src/css/custom.css @@ -429,6 +429,84 @@ color: var(--ifm-color-emphasis-500); } +/* Client sidebar icons */ +a.menu__link[href*="/sdks/python"]::before, +a.menu__link[href*="/sdks/nodejs"]::before, +a.menu__link[href*="/sdks/cli"]::before, +a.menu__link[href*="/sdks/embed"]::before { + display: inline-block; + width: 18px; + height: 18px; + margin-right: 8px; + vertical-align: middle; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + content: ''; +} + +/* Python logo */ +a.menu__link[href*="/sdks/python"]::before { + background-image: url('/img/icons/python.svg'); +} + +/* Node.js logo */ +a.menu__link[href*="/sdks/nodejs"]::before { + background-image: url('/img/icons/nodejs.svg'); +} + +/* CLI - terminal icon */ +a.menu__link[href*="/sdks/cli"]::before { + background-image: url('/img/icons/terminal.svg'); +} + +/* Embedded SDK - package icon */ +a.menu__link[href*="/sdks/embed"]::before { + background-image: url('/img/icons/package.svg'); +} + +/* Integration sidebar icons */ +a.menu__link[href*="/sdks/integrations/local-mcp"]::before, +a.menu__link[href*="/sdks/integrations/litellm"]::before, +a.menu__link[href*="/sdks/integrations/openclaw"]::before, +a.menu__link[href*="/sdks/integrations/ai-sdk"]::before, +a.menu__link[href*="/sdks/integrations/skills"]::before { + display: inline-block; + width: 18px; + height: 18px; + margin-right: 8px; + vertical-align: middle; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + content: ''; +} + +/* Local MCP Server */ +a.menu__link[href*="/sdks/integrations/local-mcp"]::before { + background-image: url('/img/icons/mcp.png'); +} + +/* LiteLLM */ +a.menu__link[href*="/sdks/integrations/litellm"]::before { + background-image: url('/img/icons/litellm.png'); +} + +/* OpenClaw */ +a.menu__link[href*="/sdks/integrations/openclaw"]::before { + background-image: url('/img/icons/openclaw.png'); +} + +/* Vercel AI SDK */ +a.menu__link[href*="/sdks/integrations/ai-sdk"]::before { + background-image: url('/img/icons/vercel.png'); +} + +/* Skills */ +a.menu__link[href*="/sdks/integrations/skills"]::before { + background-image: url('/img/icons/skills.png'); +} + /* Code blocks */ pre, pre code, @@ -1306,3 +1384,75 @@ ul[class*="suggestion"] { } } + +/* ============================================ + Cookbook: Hide sidebar for OpenAI-style layout + ============================================ */ + +/* Hide sidebar completely on cookbook pages - use multiple selectors for reliability */ +[class*="docPage"] aside[class*="docSidebarContainer"], +aside[class*="docSidebarContainer"]:has(+ * .cookbook-page), +body:has(.cookbook-page) aside[class*="docSidebarContainer"], +.hidden-sidebar aside, +article[id="cookbook-index"] ~ aside, +div:has(> article[id="cookbook-index"]) aside { + display: none !important; + width: 0 !important; + min-width: 0 !important; +} + +/* Make main wrapper full width */ +body:has(.cookbook-page) .main-wrapper, +.hidden-sidebar ~ * .main-wrapper, +div:has(> article[id="cookbook-index"]) { + max-width: 100% !important; +} + +/* Make doc page container full width */ +body:has(.cookbook-page) [class*="docMainContainer"], +.hidden-sidebar [class*="docMainContainer"], +div:has(> .cookbook-page) > div { + max-width: 100% !important; +} + +/* Make the content column full width */ +body:has(.cookbook-page) [class*="docItemCol"], +.hidden-sidebar [class*="docItemCol"], +.cookbook-page ~ * [class*="col"] { + max-width: 100% !important; + flex: 1 1 100% !important; +} + +/* Container adjustments */ +.cookbook-page .container, +body:has(.cookbook-page) .container { + max-width: 1400px !important; + padding-left: 2rem !important; + padding-right: 2rem !important; +} + +/* Responsive adjustments */ +@media (max-width: 996px) { + .cookbook-page .container { + padding-left: 1rem !important; + padding-right: 1rem !important; + } +} + +/* Additional fallback selectors for hiding cookbook sidebar */ +[data-route="/cookbook"] aside, +[data-route="/cookbook/"] aside, +div[class*="docPage"]:has(article[id*="cookbook"]) > aside:first-child { + display: none !important; +} + +/* Force full width on cookbook route */ +[data-route="/cookbook"] div[class*="docRoot"], +[data-route="/cookbook/"] div[class*="docRoot"] { + grid-template-columns: 0 auto !important; +} + +[data-route="/cookbook"] main, +[data-route="/cookbook/"] main { + max-width: 100% !important; +} diff --git a/hindsight-docs/src/theme/DocPage/Layout/Main/index.tsx b/hindsight-docs/src/theme/DocPage/Layout/Main/index.tsx new file mode 100644 index 00000000..04148589 --- /dev/null +++ b/hindsight-docs/src/theme/DocPage/Layout/Main/index.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import Main from '@theme-original/DocPage/Layout/Main'; +import type MainType from '@theme/DocPage/Layout/Main'; +import type {WrapperProps} from '@docusaurus/types'; +import {useLocation} from '@docusaurus/router'; + +type Props = WrapperProps; + +export default function MainWrapper(props: Props): JSX.Element { + const location = useLocation(); + const isCookbook = location.pathname.includes('/cookbook'); + + return ( +
+
+
+ ); +} diff --git a/hindsight-docs/src/theme/DocPage/Layout/Sidebar/index.tsx b/hindsight-docs/src/theme/DocPage/Layout/Sidebar/index.tsx new file mode 100644 index 00000000..688797aa --- /dev/null +++ b/hindsight-docs/src/theme/DocPage/Layout/Sidebar/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import Sidebar from '@theme-original/DocPage/Layout/Sidebar'; +import type SidebarType from '@theme/DocPage/Layout/Sidebar'; +import type {WrapperProps} from '@docusaurus/types'; +import {useLocation} from '@docusaurus/router'; + +type Props = WrapperProps; + +export default function SidebarWrapper(props: Props): JSX.Element | null { + const location = useLocation(); + const isCookbook = location.pathname.includes('/cookbook'); + + // Don't render sidebar for cookbook pages + if (isCookbook) { + return null; + } + + return ; +} diff --git a/hindsight-docs/static/img/icons/README.md b/hindsight-docs/static/img/icons/README.md new file mode 100644 index 00000000..96ddc0bb --- /dev/null +++ b/hindsight-docs/static/img/icons/README.md @@ -0,0 +1,47 @@ +# Sidebar Icons + +This directory contains SVG icons for the sidebar navigation items. + +## Required Icons + +### Client Icons +- `python.svg` - Python logo (download from https://www.python.org/community/logos/) +- `nodejs.svg` - Node.js logo (download from https://nodejs.org/en/about/branding) +- `terminal.svg` - Terminal/CLI icon +- `package.svg` - Package/box icon for Embedded SDK + +### Integration Icons +- `mcp.svg` - MCP Server icon +- `litellm.svg` - LiteLLM logo (download from https://github.com/BerriAI/litellm) +- `openclaw.svg` - OpenClaw logo +- `vercel.svg` - Vercel triangle logo (download from https://vercel.com/design/brands) +- `skills.svg` - Skills/star icon + +## Specifications + +- **Format**: SVG (preferred) or PNG +- **Size**: 16x16px or larger (will be scaled to 14x14px) +- **Style**: Monochrome or simple colors work best +- **Color**: Icons should work on both light and dark backgrounds + +## Alternative: Using Remote URLs + +Instead of local files, you can use remote URLs directly in the CSS: + +```css +a.menu__link[href*="/sdks/python"]::before { + background-image: url('https://cdn.jsdelivr.net/npm/simple-icons@v10/icons/python.svg'); +} +``` + +Popular icon CDNs: +- Simple Icons: https://simpleicons.org/ +- cdnjs: https://cdnjs.com/ +- jsDelivr: https://www.jsdelivr.com/ + +## Creating Icons + +If you need to create custom icons, use tools like: +- Figma (https://figma.com) +- Inkscape (https://inkscape.org) +- SVGOMG for optimization (https://jakearchibald.github.io/svgomg/) diff --git a/hindsight-docs/static/img/icons/litellm.png b/hindsight-docs/static/img/icons/litellm.png new file mode 100644 index 00000000..a99b3e97 Binary files /dev/null and b/hindsight-docs/static/img/icons/litellm.png differ diff --git a/hindsight-docs/static/img/icons/mcp.png b/hindsight-docs/static/img/icons/mcp.png new file mode 100644 index 00000000..d920e50e Binary files /dev/null and b/hindsight-docs/static/img/icons/mcp.png differ diff --git a/hindsight-docs/static/img/icons/nodejs.svg b/hindsight-docs/static/img/icons/nodejs.svg new file mode 100644 index 00000000..41d044ac --- /dev/null +++ b/hindsight-docs/static/img/icons/nodejs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hindsight-docs/static/img/icons/openclaw.png b/hindsight-docs/static/img/icons/openclaw.png new file mode 100644 index 00000000..da995cd4 Binary files /dev/null and b/hindsight-docs/static/img/icons/openclaw.png differ diff --git a/hindsight-docs/static/img/icons/package.svg b/hindsight-docs/static/img/icons/package.svg new file mode 100644 index 00000000..05155f37 --- /dev/null +++ b/hindsight-docs/static/img/icons/package.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hindsight-docs/static/img/icons/python.svg b/hindsight-docs/static/img/icons/python.svg new file mode 100644 index 00000000..05155f37 --- /dev/null +++ b/hindsight-docs/static/img/icons/python.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hindsight-docs/static/img/icons/skills.png b/hindsight-docs/static/img/icons/skills.png new file mode 100644 index 00000000..59c53037 Binary files /dev/null and b/hindsight-docs/static/img/icons/skills.png differ diff --git a/hindsight-docs/static/img/icons/terminal.svg b/hindsight-docs/static/img/icons/terminal.svg new file mode 100644 index 00000000..905b181d --- /dev/null +++ b/hindsight-docs/static/img/icons/terminal.svg @@ -0,0 +1,44 @@ + + + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hindsight-docs/static/img/icons/vercel.png b/hindsight-docs/static/img/icons/vercel.png new file mode 100644 index 00000000..84361215 Binary files /dev/null and b/hindsight-docs/static/img/icons/vercel.png differ diff --git a/hindsight-docs/versioned_docs/version-0.4/cookbook/applications/taste-ai.md b/hindsight-docs/versioned_docs/version-0.4/cookbook/applications/taste-ai.md new file mode 100644 index 00000000..f5c9888d --- /dev/null +++ b/hindsight-docs/versioned_docs/version-0.4/cookbook/applications/taste-ai.md @@ -0,0 +1,122 @@ +--- +sidebar_position: 8 +--- + +# Hindsight AI SDK - Personal Chef + + +:::info Complete Application +This is a complete, runnable application demonstrating Hindsight integration. +[**View source on GitHub →**](https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/taste-ai) +::: + + +A personal food assistant demonstrating three key Hindsight integrations using the [Vercel AI SDK v6](https://sdk.vercel.ai/docs). + +## Architecture: Single Bank with User Tags + +This demo uses a **single Hindsight bank** (`taste-ai`) for all users, with each user's data tagged using `user:${username}`. + +```typescript +// All users share the same bank +const BANK_ID = 'taste-ai'; + +// Each memory is tagged with the user +await hindsightTools.retain.execute({ + bankId: BANK_ID, + content: userData, + tags: [`user:${username}`], +}); +``` + +This architecture enables: +- **Per-user queries**: Filter by `user:alice` to get personalized results +- **Aggregated insights**: Query across all users to find popular recipes or common dietary patterns +- **Simplified management**: One bank to maintain instead of per-user banks + +## Three Hindsight Integrations + +### 1. Meal Suggestions with Memory Recall & Reflection + +Uses `recall` and `reflect` tools with AI SDK's agent-based approach to gather personalized context. + +```typescript +const contextResult = await generateText({ + model: llmModel, + tools: { + recall: hindsightTools.recall, + reflect: hindsightTools.reflect, + }, + toolChoice: 'auto', + prompt: `You are gathering context for personalized ${mealType} recipe suggestions. + +Use the recall tool to search for the user's food preferences, dislikes, and recent meals. +Then use the reflect tool to analyze their dietary patterns and restrictions. + +After gathering context, summarize their preferences and recent eating patterns.`, +}); +``` + +The AI agent autonomously: +- Searches memory for cuisine preferences and dietary restrictions +- Analyzes recent protein consumption for variety +- Identifies foods to avoid + +### 2. Goal Progress Tracking with Mental Models + +Uses mental models to automatically maintain updated insights about user progress. + +```typescript +// Create a mental model that auto-refreshes after new meals +await hindsightTools.createMentalModel.execute({ + bankId: BANK_ID, + mentalModelId: getMentalModelId(username, 'goals'), + name: `${username}'s Goal Progress`, + sourceQuery: `Analyze ${username}'s dietary goals and eating patterns. + Describe their progress towards their stated goals (weight loss, muscle gain, etc.).`, + tags: [`user:${username}`], + autoRefresh: true, // Refreshes automatically after consolidation +}); + +// Query the mental model for current insights +const result = await hindsightTools.queryMentalModel.execute({ + bankId: BANK_ID, + mentalModelId: mentalModelId, +}); +``` + +Mental models automatically: +- Track progress towards dietary goals +- Update after each new meal is logged +- Provide fresh insights without manual refresh + +### 3. Language Enforcement with Directives + +Uses directives to ensure all responses match user's language preference. + +```typescript +await hindsightClient.createDirective(BANK_ID, { + name: `${username}'s Language Preference`, + content: `Always respond in ${language}. All suggestions must be in ${language}.`, + priority: 100, + tags: [`user:${username}`, 'directive:language'], +}); +``` + +Directives are automatically injected when mental models generate insights, ensuring consistent language across all interactions. + +## Running the Demo + +```bash +npm install +npm run dev +``` + +**Requirements:** +- Hindsight server running at `http://localhost:8888` (or set `HINDSIGHT_URL`) +- Node.js 18+ + +## Learn More + +- [Hindsight AI SDK on npm](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) +- [AI SDK Documentation](https://sdk.vercel.ai/docs) diff --git a/hindsight-docs/versioned_docs/version-0.4/cookbook/index.mdx b/hindsight-docs/versioned_docs/version-0.4/cookbook/index.mdx index 4d6d367e..cb22bd4d 100644 --- a/hindsight-docs/versioned_docs/version-0.4/cookbook/index.mdx +++ b/hindsight-docs/versioned_docs/version-0.4/cookbook/index.mdx @@ -1,39 +1,147 @@ --- sidebar_position: 1 +hide_table_of_contents: true +pagination_next: null +pagination_prev: null +custom_edit_url: null +sidebar_class_name: hidden-sidebar --- import RecipeCarousel from '@site/src/components/RecipeCarousel'; +
+ # Cookbook -Practical patterns, recipes, and complete applications for building with Hindsight. +Learn how to build with Hindsight through practical examples: + +- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases +- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration + +
diff --git a/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/ai-sdk.md b/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/ai-sdk.md new file mode 100644 index 00000000..c538fc24 --- /dev/null +++ b/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/ai-sdk.md @@ -0,0 +1,366 @@ +--- +sidebar_position: 4 +--- + +# Vercel AI SDK + +Official Hindsight integration for the [Vercel AI SDK](https://ai-sdk.dev). + +## Features + +- **7 Memory Tools**: Core memory operations (retain, recall, reflect), mental models (create, query), documents (get), and directives (create) +- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent` +- **Multi-User Support**: Dynamic bank IDs per tool call for multi-user/multi-tenant scenarios +- **Full Parameter Support**: Complete access to all Hindsight API parameters +- **Type-Safe**: Full TypeScript support with Zod schemas for validation + +## Installation + +```bash +npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod +``` + +## Quick Start + +### 1. Set up your Hindsight client + +```typescript +import { HindsightClient } from '@vectorize-io/hindsight-client'; + +const hindsightClient = new HindsightClient({ + apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000', +}); +``` + +### 2. Create Hindsight tools + +```typescript +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; + +const tools = createHindsightTools({ + client: hindsightClient, +}); +``` + +### 3. Use with AI SDK + +```typescript +import { generateText } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +const result = await generateText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + prompt: 'Remember that Alice loves hiking and prefers spicy food', +}); + +console.log(result.text); +``` + +## Memory Tools + +The integration provides seven tools that the AI model can use to manage memory: + +### `retain` - Store Information + +The model calls this tool to store information for future recall. + +**Parameters:** +- `bankId` (required): Memory bank ID (usually the user ID) +- `content` (required): Content to store +- `documentId` (optional): Document ID for grouping/upserting related memories +- `timestamp` (optional): ISO timestamp for when the memory occurred +- `context` (optional): Additional context about the memory +- `metadata` (optional): Key-value metadata for filtering + +**Example tool call:** +```typescript +{ + bankId: "user-123", + content: "Alice loves hiking and goes to Yosemite every summer", + context: "User preferences", + timestamp: "2024-01-15T10:30:00Z" +} +``` + +**Returns:** +```typescript +{ + success: true, + itemsCount: 1 +} +``` + +### `recall` - Search Memories + +The model calls this tool to search for relevant information in memory. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `query` (required): What to search for +- `types` (optional): Filter by fact types (`['world', 'experience', 'opinion']`) +- `maxTokens` (optional): Maximum tokens to return (default: 4096) +- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'` +- `queryTimestamp` (optional): Query from a specific time (ISO format) +- `includeEntities` (optional): Include entity observations +- `includeChunks` (optional): Include raw document chunks + +**Example tool call:** +```typescript +{ + bankId: "user-123", + query: "What does Alice like to do outdoors?", + types: ["world", "experience"], + maxTokens: 2048, + budget: "mid" +} +``` + +**Returns:** +```typescript +{ + results: [ + { + id: "mem-123", + text: "Alice loves hiking", + type: "world", + entities: ["Alice"], + context: "User preferences", + occurred_start: "2024-01-15T10:30:00Z", + document_id: "doc-456", + metadata: { source: "chat" } + } + ], + entities: { + "Alice": { + canonical_name: "Alice", + mention_count: 15, + observations: [...] + } + } +} +``` + +### `reflect` - Synthesize Insights + +The model calls this tool to analyze memories and generate contextual insights. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `query` (required): Question to reflect on +- `context` (optional): Additional context for reflection +- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'` + +**Example tool call:** +```typescript +{ + bankId: "user-123", + query: "What outdoor activities does Alice enjoy?", + context: "Planning a weekend trip", + budget: "mid" +} +``` + +**Returns:** +```typescript +{ + text: "Alice is an avid hiker who particularly enjoys visiting Yosemite National Park during summer months. She has expressed strong preferences for mountain trails over beach activities.", + basedOn: [ + { + id: "mem-123", + text: "Alice loves hiking", + type: "world", + context: "User preferences", + occurred_start: "2024-01-15T10:30:00Z" + } + ] +} +``` + +### `createMentalModel` - Create Knowledge Consolidation + +The model calls this tool to create a mental model that automatically consolidates memories into structured knowledge. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `mentalModelId` (optional): Custom ID for the mental model (auto-generated if not provided) +- `name` (optional): Name for the mental model +- `sourceQuery` (optional): Query defining which memories to consolidate +- `tags` (optional): Tags for organizing mental models +- `maxTokens` (optional): Maximum tokens for the content +- `autoRefresh` (optional): Auto-refresh after new consolidations (default: false) + +**Example tool call:** +```typescript +{ + bankId: "user-123", + name: "User Preferences", + sourceQuery: "What are the user's preferences?", + tags: ["preferences"], + autoRefresh: true +} +``` + +**Returns:** +```typescript +{ + mentalModelId: "mm-456", + createdAt: "2024-01-15T10:30:00Z" +} +``` + +### `queryMentalModel` - Retrieve Consolidated Knowledge + +The model calls this tool to retrieve synthesized insights from an existing mental model. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `mentalModelId` (required): ID of the mental model to query + +**Example tool call:** +```typescript +{ + bankId: "user-123", + mentalModelId: "mm-456" +} +``` + +**Returns:** +```typescript +{ + content: "The user prefers outdoor activities, particularly hiking. They enjoy mountain trails and visit Yosemite regularly during summer.", + name: "User Preferences", + updatedAt: "2024-01-20T15:45:00Z" +} +``` + +### `getDocument` - Retrieve Stored Document + +The model calls this tool to retrieve a stored document by its ID. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `documentId` (required): ID of the document to retrieve + +**Example tool call:** +```typescript +{ + bankId: "user-123", + documentId: "doc-789" +} +``` + +**Returns:** +```typescript +{ + originalText: "User profile: Alice, Software Engineer, loves hiking...", + id: "doc-789", + createdAt: "2024-01-10T09:00:00Z", + updatedAt: "2024-01-15T14:30:00Z" +} +``` + +### `createDirective` - Create Behavioral Rule + +The model calls this tool to create a directive—a hard rule injected into prompts during reflect operations. + +**Parameters:** +- `bankId` (required): Memory bank ID +- `name` (required): Human-readable name for the directive +- `content` (required): The directive text to inject +- `priority` (optional): Higher priority directives are injected first (default: 0) +- `isActive` (optional): Whether this directive is active (default: true) +- `tags` (optional): Tags for filtering (e.g., user-specific directives) + +**Example tool call:** +```typescript +{ + bankId: "user-123", + name: "Response Format", + content: "Always provide responses in bullet-point format", + priority: 10, + tags: ["formatting"] +} +``` + +**Returns:** +```typescript +{ + id: "dir-321", + name: "Response Format", + content: "Always provide responses in bullet-point format", + tags: ["formatting"], + createdAt: "2024-01-15T10:30:00Z" +} +``` + +## Usage Examples + +### Using with `generateText` + +```typescript +import { HindsightClient } from '@vectorize-io/hindsight-client'; +import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; +import { generateText } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +const hindsightClient = new HindsightClient({ + apiUrl: 'http://localhost:8000', +}); + +const tools = createHindsightTools({ client: hindsightClient }); + +const result = await generateText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + system: `You are a helpful assistant with long-term memory. Use the recall tool to check for relevant memories before responding.`, + prompt: 'Remember that Alice loves hiking and prefers spicy food', +}); + +console.log(result.text); +``` + +### Using with `streamText` + +```typescript +import { streamText } from 'ai'; + +const result = streamText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + system: `You have persistent memory. Use retain to store important information and recall to retrieve it.`, + prompt: 'What do you know about Alice?', +}); + +for await (const chunk of result.textStream) { + process.stdout.write(chunk); +} +``` + +### Using with `ToolLoopAgent` + +```typescript +import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai'; + +const agent = new ToolLoopAgent({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + instructions: `You are a personal assistant with long-term memory. Always check recall before responding and use retain to store important information.`, + stopWhen: stepCountIs(10), +}); + +const result = await agent.generate({ + prompt: 'What did I say I wanted to work on this week?', +}); +``` + +### Multi-User Support + +```typescript +const result = await generateText({ + model: anthropic('claude-sonnet-4-20250514'), + tools, + system: `You are a helpful assistant. The user's ID is: ${userId}. Always pass this as the bankId parameter to memory tools.`, + prompt: 'Remember that I prefer dark mode', +}); +``` diff --git a/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json b/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json index eb6fea7f..99838774 100644 --- a/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json +++ b/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json @@ -171,7 +171,7 @@ { "type": "doc", "id": "sdks/embed", - "label": "Embedded SDK" + "label": "Embedded Python" } ] }, @@ -195,6 +195,11 @@ "id": "sdks/integrations/openclaw", "label": "OpenClaw" }, + { + "type": "doc", + "id": "sdks/integrations/ai-sdk", + "label": "Vercel AI SDK" + }, { "type": "doc", "id": "sdks/integrations/skills", @@ -207,111 +212,7 @@ { "type": "doc", "id": "cookbook/index", - "label": "Overview" - }, - { - "type": "category", - "label": "Recipes", - "collapsible": false, - "items": [ - { - "type": "doc", - "id": "cookbook/recipes/quickstart", - "label": "Hindsight Quickstart" - }, - { - "type": "doc", - "id": "cookbook/recipes/per-user-memory", - "label": "Per-User Memory" - }, - { - "type": "doc", - "id": "cookbook/recipes/support-agent-shared-knowledge", - "label": "Support Agent with Shared Knowledge" - }, - { - "type": "doc", - "id": "cookbook/recipes/litellm-memory-demo", - "label": "Memory with LiteLLM" - }, - { - "type": "doc", - "id": "cookbook/recipes/tool-learning-demo", - "label": "Routing Tool Learning" - }, - { - "type": "doc", - "id": "cookbook/recipes/fitness_tracker", - "label": "Fitness Coach with Hindsight Memory" - }, - { - "type": "doc", - "id": "cookbook/recipes/healthcare_assistant", - "label": "Healthcare Assistant with Hindsight Memory" - }, - { - "type": "doc", - "id": "cookbook/recipes/movie_recommendation", - "label": "Movie Recommendation Assistant with Hindsight Memory" - }, - { - "type": "doc", - "id": "cookbook/recipes/personal_assistant", - "label": "Personal AI Assistant with Hindsight Memory" - }, - { - "type": "doc", - "id": "cookbook/recipes/personalized_search", - "label": "Personalized Search Agent with Hindsight Memory" - }, - { - "type": "doc", - "id": "cookbook/recipes/study_buddy", - "label": "Study Buddy with Hindsight Memory" - } - ] - }, - { - "type": "category", - "label": "Applications", - "collapsible": false, - "items": [ - { - "type": "doc", - "id": "cookbook/applications/chat-memory", - "label": "Chat Memory App" - }, - { - "type": "doc", - "id": "cookbook/applications/deliveryman-demo", - "label": "Deliveryman Demo" - }, - { - "type": "doc", - "id": "cookbook/applications/hindsight-litellm-demo", - "label": "Memory Approaches Comparison Demo" - }, - { - "type": "doc", - "id": "cookbook/applications/hindsight-tool-learning-demo", - "label": "Tool Learning Demo" - }, - { - "type": "doc", - "id": "cookbook/applications/openai-fitness-coach", - "label": "OpenAI Agent + Hindsight Memory Integration" - }, - { - "type": "doc", - "id": "cookbook/applications/sanity-blog-memory", - "label": "Sanity CMS Blog Memory" - }, - { - "type": "doc", - "id": "cookbook/applications/stancetracker", - "label": "Stance Tracker" - } - ] + "label": "Cookbook" } ] } diff --git a/hindsight-integrations/ai-sdk/README.md b/hindsight-integrations/ai-sdk/README.md index 854b5598..32f9cec8 100644 --- a/hindsight-integrations/ai-sdk/README.md +++ b/hindsight-integrations/ai-sdk/README.md @@ -2,313 +2,61 @@ Give your AI agents persistent, human-like memory using [Hindsight](https://vectorize.io/hindsight) with the [Vercel AI SDK](https://ai-sdk.dev). -## Features - -- **Three Memory Operations**: `retain` (store), `recall` (retrieve), and `reflect` (reason over memories) -- **Multi-User Support**: Dynamic bank IDs per call for multi-user/multi-tenant scenarios -- **Full API Coverage**: Complete parameter support for all Hindsight operations -- **Type-Safe**: Full TypeScript support with Zod schemas for validation -- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent` - -## Installation - -```bash -npm install @vectorize-io/hindsight-ai-sdk ai zod -``` - -You'll also need a Hindsight client. Choose one: - -**Option A: TypeScript/JavaScript Client** -```bash -npm install @vectorize-io/hindsight-client -``` - -**Option B: Direct HTTP Client** (no additional dependencies) -```typescript -// See "HTTP Client Example" below -``` - ## Quick Start -### 1. Set up your Hindsight client +```bash +npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod +``` ```typescript import { HindsightClient } from '@vectorize-io/hindsight-client'; - -const hindsightClient = new HindsightClient({ - apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000', -}); -``` - -### 2. Create Hindsight tools - -```typescript import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; - -const tools = createHindsightTools({ - client: hindsightClient, -}); -``` - -### 3. Use with AI SDK - -```typescript import { generateText } from 'ai'; import { anthropic } from '@ai-sdk/anthropic'; +// 1. Initialize Hindsight client +const hindsightClient = new HindsightClient({ + apiUrl: 'http://localhost:8000', +}); + +// 2. Create memory tools +const tools = createHindsightTools({ client: hindsightClient }); + +// 3. Use with AI SDK const result = await generateText({ model: anthropic('claude-sonnet-4-20250514'), tools, + system: `You have long-term memory. Use: + - 'recall' to search past conversations + - 'retain' to remember important information + - 'reflect' to synthesize insights from memories`, prompt: 'Remember that Alice loves hiking and prefers spicy food', }); console.log(result.text); ``` -## Full Example: Memory-Enabled Chatbot +## Features -```typescript -import { HindsightClient } from '@vectorize-io/hindsight-client'; -import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; -import { streamText } from 'ai'; -import { anthropic } from '@ai-sdk/anthropic'; +✅ **Three Memory Tools**: `retain` (store), `recall` (retrieve), and `reflect` (reason over memories) +✅ **AI SDK 6 Native**: Works with `generateText`, `streamText`, and `ToolLoopAgent` +✅ **Multi-User Support**: Dynamic bank IDs per call for multi-user scenarios +✅ **Type-Safe**: Full TypeScript support with Zod schemas +✅ **Flexible Client**: Works with the official TypeScript client or custom HTTP clients -// Initialize Hindsight -const hindsightClient = new HindsightClient({ - apiUrl: 'http://localhost:8000', -}); +## Documentation -const tools = createHindsightTools({ client: hindsightClient }); +📖 **[Full Documentation](https://vectorize.io/hindsight/sdks/integrations/ai-sdk)** -// Chat with memory -const result = await streamText({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - system: `You are a helpful assistant with long-term memory. - -IMPORTANT: -- Before answering questions, use the 'recall' tool to check for relevant memories -- When users share important information, use the 'retain' tool to remember it -- For complex questions requiring synthesis, use the 'reflect' tool -- Always pass the user's ID as the bankId parameter - -Your memory persists across sessions!`, - prompt: 'Remember that I am Alice and I love hiking', -}); - -for await (const chunk of result.textStream) { - process.stdout.write(chunk); -} -``` - -## API Reference - -### `createHindsightTools(options)` - -Creates AI SDK tool definitions for Hindsight memory operations. - -**Parameters:** - -- `options.client`: `HindsightClient` - Hindsight client instance -- `options.retainDescription`: `string` (optional) - Custom description for the retain tool -- `options.recallDescription`: `string` (optional) - Custom description for the recall tool -- `options.reflectDescription`: `string` (optional) - Custom description for the reflect tool - -**Returns:** Object with three tools: `retain`, `recall`, and `reflect` - -### Tool: `retain` - -Store information in long-term memory. - -**Parameters:** -- `bankId`: `string` - Memory bank ID (usually the user ID) -- `content`: `string` - Content to store -- `documentId`: `string` (optional) - Document ID for grouping/upserting -- `timestamp`: `string` (optional) - ISO timestamp for when the memory occurred -- `context`: `string` (optional) - Additional context about the memory - -**Returns:** -```typescript -{ - success: boolean; - itemsCount: number; -} -``` - -### Tool: `recall` - -Search memory for relevant information. - -**Parameters:** -- `bankId`: `string` - Memory bank ID -- `query`: `string` - What to search for -- `types`: `string[]` (optional) - Filter by fact types -- `maxTokens`: `number` (optional) - Maximum tokens to return -- `budget`: `'low' | 'mid' | 'high'` (optional) - Processing budget -- `queryTimestamp`: `string` (optional) - Query from a specific time (ISO format) -- `includeEntities`: `boolean` (optional) - Include entity observations -- `includeChunks`: `boolean` (optional) - Include raw chunks - -**Returns:** -```typescript -{ - results: Array<{ - id: string; - text: string; - type?: string; - entities?: string[]; - context?: string; - occurred_start?: string; - occurred_end?: string; - mentioned_at?: string; - document_id?: string; - metadata?: Record; - chunk_id?: string; - }>; - entities?: Record; -} -``` - -### Tool: `reflect` - -Analyze memories to form insights and generate contextual answers. - -**Parameters:** -- `bankId`: `string` - Memory bank ID -- `query`: `string` - Question to reflect on -- `context`: `string` (optional) - Additional context for reflection -- `budget`: `'low' | 'mid' | 'high'` (optional) - Processing budget - -**Returns:** -```typescript -{ - text: string; - basedOn?: Array<{ - id?: string; - text: string; - type?: string; - context?: string; - occurred_start?: string; - occurred_end?: string; - }>; -} -``` - -## Advanced Usage - -### Custom Tool Descriptions - -Customize tool descriptions to guide model behavior: - -```typescript -const tools = createHindsightTools({ - client: hindsightClient, - retainDescription: 'Store user preferences and important facts. Always include context.', - recallDescription: 'Search past conversations. Use specific queries for best results.', - reflectDescription: 'Synthesize insights from memories. Use for complex questions.', -}); -``` - -### Multi-User Scenarios - -Each tool call accepts a `bankId` parameter, making it easy to support multiple users: - -```typescript -const result = await generateText({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - prompt: `User ID: ${userId}\n\nRemember that I prefer dark mode`, -}); -``` - -The model will automatically pass the user ID to the tools. - -### Using with ToolLoopAgent - -```typescript -import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai'; - -const agent = new ToolLoopAgent({ - model: anthropic('claude-sonnet-4-20250514'), - tools, - instructions: `You are a personal assistant with long-term memory. - - Always check memory before responding using the recall tool. - Store important user preferences with the retain tool. - Use the reflect tool to analyze patterns in the user's behavior.`, - stopWhen: stepCountIs(10), -}); - -const result = await agent.generate({ - prompt: 'What did I say I wanted to work on this week?', -}); -``` - -## HTTP Client Example - -If you prefer not to install the full Hindsight client, you can use a simple HTTP client: - -```typescript -import type { HindsightClient } from '@vectorize-io/hindsight-ai-sdk'; - -const httpClient: HindsightClient = { - async retain(bankId, content, options = {}) { - const response = await fetch(`${HINDSIGHT_URL}/v1/default/banks/${bankId}/memories/retain`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - content, - timestamp: options.timestamp, - context: options.context, - metadata: options.metadata, - document_id: options.documentId, - async: options.async, - }), - }); - return response.json(); - }, - - async recall(bankId, query, options = {}) { - const response = await fetch(`${HINDSIGHT_URL}/v1/default/banks/${bankId}/memories/recall`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query, - types: options.types, - max_tokens: options.maxTokens, - budget: options.budget, - trace: options.trace, - query_timestamp: options.queryTimestamp, - include_entities: options.includeEntities, - max_entity_tokens: options.maxEntityTokens, - include_chunks: options.includeChunks, - max_chunk_tokens: options.maxChunkTokens, - }), - }); - return response.json(); - }, - - async reflect(bankId, query, options = {}) { - const response = await fetch(`${HINDSIGHT_URL}/v1/default/banks/${bankId}/reflect`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query, - context: options.context, - budget: options.budget, - }), - }); - return response.json(); - }, -}; - -const tools = createHindsightTools({ client: httpClient }); -``` +The complete documentation includes: +- Detailed tool descriptions and parameters +- Advanced usage patterns (streaming, multi-user, ToolLoopAgent) +- HTTP client example (no dependencies) +- TypeScript types and API reference +- Best practices and system prompt examples ## Running Hindsight Locally -The easiest way to run Hindsight for development: - ```bash # Install and run with embedded mode (no setup required) uvx hindsight-embed@latest -p myapp daemon start @@ -316,41 +64,16 @@ uvx hindsight-embed@latest -p myapp daemon start # The API will be available at http://localhost:8000 ``` -For production deployments, see the [Hindsight Documentation](https://vectorize.io/hindsight). +## Examples -## TypeScript Types +Full examples are available in the [GitHub repository](https://github.com/vectorize-io/hindsight/tree/main/examples/ai-sdk). -All types are exported for your convenience: +## Support -```typescript -import type { - Budget, - HindsightClient, - HindsightTools, - HindsightToolsOptions, - RecallResult, - RecallResponse, - ReflectFact, - ReflectResponse, - RetainResponse, - EntityState, - ChunkData, -} from '@vectorize-io/hindsight-ai-sdk'; -``` - -## Documentation & Resources - -- [Hindsight Documentation](https://vectorize.io/hindsight) -- [Vercel AI SDK Documentation](https://ai-sdk.dev) -- [GitHub Repository](https://github.com/vectorize-io/hindsight) -- [Examples](https://github.com/vectorize-io/hindsight/tree/main/examples) +- [Documentation](https://vectorize.io/hindsight) +- [GitHub Issues](https://github.com/vectorize-io/hindsight/issues) +- Email: support@vectorize.io ## License MIT - -## Support - -For issues and questions: -- [GitHub Issues](https://github.com/vectorize-io/hindsight/issues) -- Email: support@vectorize.io diff --git a/hindsight-integrations/ai-sdk/src/tools/index.ts b/hindsight-integrations/ai-sdk/src/tools/index.ts index de9f2d73..47fd8d5b 100644 --- a/hindsight-integrations/ai-sdk/src/tools/index.ts +++ b/hindsight-integrations/ai-sdk/src/tools/index.ts @@ -233,11 +233,6 @@ export interface HindsightClient { } ): Promise; - getDirective( - bankId: string, - directiveId: string - ): Promise; - listDirectives( bankId: string, options?: { @@ -367,10 +362,6 @@ export function createHindsightTools({ tags: z.array(z.string()).optional().describe('Tags for filtering'), }); - const getDirectiveParams = z.object({ - bankId: z.string().describe('Memory bank ID (usually the user ID)'), - directiveId: z.string().describe('ID of the directive to retrieve'), - }); type RetainInput = z.infer; type RetainOutput = { success: boolean; itemsCount: number }; @@ -393,9 +384,6 @@ export function createHindsightTools({ type CreateDirectiveInput = z.infer; type CreateDirectiveOutput = { id: string; name: string; content: string; tags: string[]; createdAt: string }; - type GetDirectiveInput = z.infer; - type GetDirectiveOutput = { id: string; name: string; content: string; tags: string[]; isActive: boolean } | null; - return { retain: tool({ description: @@ -534,25 +522,6 @@ export function createHindsightTools({ }; }, }), - - getDirective: tool({ - description: - `Retrieve a directive by its ID. Returns the directive's content, tags, and active status.`, - inputSchema: getDirectiveParams, - execute: async (input) => { - const result = await client.getDirective(input.bankId, input.directiveId); - if (!result) { - return null; - } - return { - id: result.id, - name: result.name, - content: result.content, - tags: result.tags, - isActive: result.is_active, - }; - }, - }), }; }