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:
parent
5c3d3274d7
commit
3d87ef5cee
24 changed files with 915 additions and 150 deletions
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
|
|
@ -1297,7 +1297,6 @@ jobs:
|
|||
|
||||
test-doc-examples:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-rust-cli
|
||||
env:
|
||||
HINDSIGHT_API_LLM_PROVIDER: vertexai
|
||||
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)
|
||||
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Download CLI artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hindsight-cli
|
||||
path: /usr/local/bin
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Make CLI executable
|
||||
run: chmod +x /usr/local/bin/hindsight
|
||||
- name: Cache cargo
|
||||
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
|
||||
uses: astral-sh/setup-uv@v5
|
||||
|
|
|
|||
|
|
@ -29,16 +29,9 @@ IGNORE_DIRS = {".git", "notebooks", "node_modules", "__pycache__", ".venv", "ven
|
|||
|
||||
|
||||
def get_docs_dir() -> Path:
|
||||
"""Find the hindsight-docs directory relative to this script."""
|
||||
# Navigate from hindsight-dev to hindsight-docs
|
||||
"""Find the hindsight-docs src/pages/cookbook directory relative to this script."""
|
||||
script_dir = Path(__file__).parent
|
||||
docs_dir = script_dir.parent.parent / "hindsight-docs" / "docs" / "cookbook"
|
||||
return docs_dir
|
||||
|
||||
|
||||
def get_sidebars_file() -> Path:
|
||||
script_dir = Path(__file__).parent
|
||||
return script_dir.parent.parent / "hindsight-docs" / "sidebars.ts"
|
||||
return script_dir.parent.parent / "hindsight-docs" / "src" / "pages" / "cookbook"
|
||||
|
||||
|
||||
def slugify(filename: str) -> str:
|
||||
|
|
@ -82,30 +75,27 @@ def extract_description_from_notebook(notebook_path: Path) -> str | 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.
|
||||
|
||||
Supports both array format and structured object format.
|
||||
Returns a dict with keys like 'sdk', 'topic', 'language'.
|
||||
"""
|
||||
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" }
|
||||
# Object format already has the right structure
|
||||
if isinstance(tags, dict):
|
||||
result = []
|
||||
for key in ["language", "sdk", "topic"]:
|
||||
if key in tags and tags[key]:
|
||||
result.append(tags[key])
|
||||
return result
|
||||
return {k: v for k, v in tags.items() if v}
|
||||
|
||||
# Array format: fall back to heuristic conversion
|
||||
if isinstance(tags, list):
|
||||
return _infer_tags_from_list(tags)
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
return {}
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Supports multiple formats:
|
||||
- Array: tags: ["Python", "Client"]
|
||||
- Structured YAML: tags:\n language: "Python"\n sdk: "Client"
|
||||
- Object literal: tags: { language: "Python", sdk: "Client" }
|
||||
- Structured YAML: tags:\n sdk: "hindsight-client"\n topic: "Learning"
|
||||
- Object literal: tags: { sdk: "hindsight-client", topic: "Learning" }
|
||||
|
||||
Returns a dict with keys like 'sdk', 'topic', 'language'.
|
||||
"""
|
||||
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()
|
||||
|
|
@ -154,51 +144,47 @@ def extract_tags_from_readme(readme_path: Path) -> list[str]:
|
|||
# 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(",")]
|
||||
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("{"):
|
||||
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(","):
|
||||
result = {}
|
||||
for pair in obj_str.strip("{}").split(","):
|
||||
if ":" in pair:
|
||||
key, value = pair.split(":", 1)
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value:
|
||||
tags.append(value)
|
||||
return tags
|
||||
k, v = pair.split(":", 1)
|
||||
k = k.strip().strip('"').strip("'")
|
||||
v = v.strip().strip('"').strip("'")
|
||||
if k and v:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
# Structured YAML format:
|
||||
# Structured YAML:
|
||||
# tags:
|
||||
# language: "Python"
|
||||
# sdk: "Client"
|
||||
if not tags_str or tags_str == "":
|
||||
# Parse structured tags from following lines
|
||||
tags = []
|
||||
# sdk: "hindsight-client"
|
||||
# topic: "Learning"
|
||||
if not tags_str:
|
||||
result = {}
|
||||
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
|
||||
k, v = next_line.split(":", 1)
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"').strip("'")
|
||||
if k and v:
|
||||
result[k] = v
|
||||
return result
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
return {}
|
||||
|
||||
|
||||
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():
|
||||
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
|
||||
title = extract_title_from_readme(readme_path) or " ".join(word.capitalize() for word in slug.split("-"))
|
||||
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")
|
||||
|
||||
# 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 = strip_frontmatter(readme_content)
|
||||
readme_content = strip_local_md_links(readme_content)
|
||||
|
||||
# Create application page with frontmatter
|
||||
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")
|
||||
|
||||
|
||||
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:
|
||||
"""Clean description for display in carousel cards."""
|
||||
if not desc:
|
||||
|
|
@ -482,34 +488,19 @@ 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.
|
||||
def _infer_tags_from_list(tags: list[str]) -> dict[str, str]:
|
||||
"""Infer sdk/topic structure from a plain list of tag values (legacy array format).
|
||||
|
||||
New format has 2 tags:
|
||||
- sdk: Package name (detected from tag values)
|
||||
- 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
|
||||
Uses heuristics: package names contain '@' or '-' or start lowercase → sdk,
|
||||
everything else → topic.
|
||||
"""
|
||||
structured = {}
|
||||
topic_tags = {"Learning", "Quick Start", "Recommendation", "Chat"}
|
||||
|
||||
result: dict[str, str] = {}
|
||||
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
|
||||
if "@" in tag or (tag and not tag[0].isupper()):
|
||||
result["sdk"] = tag
|
||||
else:
|
||||
# Legacy tag values - map to new format
|
||||
# For now, treat everything else as SDK/package identifier
|
||||
structured["sdk"] = tag
|
||||
|
||||
return structured
|
||||
result["topic"] = tag
|
||||
return result
|
||||
|
||||
|
||||
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", "")
|
||||
if description:
|
||||
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"]}"'
|
||||
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"]}"')
|
||||
for key in ("language", "sdk", "topic"):
|
||||
if key in tags:
|
||||
tags_parts.append(f'{key}: "{tags[key]}"')
|
||||
if tags_parts:
|
||||
item += f",\n tags: {{ {', '.join(tags_parts)} }}"
|
||||
item += "\n }"
|
||||
|
|
@ -550,21 +536,16 @@ def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path)
|
|||
description = a.get("description", "")
|
||||
if description:
|
||||
description = clean_description(description).replace('"', '\\"')
|
||||
tags = a.get("tags", [])
|
||||
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"]}"')
|
||||
for key in ("language", "sdk", "topic"):
|
||||
if key in tags:
|
||||
tags_parts.append(f'{key}: "{tags[key]}"')
|
||||
if tags_parts:
|
||||
item += f",\n tags: {{ {', '.join(tags_parts)} }}"
|
||||
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)
|
||||
|
||||
content = f"""---
|
||||
sidebar_position: 1
|
||||
title: Cookbook
|
||||
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
|
||||
- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration
|
||||
|
||||
<RecipeCarousel
|
||||
title="Recipes"
|
||||
<CookbookGrid
|
||||
items={{[
|
||||
{recipes_json}
|
||||
]}}
|
||||
/>
|
||||
|
||||
<RecipeCarousel
|
||||
title="Applications"
|
||||
## Applications
|
||||
|
||||
<CookbookGrid
|
||||
items={{[
|
||||
{apps_json}
|
||||
]}}
|
||||
|
|
@ -682,7 +671,6 @@ def main():
|
|||
print("Syncing hindsight-cookbook...\n")
|
||||
|
||||
docs_dir = get_docs_dir()
|
||||
sidebars_file = get_sidebars_file()
|
||||
recipes_dir = docs_dir / "recipes"
|
||||
apps_dir = docs_dir / "applications"
|
||||
|
||||
|
|
@ -758,9 +746,8 @@ def main():
|
|||
all_recipes = recipes + manual_recipes
|
||||
all_apps = apps + manual_apps
|
||||
|
||||
# Update sidebars.ts and index
|
||||
# Update cookbook 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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
|
@ -100,6 +172,9 @@
|
|||
}
|
||||
|
||||
.cardSdk {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
|
|
@ -109,6 +184,13 @@
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sdkIcon {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .cardSdk {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
color: var(--ifm-color-emphasis-600);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import styles from './CookbookGrid.module.css';
|
||||
|
||||
export interface CookbookCard {
|
||||
|
|
@ -16,6 +17,26 @@ interface CookbookGridProps {
|
|||
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) {
|
||||
return (
|
||||
<Link to={href} className={styles.card}>
|
||||
|
|
@ -25,7 +46,12 @@ function Card({title, href, description, tags}: CookbookCard) {
|
|||
{(tags?.topic || tags?.sdk) && (
|
||||
<div className={styles.cardFooter}>
|
||||
{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>
|
||||
|
|
@ -34,11 +60,68 @@ function Card({title, href, description, tags}: CookbookCard) {
|
|||
}
|
||||
|
||||
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 (
|
||||
<div className={styles.grid}>
|
||||
{items.map((item) => (
|
||||
<Card key={item.href} {...item} />
|
||||
))}
|
||||
<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}>
|
||||
{filtered.map((item) => (
|
||||
<Card key={item.href} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ a.menu__link[href*="/sdks/python"]::before {
|
|||
|
||||
/* Node.js logo */
|
||||
a.menu__link[href*="/sdks/nodejs"]::before {
|
||||
background-image: url('/img/icons/nodejs.svg');
|
||||
background-image: url('/img/icons/nodejs.png');
|
||||
}
|
||||
|
||||
/* CLI - terminal icon */
|
||||
|
|
|
|||
143
hindsight-docs/src/pages/cookbook/applications/cable-co.md
Normal file
143
hindsight-docs/src/pages/cookbook/applications/cable-co.md
Normal 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.
|
||||
|
|
@ -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 |
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Chat Memory App
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Chat SDK Multi-Platform Bot
|
||||
|
|
|
|||
81
hindsight-docs/src/pages/cookbook/applications/claims-iq.md
Normal file
81
hindsight-docs/src/pages/cookbook/applications/claims-iq.md
Normal 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 |
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# CrewAI + Hindsight Memory
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Deliveryman Demo
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 5
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Go Memory-Augmented API
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Memory Approaches Comparison Demo
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Tool Learning Demo
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 8
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
# OpenAI Agent + Hindsight Memory Integration
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 9
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# Sanity CMS Blog Memory
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 10
|
||||
sidebar_position: 14
|
||||
---
|
||||
|
||||
# Stance Tracker
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
sidebar_position: 15
|
||||
---
|
||||
|
||||
# Hindsight AI SDK - Personal Chef
|
||||
|
|
|
|||
|
|
@ -101,11 +101,23 @@ import CookbookGrid from '@site/src/components/CookbookGrid';
|
|||
|
||||
<CookbookGrid
|
||||
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",
|
||||
href: "/cookbook/applications/chat-memory",
|
||||
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",
|
||||
|
|
@ -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",
|
||||
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",
|
||||
href: "/cookbook/applications/crewai-memory",
|
||||
description: "CrewAI agents with persistent long-term memory via Hindsight",
|
||||
tags: { sdk: "Agents" }
|
||||
tags: { sdk: "hindsight-crewai", topic: "Agents" }
|
||||
},
|
||||
{
|
||||
title: "Deliveryman Demo",
|
||||
href: "/cookbook/applications/deliveryman-demo",
|
||||
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",
|
||||
href: "/cookbook/applications/go-memory-service",
|
||||
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",
|
||||
|
|
@ -147,19 +165,25 @@ import CookbookGrid from '@site/src/components/CookbookGrid';
|
|||
title: "OpenAI Agent + Hindsight Memory Integration",
|
||||
href: "/cookbook/applications/openai-fitness-coach",
|
||||
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",
|
||||
href: "/cookbook/applications/sanity-blog-memory",
|
||||
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",
|
||||
href: "/cookbook/applications/stancetracker",
|
||||
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",
|
||||
|
|
|
|||
BIN
hindsight-docs/static/img/icons/nodejs.png
Normal file
BIN
hindsight-docs/static/img/icons/nodejs.png
Normal file
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 |
|
|
@ -14,4 +14,4 @@ echo ""
|
|||
echo "Starting Docusaurus development server..."
|
||||
echo "Documentation will be available at: http://localhost:3000"
|
||||
echo ""
|
||||
npm run start -w hindsight-docs
|
||||
npm run start -w hindsight-docs -- --no-open
|
||||
|
|
|
|||
Loading…
Reference in a new issue