From 3d87ef5cee2b2c02bbbbf3b6bff1627f68017574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Tue, 3 Mar 2026 18:46:59 +0100 Subject: [PATCH] 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. --- .github/workflows/test.yml | 24 +- hindsight-dev/hindsight_dev/sync_cookbook.py | 217 ++++++++-------- .../src/components/CookbookGrid.module.css | 82 ++++++ .../src/components/CookbookGrid.tsx | 95 ++++++- hindsight-docs/src/css/custom.css | 2 +- .../pages/cookbook/applications/cable-co.md | 143 +++++++++++ .../applications/chat-memory-cloud.md | 122 +++++++++ .../cookbook/applications/chat-memory.md | 2 +- .../applications/chat-sdk-multi-platform.md | 2 +- .../pages/cookbook/applications/claims-iq.md | 81 ++++++ .../cookbook/applications/crewai-memory.md | 2 +- .../cookbook/applications/deliveryman-demo.md | 2 +- .../applications/go-memory-service.md | 2 +- .../applications/hindsight-litellm-demo.md | 2 +- .../hindsight-tool-learning-demo.md | 2 +- .../applications/openai-fitness-coach.md | 2 +- .../applications/pydantic-ai-memory.md | 236 ++++++++++++++++++ .../applications/sanity-blog-memory.md | 2 +- .../cookbook/applications/stancetracker.md | 2 +- .../pages/cookbook/applications/taste-ai.md | 2 +- hindsight-docs/src/pages/cookbook/index.mdx | 38 ++- hindsight-docs/static/img/icons/nodejs.png | Bin 0 -> 969 bytes hindsight-docs/static/img/icons/nodejs.svg | 1 - scripts/dev/start-docs.sh | 2 +- 24 files changed, 915 insertions(+), 150 deletions(-) create mode 100644 hindsight-docs/src/pages/cookbook/applications/cable-co.md create mode 100644 hindsight-docs/src/pages/cookbook/applications/chat-memory-cloud.md create mode 100644 hindsight-docs/src/pages/cookbook/applications/claims-iq.md create mode 100644 hindsight-docs/src/pages/cookbook/applications/pydantic-ai-memory.md create mode 100644 hindsight-docs/static/img/icons/nodejs.png delete mode 100644 hindsight-docs/static/img/icons/nodejs.svg diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f574903e..c18d8691 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/hindsight-dev/hindsight_dev/sync_cookbook.py b/hindsight-dev/hindsight_dev/sync_cookbook.py index 7794264c..f7b5809f 100644 --- a/hindsight-dev/hindsight_dev/sync_cookbook.py +++ b/hindsight-dev/hindsight_dev/sync_cookbook.py @@ -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'; -
+
-# Cookbook +
+

Cookbook

+

+ Practical examples and complete applications built with Hindsight. +

+
-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 - - -; +} + function Card({title, href, description, tags}: CookbookCard) { return ( @@ -25,7 +46,12 @@ function Card({title, href, description, tags}: CookbookCard) { {(tags?.topic || tags?.sdk) && (
{tags.topic && {tags.topic}} - {tags.sdk && {tags.sdk}} + {tags.sdk && ( + + + {tags.sdk} + + )}
)}
@@ -34,11 +60,68 @@ function Card({title, href, description, tags}: CookbookCard) { } export default function CookbookGrid({items}: CookbookGridProps) { + const [selectedTopic, setSelectedTopic] = useState(null); + const [selectedSdk, setSelectedSdk] = useState(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 ( -
- {items.map((item) => ( - - ))} +
+ {hasFilters && ( +
+ {topics.length > 1 && ( +
+ Topic + + {topics.map((topic) => ( + + ))} +
+ )} + {sdks.length > 1 && ( +
+ SDK + + {sdks.map((sdk) => ( + + ))} +
+ )} +
+ )} +
+ {filtered.map((item) => ( + + ))} +
); } diff --git a/hindsight-docs/src/css/custom.css b/hindsight-docs/src/css/custom.css index 9ef4a8a8..9eccdff7 100644 --- a/hindsight-docs/src/css/custom.css +++ b/hindsight-docs/src/css/custom.css @@ -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 */ diff --git a/hindsight-docs/src/pages/cookbook/applications/cable-co.md b/hindsight-docs/src/pages/cookbook/applications/cable-co.md new file mode 100644 index 00000000..1666f2e7 --- /dev/null +++ b/hindsight-docs/src/pages/cookbook/applications/cable-co.md @@ -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. diff --git a/hindsight-docs/src/pages/cookbook/applications/chat-memory-cloud.md b/hindsight-docs/src/pages/cookbook/applications/chat-memory-cloud.md new file mode 100644 index 00000000..c8ac2aab --- /dev/null +++ b/hindsight-docs/src/pages/cookbook/applications/chat-memory-cloud.md @@ -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 | diff --git a/hindsight-docs/src/pages/cookbook/applications/chat-memory.md b/hindsight-docs/src/pages/cookbook/applications/chat-memory.md index cf36aa9e..d5e191ab 100644 --- a/hindsight-docs/src/pages/cookbook/applications/chat-memory.md +++ b/hindsight-docs/src/pages/cookbook/applications/chat-memory.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Chat Memory App diff --git a/hindsight-docs/src/pages/cookbook/applications/chat-sdk-multi-platform.md b/hindsight-docs/src/pages/cookbook/applications/chat-sdk-multi-platform.md index 3f780aac..424f7808 100644 --- a/hindsight-docs/src/pages/cookbook/applications/chat-sdk-multi-platform.md +++ b/hindsight-docs/src/pages/cookbook/applications/chat-sdk-multi-platform.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 4 --- # Chat SDK Multi-Platform Bot diff --git a/hindsight-docs/src/pages/cookbook/applications/claims-iq.md b/hindsight-docs/src/pages/cookbook/applications/claims-iq.md new file mode 100644 index 00000000..ac61c3f9 --- /dev/null +++ b/hindsight-docs/src/pages/cookbook/applications/claims-iq.md @@ -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 | diff --git a/hindsight-docs/src/pages/cookbook/applications/crewai-memory.md b/hindsight-docs/src/pages/cookbook/applications/crewai-memory.md index b86a0c09..68e8cd3b 100644 --- a/hindsight-docs/src/pages/cookbook/applications/crewai-memory.md +++ b/hindsight-docs/src/pages/cookbook/applications/crewai-memory.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 6 --- # CrewAI + Hindsight Memory diff --git a/hindsight-docs/src/pages/cookbook/applications/deliveryman-demo.md b/hindsight-docs/src/pages/cookbook/applications/deliveryman-demo.md index 89aebfdd..f056ab8c 100644 --- a/hindsight-docs/src/pages/cookbook/applications/deliveryman-demo.md +++ b/hindsight-docs/src/pages/cookbook/applications/deliveryman-demo.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 7 --- # Deliveryman Demo diff --git a/hindsight-docs/src/pages/cookbook/applications/go-memory-service.md b/hindsight-docs/src/pages/cookbook/applications/go-memory-service.md index aae3b5f5..b55c6f7f 100644 --- a/hindsight-docs/src/pages/cookbook/applications/go-memory-service.md +++ b/hindsight-docs/src/pages/cookbook/applications/go-memory-service.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 8 --- # Go Memory-Augmented API diff --git a/hindsight-docs/src/pages/cookbook/applications/hindsight-litellm-demo.md b/hindsight-docs/src/pages/cookbook/applications/hindsight-litellm-demo.md index b9aec100..fb8216bc 100644 --- a/hindsight-docs/src/pages/cookbook/applications/hindsight-litellm-demo.md +++ b/hindsight-docs/src/pages/cookbook/applications/hindsight-litellm-demo.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 9 --- # Memory Approaches Comparison Demo diff --git a/hindsight-docs/src/pages/cookbook/applications/hindsight-tool-learning-demo.md b/hindsight-docs/src/pages/cookbook/applications/hindsight-tool-learning-demo.md index c9e16642..3c3a3547 100644 --- a/hindsight-docs/src/pages/cookbook/applications/hindsight-tool-learning-demo.md +++ b/hindsight-docs/src/pages/cookbook/applications/hindsight-tool-learning-demo.md @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 10 --- # Tool Learning Demo diff --git a/hindsight-docs/src/pages/cookbook/applications/openai-fitness-coach.md b/hindsight-docs/src/pages/cookbook/applications/openai-fitness-coach.md index 969c5db1..30a24c1e 100644 --- a/hindsight-docs/src/pages/cookbook/applications/openai-fitness-coach.md +++ b/hindsight-docs/src/pages/cookbook/applications/openai-fitness-coach.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 11 --- # OpenAI Agent + Hindsight Memory Integration diff --git a/hindsight-docs/src/pages/cookbook/applications/pydantic-ai-memory.md b/hindsight-docs/src/pages/cookbook/applications/pydantic-ai-memory.md new file mode 100644 index 00000000..26d67404 --- /dev/null +++ b/hindsight-docs/src/pages/cookbook/applications/pydantic-ai-memory.md @@ -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 diff --git a/hindsight-docs/src/pages/cookbook/applications/sanity-blog-memory.md b/hindsight-docs/src/pages/cookbook/applications/sanity-blog-memory.md index af4b0ed2..0fc6ef09 100644 --- a/hindsight-docs/src/pages/cookbook/applications/sanity-blog-memory.md +++ b/hindsight-docs/src/pages/cookbook/applications/sanity-blog-memory.md @@ -1,5 +1,5 @@ --- -sidebar_position: 9 +sidebar_position: 13 --- # Sanity CMS Blog Memory diff --git a/hindsight-docs/src/pages/cookbook/applications/stancetracker.md b/hindsight-docs/src/pages/cookbook/applications/stancetracker.md index c7648061..b80c220c 100644 --- a/hindsight-docs/src/pages/cookbook/applications/stancetracker.md +++ b/hindsight-docs/src/pages/cookbook/applications/stancetracker.md @@ -1,5 +1,5 @@ --- -sidebar_position: 10 +sidebar_position: 14 --- # Stance Tracker diff --git a/hindsight-docs/src/pages/cookbook/applications/taste-ai.md b/hindsight-docs/src/pages/cookbook/applications/taste-ai.md index a6aca6ad..8dbad64d 100644 --- a/hindsight-docs/src/pages/cookbook/applications/taste-ai.md +++ b/hindsight-docs/src/pages/cookbook/applications/taste-ai.md @@ -1,5 +1,5 @@ --- -sidebar_position: 11 +sidebar_position: 15 --- # Hindsight AI SDK - Personal Chef diff --git a/hindsight-docs/src/pages/cookbook/index.mdx b/hindsight-docs/src/pages/cookbook/index.mdx index e2a47aa7..16d10a9d 100644 --- a/hindsight-docs/src/pages/cookbook/index.mdx +++ b/hindsight-docs/src/pages/cookbook/index.mdx @@ -101,11 +101,23 @@ import CookbookGrid from '@site/src/components/CookbookGrid'; 7hyd5IAT7@6AB6dwZctBdP{E+g?x>Gu=J)y&70JFNqsun27*C~SEyxrZ# z4|+P}$0iK+cJa?F*{2L{IBUTA(>hEQK#$yN!QEPE!QUmqtR8cF#d32izn6Z3Y{Td8 zl=JIO>*bB-445u}1~!jwp{>#ay>E7}1atc(nB6V>p_jPYs&MiNxphvTc!7q`6yPfH zVC3yvppP?iKuU8vs{v7RFBieE_M|>z06lU&S82gz(}H0FahN@D1po&TcsoT$07jwG zLbKIEyD;2>uS)ya{l0`23g<1Y(?{AIlj7gcvbHEr7L`14{4Qi_#5%k_~|4jesL8ZGpb9 zBLHqvfP5{WY#pFXJ*{XMz=sJ9b_Nif2+;Z-pi&LsbOQ~ymcFPnfItOdt_@(o1a!Uw zsQq2DepCe@K?2JUs?GueHUPHwEkK3Zs&H#vMZqBe9tY6>rgiRZckv!&hJdQA253j0 zy#*C&{^RV0l3g*KgV>~LQNcn+449DGfbW_00q#mY?ytUu^ZG{^Ayh14dS3Z zK#UVuwtsp65+E0F2$vXOu4g71(pImcMC12%2>QD|6#)Gj<|25IO|TC!Qyo;S{TxTx z|3(A&FfqgBNdd$Z`G!lgn3>7El6&P&l&jl|L#S*a=CK z)i7jzInCcLUjguR@MX2VX)oy{w=!32P^o5_`GfVFp65pZv-?DF^`Rlx0C|zQCi4k% rBhJ@$`+LNT-d^rkV^^b}ndV#q;4BegzH`7_00000NkvXXu0mjfMykGq literal 0 HcmV?d00001 diff --git a/hindsight-docs/static/img/icons/nodejs.svg b/hindsight-docs/static/img/icons/nodejs.svg deleted file mode 100644 index 41d044ac..00000000 --- a/hindsight-docs/static/img/icons/nodejs.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/dev/start-docs.sh b/scripts/dev/start-docs.sh index 17a0662b..bb259ecd 100755 --- a/scripts/dev/start-docs.sh +++ b/scripts/dev/start-docs.sh @@ -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