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:
|
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
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 className={styles.grid}>
|
<div>
|
||||||
{items.map((item) => (
|
{hasFilters && (
|
||||||
<Card key={item.href} {...item} />
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
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
|
# Chat Memory App
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
# Chat SDK Multi-Platform Bot
|
# 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
|
# CrewAI + Hindsight Memory
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 4
|
sidebar_position: 7
|
||||||
---
|
---
|
||||||
|
|
||||||
# Deliveryman Demo
|
# Deliveryman Demo
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 5
|
sidebar_position: 8
|
||||||
---
|
---
|
||||||
|
|
||||||
# Go Memory-Augmented API
|
# Go Memory-Augmented API
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 6
|
sidebar_position: 9
|
||||||
---
|
---
|
||||||
|
|
||||||
# Memory Approaches Comparison Demo
|
# Memory Approaches Comparison Demo
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 7
|
sidebar_position: 10
|
||||||
---
|
---
|
||||||
|
|
||||||
# Tool Learning Demo
|
# Tool Learning Demo
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 8
|
sidebar_position: 11
|
||||||
---
|
---
|
||||||
|
|
||||||
# OpenAI Agent + Hindsight Memory Integration
|
# 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
|
# Sanity CMS Blog Memory
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 10
|
sidebar_position: 14
|
||||||
---
|
---
|
||||||
|
|
||||||
# Stance Tracker
|
# Stance Tracker
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
sidebar_position: 11
|
sidebar_position: 15
|
||||||
---
|
---
|
||||||
|
|
||||||
# Hindsight AI SDK - Personal Chef
|
# Hindsight AI SDK - Personal Chef
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
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 "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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue