doc: update cookbook (#479)

* doc: update cookbook

* fix(cookbook): preserve tag keys during sync, strip local .md links

- Fix extract_tags_from_readme/notebook to return dict[str,str] preserving
  sdk/topic keys instead of bare values, preventing topics like
  "Customer Service" from being misclassified as SDK
- Add strip_local_md_links() to remove relative .md references that
  would cause broken link errors in Docusaurus build

* ci: run test-doc-examples independently without waiting for test-rust-cli

Build the CLI directly in the job instead of downloading the artifact,
so test-doc-examples can start at the beginning in parallel with all other jobs.
This commit is contained in:
Nicolò Boschi 2026-03-03 18:46:59 +01:00 committed by GitHub
parent 5c3d3274d7
commit 3d87ef5cee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 915 additions and 150 deletions

View file

@ -1297,7 +1297,6 @@ jobs:
test-doc-examples: test-doc-examples:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test-rust-cli
env: env:
HINDSIGHT_API_LLM_PROVIDER: vertexai HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
@ -1315,14 +1314,23 @@ jobs:
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json) PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Download CLI artifact - name: Install Rust
uses: actions/download-artifact@v4 uses: dtolnay/rust-toolchain@stable
with:
name: hindsight-cli
path: /usr/local/bin
- name: Make CLI executable - name: Cache cargo
run: chmod +x /usr/local/bin/hindsight uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
hindsight-cli/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build CLI
working-directory: hindsight-cli
run: |
cargo build --release
cp target/release/hindsight /usr/local/bin/hindsight
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@v5

View file

@ -29,16 +29,9 @@ IGNORE_DIRS = {".git", "notebooks", "node_modules", "__pycache__", ".venv", "ven
def get_docs_dir() -> Path: def get_docs_dir() -> Path:
"""Find the hindsight-docs directory relative to this script.""" """Find the hindsight-docs src/pages/cookbook directory relative to this script."""
# Navigate from hindsight-dev to hindsight-docs
script_dir = Path(__file__).parent script_dir = Path(__file__).parent
docs_dir = script_dir.parent.parent / "hindsight-docs" / "docs" / "cookbook" return script_dir.parent.parent / "hindsight-docs" / "src" / "pages" / "cookbook"
return docs_dir
def get_sidebars_file() -> Path:
script_dir = Path(__file__).parent
return script_dir.parent.parent / "hindsight-docs" / "sidebars.ts"
def slugify(filename: str) -> str: def slugify(filename: str) -> str:
@ -82,30 +75,27 @@ def extract_description_from_notebook(notebook_path: Path) -> str | None:
return None return None
def extract_tags_from_notebook(notebook_path: Path) -> list[str]: def extract_tags_from_notebook(notebook_path: Path) -> dict[str, str]:
"""Extract tags from notebook metadata. """Extract tags from notebook metadata.
Supports both array format and structured object format. Supports both array format and structured object format.
Returns a dict with keys like 'sdk', 'topic', 'language'.
""" """
try: try:
content = json.loads(notebook_path.read_text()) content = json.loads(notebook_path.read_text())
metadata = content.get("metadata", {}) metadata = content.get("metadata", {})
tags = metadata.get("tags", []) tags = metadata.get("tags", [])
# Array format: ["Python", "Client"] # Object format already has the right structure
if isinstance(tags, list):
return tags
# Object format: { "language": "Python", "sdk": "Client", "topic": "Learning" }
if isinstance(tags, dict): if isinstance(tags, dict):
result = [] return {k: v for k, v in tags.items() if v}
for key in ["language", "sdk", "topic"]:
if key in tags and tags[key]: # Array format: fall back to heuristic conversion
result.append(tags[key]) if isinstance(tags, list):
return result return _infer_tags_from_list(tags)
except Exception: except Exception:
pass pass
return [] return {}
def extract_description_from_readme(readme_path: Path) -> str | None: def extract_description_from_readme(readme_path: Path) -> str | None:
@ -129,24 +119,24 @@ def extract_description_from_readme(readme_path: Path) -> str | None:
return None return None
def extract_tags_from_readme(readme_path: Path) -> list[str]: def extract_tags_from_readme(readme_path: Path) -> dict[str, str]:
"""Extract tags from frontmatter in README if present. """Extract tags from frontmatter in README if present.
Supports multiple formats: Supports multiple formats:
- Array: tags: ["Python", "Client"] - Array: tags: ["Python", "Client"]
- Structured YAML: tags:\n language: "Python"\n sdk: "Client" - Structured YAML: tags:\n sdk: "hindsight-client"\n topic: "Learning"
- Object literal: tags: { language: "Python", sdk: "Client" } - Object literal: tags: { sdk: "hindsight-client", topic: "Learning" }
Returns a dict with keys like 'sdk', 'topic', 'language'.
""" """
try: try:
content = readme_path.read_text() content = readme_path.read_text()
# Check for frontmatter
if content.startswith("---"): if content.startswith("---"):
end_idx = content.find("---", 3) end_idx = content.find("---", 3)
if end_idx > 0: if end_idx > 0:
frontmatter = content[3:end_idx] frontmatter = content[3:end_idx]
lines = frontmatter.split("\n") lines = frontmatter.split("\n")
# Look for tags: line
for i, line in enumerate(lines): for i, line in enumerate(lines):
if line.strip().startswith("tags:"): if line.strip().startswith("tags:"):
tags_str = line.split("tags:", 1)[1].strip() tags_str = line.split("tags:", 1)[1].strip()
@ -154,51 +144,47 @@ def extract_tags_from_readme(readme_path: Path) -> list[str]:
# Inline array format: tags: ["Python", "Client"] # Inline array format: tags: ["Python", "Client"]
if tags_str.startswith("["): if tags_str.startswith("["):
tags_str = tags_str.strip("[]") tags_str = tags_str.strip("[]")
return [t.strip().strip('"').strip("'") for t in tags_str.split(",")] values = [t.strip().strip('"').strip("'") for t in tags_str.split(",")]
return _infer_tags_from_list(values)
# JavaScript object literal format: tags: { language: "Python", sdk: "Client", topic: "Learning" } # Object literal: tags: { sdk: "hindsight-client", topic: "Learning" }
if tags_str.startswith("{"): if tags_str.startswith("{"):
tags = []
# Extract the entire object literal (might span multiple lines)
obj_str = tags_str obj_str = tags_str
if "}" not in obj_str: if "}" not in obj_str:
# Multi-line object - collect remaining lines
for j in range(i + 1, len(lines)): for j in range(i + 1, len(lines)):
obj_str += " " + lines[j].strip() obj_str += " " + lines[j].strip()
if "}" in lines[j]: if "}" in lines[j]:
break break
result = {}
# Parse the object literal for pair in obj_str.strip("{}").split(","):
obj_str = obj_str.strip("{}")
# Split by comma and extract key-value pairs
for pair in obj_str.split(","):
if ":" in pair: if ":" in pair:
key, value = pair.split(":", 1) k, v = pair.split(":", 1)
value = value.strip().strip('"').strip("'") k = k.strip().strip('"').strip("'")
if value: v = v.strip().strip('"').strip("'")
tags.append(value) if k and v:
return tags result[k] = v
return result
# Structured YAML format: # Structured YAML:
# tags: # tags:
# language: "Python" # sdk: "hindsight-client"
# sdk: "Client" # topic: "Learning"
if not tags_str or tags_str == "": if not tags_str:
# Parse structured tags from following lines result = {}
tags = []
for j in range(i + 1, len(lines)): for j in range(i + 1, len(lines)):
next_line = lines[j].strip() next_line = lines[j].strip()
if not next_line or not next_line.startswith(("language:", "sdk:", "topic:")): if not next_line or not next_line.startswith(("language:", "sdk:", "topic:")):
break break
# Extract value
if ":" in next_line: if ":" in next_line:
value = next_line.split(":", 1)[1].strip().strip('"').strip("'") k, v = next_line.split(":", 1)
if value: k = k.strip()
tags.append(value) v = v.strip().strip('"').strip("'")
return tags if k and v:
result[k] = v
return result
except Exception: except Exception:
pass pass
return [] return {}
def extract_title_from_readme(readme_path: Path) -> str | None: def extract_title_from_readme(readme_path: Path) -> str | None:
@ -364,6 +350,17 @@ def process_applications(cookbook_dir: Path, apps_dir: Path) -> list[dict]:
if not readme_path.exists(): if not readme_path.exists():
continue continue
# Validate that README has frontmatter
readme_raw = readme_path.read_text()
if not readme_raw.startswith("---"):
raise SystemExit(
f"Error: {readme_path} is missing frontmatter.\n"
f"Applications must have a frontmatter block (---) with 'description' and 'tags'."
)
closing = readme_raw.find("---", 3)
if closing <= 0:
raise SystemExit(f"Error: {readme_path} has malformed frontmatter (missing closing ---).")
slug = entry.name slug = entry.name
title = extract_title_from_readme(readme_path) or " ".join(word.capitalize() for word in slug.split("-")) title = extract_title_from_readme(readme_path) or " ".join(word.capitalize() for word in slug.split("-"))
description = extract_description_from_readme(readme_path) description = extract_description_from_readme(readme_path)
@ -371,9 +368,10 @@ def process_applications(cookbook_dir: Path, apps_dir: Path) -> list[dict]:
print(f" Processing app: {entry.name}{slug}.md") print(f" Processing app: {entry.name}{slug}.md")
# Read README content and strip existing frontmatter # Read README content, strip existing frontmatter and local .md links
readme_content = readme_path.read_text() readme_content = readme_path.read_text()
readme_content = strip_frontmatter(readme_content) readme_content = strip_frontmatter(readme_content)
readme_content = strip_local_md_links(readme_content)
# Create application page with frontmatter # Create application page with frontmatter
app_url = f"https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/{entry.name}" app_url = f"https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/{entry.name}"
@ -460,6 +458,14 @@ def update_sidebars(recipes: list[dict], apps: list[dict], sidebars_file: Path):
print("\nUpdated sidebars.ts") print("\nUpdated sidebars.ts")
def strip_local_md_links(content: str) -> str:
"""Replace relative .md links with plain text to avoid broken links in Docusaurus.
e.g. [see article](article.md) see article
"""
return re.sub(r"\[([^\]]+)\]\((?!https?://)([^)]+\.md)\)", r"\1", content)
def clean_description(desc: str) -> str: def clean_description(desc: str) -> str:
"""Clean description for display in carousel cards.""" """Clean description for display in carousel cards."""
if not desc: if not desc:
@ -482,34 +488,19 @@ def clean_description(desc: str) -> str:
return desc return desc
def convert_tags_to_structured(tags: list[str]) -> dict[str, str]: def _infer_tags_from_list(tags: list[str]) -> dict[str, str]:
"""Convert list of tags to structured format. """Infer sdk/topic structure from a plain list of tag values (legacy array format).
New format has 2 tags: Uses heuristics: package names contain '@' or '-' or start lowercase sdk,
- sdk: Package name (detected from tag values) everything else topic.
- topic: anything else (Learning, Quick Start, etc.)
Supported languages:
- Node.js: packages starting with '@vectorize-io'
- Go: packages ending with '-go' or containing 'go-'
- Python: everything else
""" """
structured = {} result: dict[str, str] = {}
topic_tags = {"Learning", "Quick Start", "Recommendation", "Chat"}
for tag in tags: for tag in tags:
# Check if it's a topic tag if "@" in tag or (tag and not tag[0].isupper()):
if tag in topic_tags: result["sdk"] = tag
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: else:
# Legacy tag values - map to new format result["topic"] = tag
# For now, treat everything else as SDK/package identifier return result
structured["sdk"] = tag
return structured
def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path): def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path):
@ -521,21 +512,16 @@ def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path)
description = r.get("description", "") description = r.get("description", "")
if description: if description:
description = clean_description(description).replace('"', '\\"') description = clean_description(description).replace('"', '\\"')
tags = r.get("tags", []) tags: dict[str, str] = r.get("tags", {})
item = f' {{\n title: "{title}",\n href: "/cookbook/recipes/{r["slug"]}"' item = f' {{\n title: "{title}",\n href: "/cookbook/recipes/{r["slug"]}"'
if description: if description:
item += f',\n description: "{description}"' item += f',\n description: "{description}"'
if tags: if tags:
# Convert tags list to structured format
structured_tags = convert_tags_to_structured(tags)
tags_parts = [] tags_parts = []
if "language" in structured_tags: for key in ("language", "sdk", "topic"):
tags_parts.append(f'language: "{structured_tags["language"]}"') if key in tags:
if "sdk" in structured_tags: tags_parts.append(f'{key}: "{tags[key]}"')
tags_parts.append(f'sdk: "{structured_tags["sdk"]}"')
if "topic" in structured_tags:
tags_parts.append(f'topic: "{structured_tags["topic"]}"')
if tags_parts: if tags_parts:
item += f",\n tags: {{ {', '.join(tags_parts)} }}" item += f",\n tags: {{ {', '.join(tags_parts)} }}"
item += "\n }" item += "\n }"
@ -550,21 +536,16 @@ def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path)
description = a.get("description", "") description = a.get("description", "")
if description: if description:
description = clean_description(description).replace('"', '\\"') description = clean_description(description).replace('"', '\\"')
tags = a.get("tags", []) tags = a.get("tags", {})
item = f' {{\n title: "{title}",\n href: "/cookbook/applications/{a["slug"]}"' item = f' {{\n title: "{title}",\n href: "/cookbook/applications/{a["slug"]}"'
if description: if description:
item += f',\n description: "{description}"' item += f',\n description: "{description}"'
if tags: if tags:
# Convert tags list to structured format
structured_tags = convert_tags_to_structured(tags)
tags_parts = [] tags_parts = []
if "language" in structured_tags: for key in ("language", "sdk", "topic"):
tags_parts.append(f'language: "{structured_tags["language"]}"') if key in tags:
if "sdk" in structured_tags: tags_parts.append(f'{key}: "{tags[key]}"')
tags_parts.append(f'sdk: "{structured_tags["sdk"]}"')
if "topic" in structured_tags:
tags_parts.append(f'topic: "{structured_tags["topic"]}"')
if tags_parts: if tags_parts:
item += f",\n tags: {{ {', '.join(tags_parts)} }}" item += f",\n tags: {{ {', '.join(tags_parts)} }}"
item += "\n }" item += "\n }"
@ -573,34 +554,42 @@ def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path)
apps_json = ",\n".join(app_items) apps_json = ",\n".join(app_items)
content = f"""--- content = f"""---
sidebar_position: 1 title: Cookbook
hide_table_of_contents: true 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'; import CookbookGrid from '@site/src/components/CookbookGrid';
<div className="cookbook-page"> <div>
# Cookbook <div style={{{{textAlign: 'center', marginBottom: '3.5rem'}}}}>
<h1 style={{{{
fontSize: '3rem',
fontWeight: 800,
background: 'linear-gradient(135deg, #0074d9, #009296)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
letterSpacing: '-0.03em',
lineHeight: 1.15,
marginBottom: '0.75rem',
}}}}>Cookbook</h1>
<p style={{{{fontSize: '1.05rem', color: 'var(--ifm-color-emphasis-600)', maxWidth: 520, margin: '0 auto', lineHeight: 1.7}}}}>
Practical examples and complete applications built with Hindsight.
</p>
</div>
Learn how to build with Hindsight through practical examples: ## Recipes
- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases <CookbookGrid
- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration
<RecipeCarousel
title="Recipes"
items={{[ items={{[
{recipes_json} {recipes_json}
]}} ]}}
/> />
<RecipeCarousel ## Applications
title="Applications"
<CookbookGrid
items={{[ items={{[
{apps_json} {apps_json}
]}} ]}}
@ -682,7 +671,6 @@ def main():
print("Syncing hindsight-cookbook...\n") print("Syncing hindsight-cookbook...\n")
docs_dir = get_docs_dir() docs_dir = get_docs_dir()
sidebars_file = get_sidebars_file()
recipes_dir = docs_dir / "recipes" recipes_dir = docs_dir / "recipes"
apps_dir = docs_dir / "applications" apps_dir = docs_dir / "applications"
@ -758,9 +746,8 @@ def main():
all_recipes = recipes + manual_recipes all_recipes = recipes + manual_recipes
all_apps = apps + manual_apps all_apps = apps + manual_apps
# Update sidebars.ts and index # Update cookbook index
if all_recipes or all_apps: if all_recipes or all_apps:
update_sidebars(all_recipes, all_apps, sidebars_file)
update_cookbook_index(all_recipes, all_apps, docs_dir) update_cookbook_index(all_recipes, all_apps, docs_dir)
print( print(

View file

@ -1,3 +1,75 @@
/* Filters */
.filters {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1.25rem;
}
.filterGroup {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4rem;
}
.filterLabel {
font-size: 0.72rem;
font-weight: 700;
color: var(--ifm-color-emphasis-500);
text-transform: uppercase;
letter-spacing: 0.06em;
min-width: 44px;
}
.filterPill {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.78rem;
font-weight: 500;
padding: 0.2rem 0.65rem;
border-radius: 999px;
border: 1px solid var(--ifm-color-emphasis-300);
background: transparent;
color: var(--ifm-color-emphasis-700);
cursor: pointer;
transition: all 0.15s ease;
font-family: inherit;
}
.filterPill:hover {
border-color: var(--ifm-color-primary);
color: var(--ifm-color-primary);
}
.filterPillActive {
background: var(--ifm-color-primary);
border-color: var(--ifm-color-primary);
color: #fff;
}
.filterPillActive:hover {
color: #fff;
}
.filterPillIcon {
width: 13px;
height: 13px;
object-fit: contain;
flex-shrink: 0;
vertical-align: middle;
}
[data-theme='dark'] .filterPill {
border-color: rgba(255, 255, 255, 0.15);
color: var(--ifm-color-emphasis-600);
}
[data-theme='dark'] .filterPillActive {
color: #fff;
}
.grid { .grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@ -100,6 +172,9 @@
} }
.cardSdk { .cardSdk {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.72rem; font-size: 0.72rem;
font-weight: 500; font-weight: 500;
font-family: 'JetBrains Mono', 'Fira Code', monospace; font-family: 'JetBrains Mono', 'Fira Code', monospace;
@ -109,6 +184,13 @@
border-radius: 4px; border-radius: 4px;
} }
.sdkIcon {
width: 13px;
height: 13px;
object-fit: contain;
flex-shrink: 0;
}
[data-theme='dark'] .cardSdk { [data-theme='dark'] .cardSdk {
background: rgba(255, 255, 255, 0.07); background: rgba(255, 255, 255, 0.07);
color: var(--ifm-color-emphasis-600); color: var(--ifm-color-emphasis-600);

View file

@ -1,5 +1,6 @@
import React from 'react'; import React, {useState} from 'react';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import styles from './CookbookGrid.module.css'; import styles from './CookbookGrid.module.css';
export interface CookbookCard { export interface CookbookCard {
@ -16,6 +17,26 @@ interface CookbookGridProps {
items: CookbookCard[]; items: CookbookCard[];
} }
function sdkIcon(sdk: string): string | null {
if (sdk.startsWith('@') || sdk.includes('node') || sdk.includes('chat') || sdk.includes('ai-sdk')) {
return '/img/icons/nodejs.png';
}
if (sdk.includes('-go') || sdk === 'go') {
return '/img/icons/golang.png';
}
if (sdk.includes('hindsight-client') || sdk.includes('hindsight-api') || sdk.includes('litellm') || sdk.includes('pydantic') || sdk.includes('crewai')) {
return '/img/icons/python.svg';
}
return null;
}
function SdkIcon({sdk, className}: {sdk: string; className?: string}) {
const icon = sdkIcon(sdk);
const src = useBaseUrl(icon ?? '');
if (!icon) return null;
return <img src={src} alt="" className={className} aria-hidden />;
}
function Card({title, href, description, tags}: CookbookCard) { function Card({title, href, description, tags}: CookbookCard) {
return ( return (
<Link to={href} className={styles.card}> <Link to={href} className={styles.card}>
@ -25,7 +46,12 @@ function Card({title, href, description, tags}: CookbookCard) {
{(tags?.topic || tags?.sdk) && ( {(tags?.topic || tags?.sdk) && (
<div className={styles.cardFooter}> <div className={styles.cardFooter}>
{tags.topic && <span className={styles.cardTopic}>{tags.topic}</span>} {tags.topic && <span className={styles.cardTopic}>{tags.topic}</span>}
{tags.sdk && <span className={styles.cardSdk}>{tags.sdk}</span>} {tags.sdk && (
<span className={styles.cardSdk}>
<SdkIcon sdk={tags.sdk} className={styles.sdkIcon} />
{tags.sdk}
</span>
)}
</div> </div>
)} )}
</div> </div>
@ -34,11 +60,68 @@ function Card({title, href, description, tags}: CookbookCard) {
} }
export default function CookbookGrid({items}: CookbookGridProps) { export default function CookbookGrid({items}: CookbookGridProps) {
const [selectedTopic, setSelectedTopic] = useState<string | null>(null);
const [selectedSdk, setSelectedSdk] = useState<string | null>(null);
const topics = [...new Set(items.map((i) => i.tags?.topic).filter(Boolean))] as string[];
const sdks = [...new Set(items.map((i) => i.tags?.sdk).filter(Boolean))] as string[];
const filtered = items.filter((item) => {
if (selectedTopic && item.tags?.topic !== selectedTopic) return false;
if (selectedSdk && item.tags?.sdk !== selectedSdk) return false;
return true;
});
const hasFilters = topics.length > 1 || sdks.length > 1;
return ( return (
<div>
{hasFilters && (
<div className={styles.filters}>
{topics.length > 1 && (
<div className={styles.filterGroup}>
<span className={styles.filterLabel}>Topic</span>
<button
className={`${styles.filterPill} ${selectedTopic === null ? styles.filterPillActive : ''}`}
onClick={() => setSelectedTopic(null)}>
All
</button>
{topics.map((topic) => (
<button
key={topic}
className={`${styles.filterPill} ${selectedTopic === topic ? styles.filterPillActive : ''}`}
onClick={() => setSelectedTopic(selectedTopic === topic ? null : topic)}>
{topic}
</button>
))}
</div>
)}
{sdks.length > 1 && (
<div className={styles.filterGroup}>
<span className={styles.filterLabel}>SDK</span>
<button
className={`${styles.filterPill} ${selectedSdk === null ? styles.filterPillActive : ''}`}
onClick={() => setSelectedSdk(null)}>
All
</button>
{sdks.map((sdk) => (
<button
key={sdk}
className={`${styles.filterPill} ${selectedSdk === sdk ? styles.filterPillActive : ''}`}
onClick={() => setSelectedSdk(selectedSdk === sdk ? null : sdk)}>
<SdkIcon sdk={sdk} className={styles.filterPillIcon} />
{sdk}
</button>
))}
</div>
)}
</div>
)}
<div className={styles.grid}> <div className={styles.grid}>
{items.map((item) => ( {filtered.map((item) => (
<Card key={item.href} {...item} /> <Card key={item.href} {...item} />
))} ))}
</div> </div>
</div>
); );
} }

View file

@ -396,7 +396,7 @@ a.menu__link[href*="/sdks/python"]::before {
/* Node.js logo */ /* Node.js logo */
a.menu__link[href*="/sdks/nodejs"]::before { a.menu__link[href*="/sdks/nodejs"]::before {
background-image: url('/img/icons/nodejs.svg'); background-image: url('/img/icons/nodejs.png');
} }
/* CLI - terminal icon */ /* CLI - terminal icon */

View file

@ -0,0 +1,143 @@
---
sidebar_position: 1
---
# CableConnect — AI Customer Service Copilot Demo
:::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/cable-co)
:::
An AI copilot that assists a customer service representative (CSR) by suggesting responses and actions for simulated customer scenarios. The CSR approves or rejects each suggestion with feedback. The copilot learns from corrections via [Hindsight](https://hindsight.vectorize.io) and stops repeating mistakes.
## Prerequisites
- Python 3.11+
- Node.js 18+
- An OpenAI API key (for GPT-4o)
- A Hindsight API key ([sign up](https://hindsight.vectorize.io))
## Quick Start
### 1. Backend
```bash
cd backend
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Create a .env file with your credentials
cat > .env << 'EOF'
OPENAI_API_KEY=sk-your-openai-key
HINDSIGHT_API_KEY=hsk_your-hindsight-key
HINDSIGHT_API_URL=https://api.hindsight.vectorize.io
HINDSIGHT_BANK_NAME=cable-connect-demo
EOF
# Start the backend (port 8002)
./run.sh
```
### 2. Frontend
In a second terminal:
```bash
cd frontend
# Install dependencies
npm install
# Start the dev server (port 5173)
npm run dev
```
Open http://localhost:5173 in your browser.
## Running the Demo
1. Click **Next Customer** to load the first scenario
2. The AI copilot will analyze the customer's issue and suggest a response
3. Review the suggestion in the right panel:
- **Send to Customer** — approves the response, sends it to the customer chat
- **Approve** — executes a system action (credit, dispatch, etc.)
- **Reject** — type feedback explaining what was wrong
4. The copilot adjusts based on your feedback and tries again
5. Continue until the customer is satisfied, then approve the resolve action
6. Click **Next Customer** for the next scenario
### What to Watch For
The 8 scenarios include 3 **learning pairs** — the first scenario teaches the agent a rule, the second tests whether it remembers:
| Pair | Scenarios | What the Agent Learns |
|------|-----------|----------------------|
| A | 2 then 4 | Credit adjustments are capped at $25 |
| B | 3 then 8 | Run remote diagnostics before scheduling a dispatch |
| C | 5 then 6 | Retention offers require 24+ months of tenure |
With **Memory On** (the default), the copilot recalls past CSR feedback before each new customer. By the test scenario, it should handle the situation correctly without being corrected.
Toggle **Memory Off** to see how the agent behaves without learning — it will make the same mistakes every time.
### Controls
- **Mode** dropdown — Switch between Memory On and Memory Off
- **Reset** — Deletes all stored memories and starts the scenario queue over
- **Refresh Models** — Manually triggers a refresh of the agent's mental models
## Configuration
All configuration is via environment variables in `backend/.env`:
| Variable | Default | Description |
|----------|---------|-------------|
| `OPENAI_API_KEY` | — | Your OpenAI API key (required) |
| `HINDSIGHT_API_KEY` | — | Your Hindsight API key (required) |
| `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` | Hindsight API endpoint |
| `HINDSIGHT_BANK_NAME` | `cable-connect-demo` | Name of the memory bank |
| `LLM_MODEL` | `openai/gpt-4o` | LLM model (via LiteLLM format) |
| `BACKEND_PORT` | `8002` | Backend server port |
## Project Structure
```
cable-co/
├── backend/
│ ├── run.sh # Start script (loads .env, runs uvicorn)
│ ├── requirements.txt
│ ├── telecom_data.py # Accounts, plans, billing, outages, scenarios
│ ├── agent_tools.py # 19 tools + business rule hints
│ └── app/
│ ├── main.py # FastAPI + WebSocket
│ ├── config.py
│ └── services/
│ ├── agent_service.py # Copilot loop with CSR approval gate
│ └── memory_service.py # Hindsight retain/recall/mental models
├── frontend/
│ ├── package.json
│ ├── vite.config.ts
│ └── src/
│ ├── App.tsx
│ ├── stores/sessionStore.ts
│ ├── hooks/useWebSocket.ts
│ └── components/
│ ├── ControlBar.tsx
│ ├── CustomerChat.tsx
│ ├── CopilotChat.tsx
│ ├── KnowledgePanel.tsx
│ └── MentalModelsPanel.tsx
└── article.md # Detailed writeup of how agent learning works
```
## How It Works
See article.md for a detailed explanation of the agent learning architecture, including how Hindsight transforms CSR feedback into observations and mental models that improve the copilot's behavior over time.

View file

@ -0,0 +1,122 @@
---
sidebar_position: 3
---
# Chat Memory App (Hindsight Cloud)
:::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/chat-memory-cloud)
:::
A demo chat application with persistent per-user memory powered by [Hindsight Cloud](https://hindsight.vectorize.io). Supports OpenAI or Groq as the LLM provider. No local Hindsight server required.
## Features
- 🧠 **Persistent Memory**: Each user gets their own memory bank that remembers conversations
- ☁️ **Hindsight Cloud**: Memory stored in the cloud — no Docker setup needed
- 🔀 **Selectable LLM**: Choose between OpenAI (GPT-4o) or Groq (Qwen 32B)
- 🎯 **Per-User Context**: Isolated memory per user with automatic context retrieval
- 💬 **Real-time Chat**: Instant responses with memory-augmented context
## Setup
### 1. Get API Keys
- **Hindsight** — Sign up at https://hindsight.vectorize.io
- **OpenAI** — https://platform.openai.com/api-keys
- **Groq** (alternative) — Free at https://console.groq.com/home
### 2. Configure Environment
Edit `.env.local` with your API keys and preferred provider:
```bash
# LLM Provider: "openai" or "groq"
LLM_PROVIDER=openai
# OpenAI (required if LLM_PROVIDER=openai)
OPENAI_API_KEY=sk-your-key-here
# Groq (required if LLM_PROVIDER=groq)
GROQ_API_KEY=gsk_your-key-here
# Hindsight Cloud
HINDSIGHT_API_URL=https://api.hindsight.vectorize.io
HINDSIGHT_API_KEY=hsk_your-key-here
```
You can also override the model with `LLM_MODEL` (defaults to `gpt-4o` for OpenAI, `qwen/qwen3-32b` for Groq).
### 3. Install Dependencies
```bash
npm install
```
### 4. Run the App
```bash
npm run dev
```
Open http://localhost:3000 in your browser.
## How It Works
1. **User Identity**: Each browser session gets a unique user ID
2. **Memory Bank Creation**: First message creates a personal memory bank in Hindsight Cloud
3. **Context Retrieval**: Before responding, relevant memories are recalled
4. **Memory Augmented Response**: LLM generates responses with memory context
5. **Conversation Storage**: Each conversation is retained for future context
## Architecture
```
User Message
Next.js API Route (/api/chat)
Hindsight Cloud recall() → Get relevant memories
OpenAI or Groq → Generate response with memory context
Hindsight Cloud retain() → Store conversation
Response to User
```
## Memory Bank Structure
Each user gets their own isolated memory bank with:
- **Name**: "Chat Memory for [userId]"
- **Background**: Conversational AI assistant context
- **Disposition**: Empathetic (4), Low Skepticism (2), Balanced Literalism (3)
## Try It Out
1. **First Conversation**: Tell the assistant about yourself
- "Hi! I'm a software engineer from San Francisco. I love Python and machine learning."
2. **Second Conversation**: Ask what it remembers
- "What do you know about me?"
- "What programming languages do I like?"
3. **Context Building**: Continue sharing preferences
- "I prefer VS Code over other editors"
- "I'm working on a React project"
4. **Memory Verification**: Log in to the [Hindsight dashboard](https://hindsight.vectorize.io) to see stored memories
## Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `LLM_PROVIDER` | `openai` | LLM provider: `openai` or `groq` |
| `LLM_MODEL` | auto | Model override (defaults: `gpt-4o` / `qwen/qwen3-32b`) |
| `OPENAI_API_KEY` | — | Required when using OpenAI |
| `GROQ_API_KEY` | — | Required when using Groq |
| `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` | Hindsight API endpoint |
| `HINDSIGHT_API_KEY` | — | Your Hindsight API key |

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 1 sidebar_position: 2
--- ---
# Chat Memory App # Chat Memory App

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 2 sidebar_position: 4
--- ---
# Chat SDK Multi-Platform Bot # Chat SDK Multi-Platform Bot

View file

@ -0,0 +1,81 @@
---
sidebar_position: 5
---
# ClaimsIQ — Insurance Claims Triage Agent Demo
:::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/claims-iq)
:::
An AI agent that processes insurance claims through a multi-step workflow. The agent starts as a "confused rookie" and becomes a "seasoned expert" as [Hindsight](https://github.com/anthropics/hindsight) memories accumulate.
Watch the agent learn coverage rules, adjuster assignments, and escalation patterns in real-time through a pipeline dashboard.
## Quick Start
### 1. Start Hindsight API (port 8888)
```bash
docker run -p 8888:8888 ghcr.io/anthropics/hindsight:latest
```
### 2. Start Backend (port 8000)
```bash
cd backend
pip install -r requirements.txt
./run.sh
```
### 3. Start Frontend (port 5173)
```bash
cd frontend
npm install
npm run dev
```
### 4. Open Browser
Navigate to `http://localhost:5173`.
## How It Works
The agent processes insurance claims using 6 tools:
1. **Classify** the claim category (auto, property, flood, etc.)
2. **Look up** the policy details
3. **Check coverage** rules for the policy type
4. **Check fraud** indicators
5. **Assign** the right adjuster
6. **Submit** a decision for validation
The system validates each decision against ground truth. If the agent makes a mistake (wrong adjuster, incorrect coverage call), the decision is rejected with feedback — creating learning signal for Hindsight.
## Agent Modes
| Mode | Description |
|------|-------------|
| **No Memory** | Baseline — agent starts fresh every claim |
| **Recall** | Raw facts from past claims injected before processing |
| **Reflect** | LLM-synthesized knowledge injected |
| **Mental Models** | Full Hindsight mental models with auto-refresh |
## Key Learning Challenges
- **Water damage vs Flood**: Gold policies cover water damage (burst pipe) but NOT flood damage (rain/river). The agent must learn this subtle distinction.
- **Adjuster routing**: 8 adjusters with different specialties and regions. The agent must learn who handles what.
- **Escalation thresholds**: Claims over $50K need a senior adjuster; over $100K need manager review.
- **Fraud detection**: Multiple indicators (near-limit claims, repeated address) route to the fraud specialist.
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `LLM_MODEL` | `openai/gpt-4o` | LLM model for the agent |
| `HINDSIGHT_API_URL` | `http://localhost:8888` | Hindsight API URL |
| `BACKEND_PORT` | `8000` | Backend server port |

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 3 sidebar_position: 6
--- ---
# CrewAI + Hindsight Memory # CrewAI + Hindsight Memory

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 4 sidebar_position: 7
--- ---
# Deliveryman Demo # Deliveryman Demo

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 5 sidebar_position: 8
--- ---
# Go Memory-Augmented API # Go Memory-Augmented API

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 6 sidebar_position: 9
--- ---
# Memory Approaches Comparison Demo # Memory Approaches Comparison Demo

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 7 sidebar_position: 10
--- ---
# Tool Learning Demo # Tool Learning Demo

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 8 sidebar_position: 11
--- ---
# OpenAI Agent + Hindsight Memory Integration # OpenAI Agent + Hindsight Memory Integration

View file

@ -0,0 +1,236 @@
---
sidebar_position: 12
---
# Pydantic AI + Hindsight Memory
:::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/pydantic-ai-memory)
:::
Give your Pydantic AI agents persistent long-term memory. Chat with an assistant multiple times and watch it remember what you told it in previous sessions.
## What This Demonstrates
- **Memory tools** — retain, recall, and reflect via `create_hindsight_tools()`
- **Auto-injected context** — relevant memories in every run via `memory_instructions()`
- **Persistent memory across sessions** — the agent remembers between script runs
- **Interactive chat loop** with message history reuse
## Architecture
```
Session 1:
You: "I'm a Python developer working on a FastAPI project"
├─ memory_instructions() ──► recalls prior context (empty on first run)
├─ Agent decides to call hindsight_retain ──► stores the fact
└─ Agent responds with acknowledgement
Session 2:
You: "What do you know about me?"
├─ memory_instructions() ──► injects "User is a Python developer..."
├─ Agent calls hindsight_recall ──► finds stored facts
└─ Agent responds with everything it remembers
```
## Prerequisites
1. **Hindsight running**
```bash
export OPENAI_API_KEY=your-key
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
```
2. **OpenAI API key** (for Pydantic AI's LLM)
```bash
export OPENAI_API_KEY=your-key
```
3. **Install dependencies**
```bash
cd applications/pydantic-ai-memory
pip install -r requirements.txt
```
## Quick Start
### Interactive Chat
```bash
python personal_assistant.py
```
Example session:
```
Personal assistant ready (bank: personal-assistant)
Type 'quit' or 'exit' to stop.
You: I'm a Python developer and I love hiking on weekends
Assistant: I've noted that! You're a Python developer who enjoys weekend hiking.
You: What do you know about me?
Assistant: From my memory, I know that you're a Python developer and you
love hiking on weekends.
You: quit
```
Run it again — the agent still remembers:
```
You: What are my hobbies?
Assistant: Based on my memories, you enjoy hiking on weekends!
```
### Single Query
```bash
python personal_assistant.py "What do you remember about my preferences?"
```
### Reset Memory
```bash
python personal_assistant.py --reset
```
## How It Works
### 1. Create a Hindsight Client
```python
from hindsight_client import Hindsight
client = Hindsight(base_url="http://localhost:8888")
```
### 2. Create Memory Tools
`create_hindsight_tools()` returns Pydantic AI `Tool` instances the agent can call:
```python
from hindsight_pydantic_ai import create_hindsight_tools
tools = create_hindsight_tools(client=client, bank_id="personal-assistant")
# Returns: [hindsight_retain, hindsight_recall, hindsight_reflect]
```
### 3. Add Memory Instructions
`memory_instructions()` returns an async callable that auto-recalls relevant memories and injects them into the system prompt on every run:
```python
from hindsight_pydantic_ai import memory_instructions
instructions_fn = memory_instructions(
client=client,
bank_id="personal-assistant",
query="important context about the user",
max_results=5,
)
```
### 4. Wire Up the Agent
```python
from pydantic_ai import Agent
agent = Agent(
"openai:gpt-4o-mini",
system_prompt="You are a helpful assistant with long-term memory...",
tools=tools,
instructions=[instructions_fn],
)
result = await agent.run("What do you know about me?")
```
## Core Files
| File | Description |
|------|-------------|
| `personal_assistant.py` | Complete working example with interactive chat and single-query modes |
| `requirements.txt` | Python dependencies |
## Customization
### Use Only Tools (No Auto-Injection)
Let the agent decide when to search memory, rather than always injecting context:
```python
agent = Agent(
"openai:gpt-4o-mini",
tools=create_hindsight_tools(client=client, bank_id="my-bank"),
)
```
### Use Only Instructions (No Tools)
Auto-inject memories without giving the agent explicit retain/recall/reflect tools:
```python
agent = Agent(
"openai:gpt-4o-mini",
instructions=[memory_instructions(client=client, bank_id="my-bank")],
)
```
### Select Specific Tools
```python
tools = create_hindsight_tools(
client=client,
bank_id="my-bank",
include_retain=True,
include_recall=True,
include_reflect=False, # Omit reflect
)
```
### Use a Different Model
Any [Pydantic AI model](https://ai.pydantic.dev/models/) works:
```python
agent = Agent(
"anthropic:claude-sonnet-4-20250514",
tools=create_hindsight_tools(client=client, bank_id="my-bank"),
)
```
## Common Issues
**"Connection refused"**
- Make sure Hindsight is running on `localhost:8888`
**"OPENAI_API_KEY not set"**
```bash
export OPENAI_API_KEY=your-key
```
**"No module named 'hindsight_pydantic_ai'"**
```bash
pip install -r requirements.txt
```
---
**Built with:**
- [Pydantic AI](https://ai.pydantic.dev) - Type-safe AI agent framework
- [hindsight-pydantic-ai](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/pydantic-ai) - Hindsight memory tools for Pydantic AI
- [Hindsight](https://github.com/vectorize-io/hindsight) - Long-term memory for AI agents

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 9 sidebar_position: 13
--- ---
# Sanity CMS Blog Memory # Sanity CMS Blog Memory

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 10 sidebar_position: 14
--- ---
# Stance Tracker # Stance Tracker

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 11 sidebar_position: 15
--- ---
# Hindsight AI SDK - Personal Chef # Hindsight AI SDK - Personal Chef

View file

@ -101,11 +101,23 @@ import CookbookGrid from '@site/src/components/CookbookGrid';
<CookbookGrid <CookbookGrid
items={[ items={[
{
title: "CableConnect — AI Customer Service Copilot Demo",
href: "/cookbook/applications/cable-co",
description: "AI customer service copilot that learns from CSR feedback via Hindsight",
tags: { sdk: "hindsight-client", topic: "Customer Service" }
},
{ {
title: "Chat Memory App", title: "Chat Memory App",
href: "/cookbook/applications/chat-memory", href: "/cookbook/applications/chat-memory",
description: "Real-time chat app with per-user memory using Groq and Hindsight", description: "Real-time chat app with per-user memory using Groq and Hindsight",
tags: { sdk: "hindsight-client", topic: "Chat" } tags: { sdk: "@vectorize-io/hindsight-client", topic: "Chat" }
},
{
title: "Chat Memory App (Hindsight Cloud)",
href: "/cookbook/applications/chat-memory-cloud",
description: "Real-time chat app with per-user memory powered by Hindsight Cloud",
tags: { sdk: "@vectorize-io/hindsight-client", topic: "Chat" }
}, },
{ {
title: "Chat SDK Multi-Platform Bot", title: "Chat SDK Multi-Platform Bot",
@ -113,23 +125,29 @@ import CookbookGrid from '@site/src/components/CookbookGrid';
description: "Multi-platform chat bot with cross-platform memory using Vercel Chat SDK and Hindsight", description: "Multi-platform chat bot with cross-platform memory using Vercel Chat SDK and Hindsight",
tags: { sdk: "@vectorize-io/hindsight-chat", topic: "Recommendation" } tags: { sdk: "@vectorize-io/hindsight-chat", topic: "Recommendation" }
}, },
{
title: "ClaimsIQ — Insurance Claims Triage Agent Demo",
href: "/cookbook/applications/claims-iq",
description: "Insurance claims triage agent that learns adjudication rules via Hindsight",
tags: { sdk: "hindsight-litellm", topic: "Agents" }
},
{ {
title: "CrewAI + Hindsight Memory", title: "CrewAI + Hindsight Memory",
href: "/cookbook/applications/crewai-memory", href: "/cookbook/applications/crewai-memory",
description: "CrewAI agents with persistent long-term memory via Hindsight", description: "CrewAI agents with persistent long-term memory via Hindsight",
tags: { sdk: "Agents" } tags: { sdk: "hindsight-crewai", topic: "Agents" }
}, },
{ {
title: "Deliveryman Demo", title: "Deliveryman Demo",
href: "/cookbook/applications/deliveryman-demo", href: "/cookbook/applications/deliveryman-demo",
description: "Delivery agent simulation demonstrating learning through mental models", description: "Delivery agent simulation demonstrating learning through mental models",
tags: { sdk: "hindsight-client", topic: "Learning" } tags: { sdk: "hindsight-litellm", topic: "Learning" }
}, },
{ {
title: "Go Memory-Augmented API", title: "Go Memory-Augmented API",
href: "/cookbook/applications/go-memory-service", href: "/cookbook/applications/go-memory-service",
description: "Go HTTP microservice with per-user memory banks for a developer knowledge assistant", description: "Go HTTP microservice with per-user memory banks for a developer knowledge assistant",
tags: { sdk: "hindsight-go", topic: "Learning" } tags: { sdk: "hindsight-client-go", topic: "Learning" }
}, },
{ {
title: "Memory Approaches Comparison Demo", title: "Memory Approaches Comparison Demo",
@ -147,19 +165,25 @@ import CookbookGrid from '@site/src/components/CookbookGrid';
title: "OpenAI Agent + Hindsight Memory Integration", title: "OpenAI Agent + Hindsight Memory Integration",
href: "/cookbook/applications/openai-fitness-coach", href: "/cookbook/applications/openai-fitness-coach",
description: "Fitness coach using OpenAI Assistants with Hindsight as memory backend", description: "Fitness coach using OpenAI Assistants with Hindsight as memory backend",
tags: { sdk: "hindsight-client", topic: "Recommendation" } tags: { sdk: "hindsight-api", topic: "Recommendation" }
},
{
title: "Pydantic AI + Hindsight Memory",
href: "/cookbook/applications/pydantic-ai-memory",
description: "Pydantic AI agent with persistent long-term memory via Hindsight",
tags: { sdk: "hindsight-pydantic-ai", topic: "Agents" }
}, },
{ {
title: "Sanity CMS Blog Memory", title: "Sanity CMS Blog Memory",
href: "/cookbook/applications/sanity-blog-memory", href: "/cookbook/applications/sanity-blog-memory",
description: "Sync Sanity CMS blog posts to Hindsight for semantic search and AI insights", description: "Sync Sanity CMS blog posts to Hindsight for semantic search and AI insights",
tags: { sdk: "hindsight-client", topic: "Learning" } tags: { sdk: "@vectorize-io/hindsight-client", topic: "Learning" }
}, },
{ {
title: "Stance Tracker", title: "Stance Tracker",
href: "/cookbook/applications/stancetracker", href: "/cookbook/applications/stancetracker",
description: "Track political candidates' stances over time with automated web scraping", description: "Track political candidates' stances over time with automated web scraping",
tags: { sdk: "hindsight-client", topic: "Recommendation" } tags: { sdk: "@vectorize-io/hindsight-client", topic: "Recommendation" }
}, },
{ {
title: "Hindsight AI SDK - Personal Chef", title: "Hindsight AI SDK - Personal Chef",

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -14,4 +14,4 @@ echo ""
echo "Starting Docusaurus development server..." echo "Starting Docusaurus development server..."
echo "Documentation will be available at: http://localhost:3000" echo "Documentation will be available at: http://localhost:3000"
echo "" echo ""
npm run start -w hindsight-docs npm run start -w hindsight-docs -- --no-open