Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdef0d80f4 | ||
|
|
7db1eb1b3a | ||
|
|
c96ae58c28 | ||
|
|
d349376df7 | ||
|
|
b403b94795 | ||
|
|
fe5d3a999b |
@@ -2,23 +2,11 @@
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# LLM Configuration (Required)
|
||||
# Supported providers: openai, groq, ollama, gemini, anthropic, lmstudio
|
||||
HINDSIGHT_API_LLM_PROVIDER=openai
|
||||
HINDSIGHT_API_LLM_API_KEY=your-api-key-here
|
||||
HINDSIGHT_API_LLM_MODEL=o3-mini
|
||||
HINDSIGHT_API_LLM_BASE_URL=https://api.openai.com/v1
|
||||
|
||||
# Example: Anthropic Claude configuration
|
||||
# HINDSIGHT_API_LLM_PROVIDER=anthropic
|
||||
# HINDSIGHT_API_LLM_API_KEY=your-anthropic-api-key
|
||||
# HINDSIGHT_API_LLM_MODEL=claude-sonnet-4-20250514
|
||||
|
||||
# Example: LM Studio local configuration (Qwen 2.5 32B recommended)
|
||||
# HINDSIGHT_API_LLM_PROVIDER=lmstudio
|
||||
# HINDSIGHT_API_LLM_API_KEY=lmstudio
|
||||
# HINDSIGHT_API_LLM_BASE_URL=http://localhost:1234/v1
|
||||
# HINDSIGHT_API_LLM_MODEL=qwen2.5-32b-instruct
|
||||
|
||||
# API Configuration (Optional)
|
||||
HINDSIGHT_API_HOST=0.0.0.0
|
||||
HINDSIGHT_API_PORT=8888
|
||||
|
||||
@@ -182,83 +182,6 @@ jobs:
|
||||
path: hindsight-cli/target/release/hindsight
|
||||
retention-days: 1
|
||||
|
||||
test-rust-cli:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-rust-cli
|
||||
env:
|
||||
HINDSIGHT_API_LLM_PROVIDER: groq
|
||||
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
|
||||
HINDSIGHT_API_URL: http://localhost:8888
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download CLI artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hindsight-cli
|
||||
path: /tmp/cli
|
||||
|
||||
- name: Make CLI executable
|
||||
run: chmod +x /tmp/cli/hindsight
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
prune-cache: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
|
||||
- name: Build API
|
||||
working-directory: ./hindsight-api
|
||||
run: uv build
|
||||
|
||||
- name: Install API dependencies
|
||||
working-directory: ./hindsight-api
|
||||
run: uv sync --no-install-project --index-strategy unsafe-best-match
|
||||
|
||||
- name: Create .env file
|
||||
run: |
|
||||
cat > .env << EOF
|
||||
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
|
||||
HINDSIGHT_API_LLM_API_KEY=${{ env.HINDSIGHT_API_LLM_API_KEY }}
|
||||
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
|
||||
EOF
|
||||
|
||||
- name: Start API server
|
||||
run: |
|
||||
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
|
||||
echo "Waiting for API server to be ready..."
|
||||
for i in {1..60}; do
|
||||
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
|
||||
echo "API server is ready after ${i}s"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 60 ]; then
|
||||
echo "API server failed to start after 60s"
|
||||
cat /tmp/api-server.log
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run CLI smoke test
|
||||
run: |
|
||||
HINDSIGHT_CLI=/tmp/cli/hindsight ./hindsight-cli/smoke-test.sh
|
||||
|
||||
- name: Show API server logs
|
||||
if: always()
|
||||
run: |
|
||||
echo "=== API Server Logs ==="
|
||||
cat /tmp/api-server.log || echo "No API server log found"
|
||||
|
||||
lint-helm-chart:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -613,97 +536,6 @@ jobs:
|
||||
echo "=== API Server Logs ==="
|
||||
cat /tmp/api-server.log || echo "No API server log found"
|
||||
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HINDSIGHT_API_LLM_PROVIDER: groq
|
||||
HINDSIGHT_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
HINDSIGHT_API_LLM_MODEL: openai/gpt-oss-20b
|
||||
HINDSIGHT_API_URL: http://localhost:8888
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
prune-cache: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: ".python-version"
|
||||
|
||||
- name: Build API
|
||||
working-directory: ./hindsight-api
|
||||
run: uv build
|
||||
|
||||
- name: Install API dependencies
|
||||
working-directory: ./hindsight-api
|
||||
run: uv sync --no-install-project --index-strategy unsafe-best-match
|
||||
|
||||
- name: Install integration test dependencies
|
||||
working-directory: ./hindsight-integration-tests
|
||||
run: uv sync
|
||||
|
||||
- name: Cache HuggingFace models
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/huggingface
|
||||
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api/pyproject.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-huggingface-
|
||||
|
||||
- name: Pre-download models
|
||||
working-directory: ./hindsight-api
|
||||
run: |
|
||||
uv run python -c "
|
||||
from sentence_transformers import SentenceTransformer, CrossEncoder
|
||||
print('Downloading embedding model...')
|
||||
SentenceTransformer('BAAI/bge-small-en-v1.5')
|
||||
print('Downloading cross-encoder model...')
|
||||
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
|
||||
print('Models downloaded successfully')
|
||||
"
|
||||
|
||||
- name: Create .env file
|
||||
run: |
|
||||
cat > .env << EOF
|
||||
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
|
||||
HINDSIGHT_API_LLM_API_KEY=${{ env.HINDSIGHT_API_LLM_API_KEY }}
|
||||
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
|
||||
EOF
|
||||
|
||||
- name: Start API server
|
||||
run: |
|
||||
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
|
||||
echo "Waiting for API server to be ready..."
|
||||
for i in {1..60}; do
|
||||
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
|
||||
echo "API server is ready after ${i}s"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 60 ]; then
|
||||
echo "API server failed to start after 60s"
|
||||
cat /tmp/api-server.log
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run integration tests
|
||||
working-directory: ./hindsight-integration-tests
|
||||
run: uv run pytest tests/ -v
|
||||
|
||||
- name: Show API server logs
|
||||
if: always()
|
||||
run: |
|
||||
echo "=== API Server Logs ==="
|
||||
cat /tmp/api-server.log || echo "No API server log found"
|
||||
|
||||
test-litellm-integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
+1
-3
@@ -12,10 +12,8 @@ wheels/
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Environment variables and local config
|
||||
# Environment variables
|
||||
.env
|
||||
docker-compose.yml
|
||||
docker-compose.override.yml
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Hindsight is an agent memory system that provides long-term memory for AI agents using biomimetic data structures. It stores memories as World facts, Experiences, Opinions, and Observations across memory banks.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### API Server (Python/FastAPI)
|
||||
```bash
|
||||
# Start API server (loads .env automatically)
|
||||
./scripts/dev/start-api.sh
|
||||
|
||||
# Run tests
|
||||
cd hindsight-api && uv run pytest tests/
|
||||
|
||||
# Run specific test file
|
||||
cd hindsight-api && uv run pytest tests/test_http_api_integration.py -v
|
||||
|
||||
# Lint
|
||||
cd hindsight-api && uv run ruff check .
|
||||
```
|
||||
|
||||
### Control Plane (Next.js)
|
||||
```bash
|
||||
./scripts/dev/start-control-plane.sh
|
||||
# Or manually:
|
||||
cd hindsight-control-plane && npm run dev
|
||||
```
|
||||
|
||||
### Documentation Site (Docusaurus)
|
||||
```bash
|
||||
./scripts/dev/start-docs.sh
|
||||
```
|
||||
|
||||
### Generating Clients/OpenAPI
|
||||
```bash
|
||||
# Regenerate OpenAPI spec after API changes
|
||||
./scripts/generate-openapi.sh
|
||||
|
||||
# Regenerate all client SDKs (Python, TypeScript, Rust)
|
||||
./scripts/generate-clients.sh
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
```bash
|
||||
./scripts/benchmarks/run-longmemeval.sh
|
||||
./scripts/benchmarks/run-locomo.sh
|
||||
./scripts/benchmarks/start-visualizer.sh # View results at localhost:8001
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Monorepo Structure
|
||||
- **hindsight-api/**: Core FastAPI server with memory engine (Python, uv)
|
||||
- **hindsight/**: Embedded Python bundle (hindsight-all package)
|
||||
- **hindsight-control-plane/**: Admin UI (Next.js, npm)
|
||||
- **hindsight-cli/**: CLI tool (Rust, cargo)
|
||||
- **hindsight-clients/**: Generated SDK clients (Python, TypeScript, Rust)
|
||||
- **hindsight-docs/**: Docusaurus documentation site
|
||||
- **hindsight-integrations/**: Framework integrations (LiteLLM, OpenAI)
|
||||
- **hindsight-dev/**: Development tools and benchmarks
|
||||
|
||||
### Core Engine (hindsight-api/hindsight_api/engine/)
|
||||
- `memory_engine.py`: Main orchestrator for retain/recall/reflect operations
|
||||
- `llm_wrapper.py`: LLM abstraction supporting OpenAI, Anthropic, Gemini, Groq, Ollama, LM Studio
|
||||
- `embeddings.py`: Embedding generation (local or TEI)
|
||||
- `cross_encoder.py`: Reranking (local or TEI)
|
||||
- `entity_resolver.py`: Entity extraction and normalization
|
||||
- `query_analyzer.py`: Query intent analysis
|
||||
- `retain/`: Memory ingestion pipeline
|
||||
- `search/`: Multi-strategy retrieval (semantic, BM25, graph, temporal)
|
||||
|
||||
### API Layer (hindsight-api/hindsight_api/api/)
|
||||
FastAPI routers for all endpoints. Main operations:
|
||||
- **Retain**: Store memories, extracts facts/entities/relationships
|
||||
- **Recall**: Retrieve memories via parallel search strategies + reranking
|
||||
- **Reflect**: Deep analysis forming new opinions/observations
|
||||
|
||||
### Database
|
||||
PostgreSQL with pgvector. Schema managed via Alembic migrations in `hindsight-api/hindsight_api/alembic/`. Migrations run automatically on API startup.
|
||||
|
||||
Key tables: `banks`, `memory_units`, `documents`, `entities`, `entity_links`
|
||||
|
||||
### Database Backups (IMPORTANT)
|
||||
**Before any operation that may affect the database, run a backup:**
|
||||
```bash
|
||||
docker exec hindsight /backups/backup.sh
|
||||
```
|
||||
|
||||
Operations requiring backup:
|
||||
- Running database migrations
|
||||
- Modifying Alembic migration files
|
||||
- Rebuilding Docker images
|
||||
- Resetting or recreating containers
|
||||
- Any schema changes
|
||||
- Bulk data operations
|
||||
|
||||
Backups are stored in `~/hindsight-backups/` on the host.
|
||||
|
||||
To restore:
|
||||
```bash
|
||||
docker exec -it hindsight /backups/restore.sh <backup-file.sql.gz>
|
||||
```
|
||||
|
||||
## Key Conventions
|
||||
|
||||
### Memory Banks
|
||||
- Each bank is isolated (no cross-bank data access)
|
||||
- Banks have dispositions (skepticism, literalism, empathy traits 1-5) affecting reflect
|
||||
- Banks can have background context
|
||||
|
||||
### API Design
|
||||
- All endpoints operate on a single bank per request
|
||||
- Multi-bank queries are client responsibility
|
||||
- Disposition traits only affect reflect, not recall
|
||||
|
||||
### Python Style
|
||||
- Python 3.11+, type hints required
|
||||
- Async throughout (asyncpg, async FastAPI)
|
||||
- Pydantic models for request/response
|
||||
- Ruff for linting (line-length 120)
|
||||
|
||||
### TypeScript Style
|
||||
- Next.js App Router for control plane
|
||||
- Tailwind CSS with shadcn/ui components
|
||||
|
||||
## Environment Setup
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with LLM API key
|
||||
|
||||
# Python deps
|
||||
uv sync --directory hindsight-api/
|
||||
|
||||
# Node deps (workspace)
|
||||
npm install
|
||||
```
|
||||
|
||||
Required env vars:
|
||||
- `HINDSIGHT_API_LLM_PROVIDER`: openai, anthropic, gemini, groq, ollama, lmstudio
|
||||
- `HINDSIGHT_API_LLM_API_KEY`: Your API key
|
||||
- `HINDSIGHT_API_LLM_MODEL`: Model name (e.g., o3-mini, claude-sonnet-4-20250514)
|
||||
+1
-30
@@ -51,36 +51,7 @@ cd hindsight-api
|
||||
uv run pytest tests/
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
We use [Ruff](https://docs.astral.sh/ruff/) for Python linting and formatting, and ESLint/Prettier for TypeScript.
|
||||
|
||||
#### Setting up git hooks (recommended)
|
||||
|
||||
Set up git hooks to automatically lint and format code before each commit:
|
||||
|
||||
```bash
|
||||
./scripts/setup-hooks.sh
|
||||
```
|
||||
|
||||
This configures git to use the hooks in `.githooks/`, which run all scripts in `scripts/hooks/` on commit. The lint hook runs in parallel:
|
||||
- **Python**: `ruff check --fix`, `ruff format`, `ty check`
|
||||
- **TypeScript**: `eslint --fix`, `prettier`
|
||||
|
||||
#### Manual linting and formatting
|
||||
|
||||
```bash
|
||||
# Run all lints (same as pre-commit)
|
||||
./scripts/hooks/lint.sh
|
||||
|
||||
# Or run individually for Python:
|
||||
cd hindsight-api
|
||||
uv run ruff check --fix . # Lint and auto-fix
|
||||
uv run ruff format . # Format code
|
||||
uv run ty check hindsight_api # Type check
|
||||
```
|
||||
|
||||
#### Style guidelines
|
||||
### Code style
|
||||
|
||||
- Use Python type hints
|
||||
- Follow existing code patterns
|
||||
|
||||
@@ -81,8 +81,6 @@ docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
|
||||
ghcr.io/vectorize-io/hindsight:latest
|
||||
```
|
||||
|
||||
You can modify the LLM provider by setting `HINDSIGHT_API_LLM_PROVIDER`. Valid options are `openai`, `anthropic`, `gemini`, `groq`, `ollama`, and `lmstudio`. The documentation provides more details on [supported models](https://hindsight.vectorize.io/developer/models).
|
||||
|
||||
API: http://localhost:8888
|
||||
UI: http://localhost:9999
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ FROM python:3.11-slim AS api-only
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install pg0 dependencies (procps provides 'kill' command needed by pg0)
|
||||
# Note: libicu version varies by Debian version - try common versions in order
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
@@ -137,6 +138,7 @@ RUN apt-get update && apt-get install -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir uv
|
||||
|
||||
# Create non-root user (PostgreSQL cannot run as root)
|
||||
RUN useradd -m -s /bin/bash hindsight
|
||||
|
||||
# Copy API with virtual environment from builder
|
||||
@@ -146,12 +148,20 @@ COPY --from=api-builder /app/api /app/api
|
||||
COPY docker/standalone/start-all.sh /app/start-all.sh
|
||||
RUN chmod +x /app/start-all.sh
|
||||
|
||||
RUN chown -R hindsight:hindsight /app
|
||||
# Create data directory for pg0 and set ownership
|
||||
RUN mkdir -p /app/data && chown -R hindsight:hindsight /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER hindsight
|
||||
|
||||
# Set PATH for hindsight user
|
||||
ENV PATH="/app/api/.venv/bin:${PATH}"
|
||||
|
||||
# Pre-cache PostgreSQL binaries by starting/stopping pg0-embedded
|
||||
ENV PG0_HOME=/home/hindsight/.pg0-cache
|
||||
|
||||
ENV PG0_HOME=/home/hindsight/.pg0
|
||||
|
||||
# Pre-download ML models to avoid runtime download (conditional)
|
||||
ARG PRELOAD_ML_MODELS
|
||||
RUN if [ "$PRELOAD_ML_MODELS" = "true" ]; then \
|
||||
@@ -216,7 +226,7 @@ FROM python:3.11-slim AS standalone
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install Node.js, curl, uv, and system dependencies
|
||||
# Install Node.js, curl, uv, and pg0 dependencies (procps provides 'kill' command needed by pg0)
|
||||
# Note: libicu version varies by Debian version - try common versions in order
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
@@ -231,6 +241,7 @@ RUN apt-get update && apt-get install -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir uv
|
||||
|
||||
# Create non-root user (PostgreSQL cannot run as root)
|
||||
RUN useradd -m -s /bin/bash hindsight
|
||||
|
||||
# Copy API with virtual environment from builder
|
||||
@@ -251,12 +262,27 @@ WORKDIR /app
|
||||
COPY docker/standalone/start-all.sh /app/start-all.sh
|
||||
RUN chmod +x /app/start-all.sh
|
||||
|
||||
RUN chown -R hindsight:hindsight /app
|
||||
# Create data directory for pg0 and set ownership
|
||||
RUN mkdir -p /app/data && chown -R hindsight:hindsight /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER hindsight
|
||||
|
||||
# Set PATH for hindsight user
|
||||
ENV PATH="/app/api/.venv/bin:${PATH}"
|
||||
|
||||
# Pre-cache PostgreSQL binaries by starting/stopping pg0-embedded
|
||||
ENV PG0_HOME=/home/hindsight/.pg0-cache
|
||||
RUN /app/api/.venv/bin/python -c "\
|
||||
from pg0 import Pg0; \
|
||||
print('Pre-caching PostgreSQL binaries...'); \
|
||||
pg = Pg0(name='hindsight', port=5555, username='hindsight', password='hindsight', database='hindsight'); \
|
||||
pg.start(); \
|
||||
pg.stop(); \
|
||||
print('PostgreSQL pre-cached to PG0_HOME')" || echo "Pre-download skipped"
|
||||
|
||||
ENV PG0_HOME=/home/hindsight/.pg0
|
||||
|
||||
# Pre-download ML models to avoid runtime download (conditional)
|
||||
ARG PRELOAD_ML_MODELS
|
||||
RUN if [ "$PRELOAD_ML_MODELS" = "true" ]; then \
|
||||
|
||||
@@ -5,70 +5,16 @@ set -e
|
||||
ENABLE_API="${HINDSIGHT_ENABLE_API:-true}"
|
||||
ENABLE_CP="${HINDSIGHT_ENABLE_CP:-true}"
|
||||
|
||||
# =============================================================================
|
||||
# Dependency waiting (opt-in via HINDSIGHT_WAIT_FOR_DEPS=true)
|
||||
#
|
||||
# Problem: When running with LM Studio, the LLM may take time to load models.
|
||||
# If Hindsight starts before LM Studio is ready, it fails on LLM verification.
|
||||
# This wait loop ensures dependencies are ready before starting.
|
||||
# =============================================================================
|
||||
if [ "${HINDSIGHT_WAIT_FOR_DEPS:-false}" = "true" ]; then
|
||||
LLM_BASE_URL="${HINDSIGHT_API_LLM_BASE_URL:-http://host.docker.internal:1234/v1}"
|
||||
MAX_RETRIES="${HINDSIGHT_RETRY_MAX:-0}" # 0 = infinite
|
||||
RETRY_INTERVAL="${HINDSIGHT_RETRY_INTERVAL:-10}"
|
||||
|
||||
# Check if external database is configured (skip check for embedded pg0)
|
||||
SKIP_DB_CHECK=false
|
||||
if [ -z "${HINDSIGHT_API_DATABASE_URL}" ]; then
|
||||
SKIP_DB_CHECK=true
|
||||
else
|
||||
DB_CHECK_HOST=$(echo "$HINDSIGHT_API_DATABASE_URL" | sed -E 's|.*@([^:/]+):([0-9]+)/.*|\1 \2|')
|
||||
# Copy pre-cached PostgreSQL data if runtime directory is empty (first run with volume)
|
||||
if [ "$ENABLE_API" = "true" ]; then
|
||||
PG0_CACHE="/home/hindsight/.pg0-cache"
|
||||
PG0_HOME="/home/hindsight/.pg0"
|
||||
if [ -d "$PG0_CACHE" ] && [ "$(ls -A $PG0_CACHE 2>/dev/null)" ]; then
|
||||
if [ ! "$(ls -A $PG0_HOME 2>/dev/null)" ]; then
|
||||
echo "📦 Copying pre-cached PostgreSQL data..."
|
||||
cp -r "$PG0_CACHE"/* "$PG0_HOME"/ 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
check_db() {
|
||||
if $SKIP_DB_CHECK; then
|
||||
return 0
|
||||
fi
|
||||
if command -v pg_isready &> /dev/null; then
|
||||
pg_isready -h $(echo $DB_CHECK_HOST | cut -d' ' -f1) -p $(echo $DB_CHECK_HOST | cut -d' ' -f2) &>/dev/null
|
||||
else
|
||||
python3 -c "import socket; s=socket.socket(); s.settimeout(5); exit(0 if s.connect_ex(('$(echo $DB_CHECK_HOST | cut -d' ' -f1)', $(echo $DB_CHECK_HOST | cut -d' ' -f2))) == 0 else 1)" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
check_llm() {
|
||||
curl -sf "${LLM_BASE_URL}/models" --connect-timeout 5 &>/dev/null
|
||||
}
|
||||
|
||||
echo "⏳ Waiting for dependencies to be ready..."
|
||||
attempt=1
|
||||
|
||||
while true; do
|
||||
db_ok=false
|
||||
llm_ok=false
|
||||
|
||||
if check_db; then
|
||||
db_ok=true
|
||||
fi
|
||||
|
||||
if check_llm; then
|
||||
llm_ok=true
|
||||
fi
|
||||
|
||||
if $db_ok && $llm_ok; then
|
||||
echo "✅ Dependencies ready!"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$MAX_RETRIES" -ne 0 ] && [ "$attempt" -ge "$MAX_RETRIES" ]; then
|
||||
echo "❌ Max retries ($MAX_RETRIES) reached. Dependencies not available."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Attempt $attempt: DB=$( $db_ok && echo 'ok' || echo 'waiting' ), LLM=$( $llm_ok && echo 'ok' || echo 'waiting' )"
|
||||
sleep "$RETRY_INTERVAL"
|
||||
((attempt++))
|
||||
done
|
||||
fi
|
||||
|
||||
# Track PIDs for wait
|
||||
|
||||
@@ -2,8 +2,8 @@ apiVersion: v2
|
||||
name: hindsight
|
||||
description: Hindsight helm chart
|
||||
type: application
|
||||
version: 0.1.16
|
||||
appVersion: "0.1.16"
|
||||
version: 0.1.12
|
||||
appVersion: "0.1.12"
|
||||
keywords:
|
||||
- ai
|
||||
- memory
|
||||
|
||||
@@ -80,7 +80,7 @@ Configure via environment variables:
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `HINDSIGHT_API_DATABASE_URL` | PostgreSQL connection string | `pg0` (embedded) |
|
||||
| `HINDSIGHT_API_LLM_PROVIDER` | `openai`, `anthropic`, `gemini`, `groq`, `ollama`, `lmstudio` | `openai` |
|
||||
| `HINDSIGHT_API_LLM_PROVIDER` | `openai`, `groq`, `gemini`, `ollama` | `openai` |
|
||||
| `HINDSIGHT_API_LLM_API_KEY` | API key for LLM provider | - |
|
||||
| `HINDSIGHT_API_LLM_MODEL` | Model name | `gpt-4o-mini` |
|
||||
| `HINDSIGHT_API_HOST` | Server bind address | `0.0.0.0` |
|
||||
|
||||
@@ -5,7 +5,6 @@ Provides both HTTP REST API and MCP (Model Context Protocol) server.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -46,18 +45,6 @@ def create_app(
|
||||
# Both HTTP and MCP
|
||||
app = create_app(memory, mcp_api_enabled=True)
|
||||
"""
|
||||
mcp_app = None
|
||||
|
||||
# Create MCP app first if enabled (we need its lifespan for chaining)
|
||||
if mcp_api_enabled:
|
||||
try:
|
||||
from .mcp import create_mcp_app
|
||||
|
||||
mcp_app = create_mcp_app(memory=memory)
|
||||
except ImportError as e:
|
||||
logger.error(f"MCP server requested but dependencies not available: {e}")
|
||||
logger.error("Install with: pip install hindsight-api[mcp]")
|
||||
raise
|
||||
|
||||
# Import and create HTTP API if enabled
|
||||
if http_api_enabled:
|
||||
@@ -70,31 +57,20 @@ def create_app(
|
||||
app = FastAPI(title="Hindsight API", version="0.0.7")
|
||||
logger.info("HTTP REST API disabled")
|
||||
|
||||
# Mount MCP server and chain its lifespan if enabled
|
||||
if mcp_app is not None:
|
||||
# Get the MCP app's underlying Starlette app for lifespan access
|
||||
mcp_starlette_app = mcp_app.mcp_app
|
||||
# Mount MCP server if enabled
|
||||
if mcp_api_enabled:
|
||||
try:
|
||||
from .mcp import create_mcp_app
|
||||
|
||||
# Store the original lifespan
|
||||
original_lifespan = app.router.lifespan_context
|
||||
|
||||
@asynccontextmanager
|
||||
async def chained_lifespan(app_instance: FastAPI):
|
||||
"""Chain the MCP lifespan with the main app lifespan."""
|
||||
# Start MCP lifespan first
|
||||
async with mcp_starlette_app.router.lifespan_context(mcp_starlette_app):
|
||||
logger.info("MCP lifespan started")
|
||||
# Then start the original app lifespan
|
||||
async with original_lifespan(app_instance):
|
||||
yield
|
||||
logger.info("MCP lifespan stopped")
|
||||
|
||||
# Replace the app's lifespan with the chained version
|
||||
app.router.lifespan_context = chained_lifespan
|
||||
|
||||
# Mount the MCP middleware
|
||||
app.mount(mcp_mount_path, mcp_app)
|
||||
logger.info(f"MCP server enabled at {mcp_mount_path}/")
|
||||
# Create MCP app with dynamic bank_id support
|
||||
# Supports: /mcp/{bank_id}/sse (bank-specific SSE endpoint)
|
||||
mcp_app = create_mcp_app(memory=memory)
|
||||
app.mount(mcp_mount_path, mcp_app)
|
||||
logger.info(f"MCP server enabled at {mcp_mount_path}/{{bank_id}}/sse")
|
||||
except ImportError as e:
|
||||
logger.error(f"MCP server requested but dependencies not available: {e}")
|
||||
logger.error("Install with: pip install hindsight-api[mcp]")
|
||||
raise
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ from typing import Any
|
||||
|
||||
from fastapi import Depends, FastAPI, Header, HTTPException, Query
|
||||
|
||||
from hindsight_api.extensions import AuthenticationError
|
||||
|
||||
|
||||
def _parse_metadata(metadata: Any) -> dict[str, Any]:
|
||||
"""Parse metadata that may be a dict, JSON string, or None."""
|
||||
@@ -31,13 +29,13 @@ def _parse_metadata(metadata: Any) -> dict[str, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from hindsight_api import MemoryEngine
|
||||
from hindsight_api.engine.db_utils import acquire_with_retry
|
||||
from hindsight_api.engine.memory_engine import Budget, fq_table
|
||||
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
|
||||
from hindsight_api.extensions import HttpExtension, OperationValidationError, load_extension
|
||||
from hindsight_api.extensions import HttpExtension, load_extension
|
||||
from hindsight_api.metrics import create_metrics_collector, get_metrics_collector, initialize_metrics
|
||||
from hindsight_api.models import RequestContext
|
||||
|
||||
@@ -281,13 +279,6 @@ class RecallResponse(BaseModel):
|
||||
chunks: dict[str, ChunkData] | None = Field(default=None, description="Chunks for facts, keyed by chunk_id")
|
||||
|
||||
|
||||
class EntityInput(BaseModel):
|
||||
"""Entity to associate with retained content."""
|
||||
|
||||
text: str = Field(description="The entity name/text")
|
||||
type: str | None = Field(default=None, description="Optional entity type (e.g., 'PERSON', 'ORG', 'CONCEPT')")
|
||||
|
||||
|
||||
class MemoryItem(BaseModel):
|
||||
"""Single memory item for retain."""
|
||||
|
||||
@@ -299,9 +290,8 @@ class MemoryItem(BaseModel):
|
||||
"context": "team meeting",
|
||||
"metadata": {"source": "slack", "channel": "engineering"},
|
||||
"document_id": "meeting_notes_2024_01_15",
|
||||
"entities": [{"text": "Alice"}, {"text": "ML model", "type": "CONCEPT"}],
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
content: str
|
||||
@@ -309,27 +299,6 @@ class MemoryItem(BaseModel):
|
||||
context: str | None = None
|
||||
metadata: dict[str, str] | None = None
|
||||
document_id: str | None = Field(default=None, description="Optional document ID for this memory item.")
|
||||
entities: list[EntityInput] | None = Field(
|
||||
default=None,
|
||||
description="Optional entities to combine with auto-extracted entities.",
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before")
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
if v is None or v == "":
|
||||
return None
|
||||
if isinstance(v, datetime):
|
||||
return v
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
# Try parsing as ISO format
|
||||
return datetime.fromisoformat(v.replace("Z", "+00:00"))
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
f"Invalid timestamp/event_date format: '{v}'. Expected ISO format like '2024-01-15T10:30:00' or '2024-01-15T10:30:00Z'"
|
||||
) from e
|
||||
raise ValueError(f"timestamp must be a string or datetime, got {type(v).__name__}")
|
||||
|
||||
|
||||
class RetainRequest(BaseModel):
|
||||
@@ -399,16 +368,7 @@ class ReflectRequest(BaseModel):
|
||||
"query": "What do you think about artificial intelligence?",
|
||||
"budget": "low",
|
||||
"context": "This is for a research paper on AI ethics",
|
||||
"max_tokens": 4096,
|
||||
"include": {"facts": {}},
|
||||
"response_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"summary": {"type": "string"},
|
||||
"key_points": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"required": ["summary", "key_points"],
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -416,14 +376,9 @@ class ReflectRequest(BaseModel):
|
||||
query: str
|
||||
budget: Budget = Budget.LOW
|
||||
context: str | None = None
|
||||
max_tokens: int = Field(default=4096, description="Maximum tokens for the response")
|
||||
include: ReflectIncludeOptions = Field(
|
||||
default_factory=ReflectIncludeOptions, description="Options for including additional data (disabled by default)"
|
||||
)
|
||||
response_schema: dict | None = Field(
|
||||
default=None,
|
||||
description="Optional JSON Schema for structured output. When provided, the response will include a 'structured_output' field with the LLM response parsed according to this schema.",
|
||||
)
|
||||
|
||||
|
||||
class OpinionItem(BaseModel):
|
||||
@@ -468,20 +423,12 @@ class ReflectResponse(BaseModel):
|
||||
{"id": "123", "text": "AI is used in healthcare", "type": "world"},
|
||||
{"id": "456", "text": "I discussed AI applications last week", "type": "experience"},
|
||||
],
|
||||
"structured_output": {
|
||||
"summary": "AI is transformative",
|
||||
"key_points": ["Used in healthcare", "Discussed recently"],
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
text: str
|
||||
based_on: list[ReflectFact] = [] # Facts used to generate the response
|
||||
structured_output: dict | None = Field(
|
||||
default=None,
|
||||
description="Structured output parsed according to the request's response_schema. Only present when response_schema was provided in the request.",
|
||||
)
|
||||
|
||||
|
||||
class BanksResponse(BaseModel):
|
||||
@@ -725,26 +672,6 @@ class DocumentResponse(BaseModel):
|
||||
memory_unit_count: int
|
||||
|
||||
|
||||
class DeleteDocumentResponse(BaseModel):
|
||||
"""Response model for delete document endpoint."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Document 'session_1' and 5 associated memory units deleted successfully",
|
||||
"document_id": "session_1",
|
||||
"memory_units_deleted": 5,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
success: bool
|
||||
message: str
|
||||
document_id: str
|
||||
memory_units_deleted: int
|
||||
|
||||
|
||||
class ChunkResponse(BaseModel):
|
||||
"""Response model for get chunk endpoint."""
|
||||
|
||||
@@ -781,108 +708,6 @@ class DeleteResponse(BaseModel):
|
||||
deleted_count: int | None = None
|
||||
|
||||
|
||||
class BankStatsResponse(BaseModel):
|
||||
"""Response model for bank statistics endpoint."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"bank_id": "user123",
|
||||
"total_nodes": 150,
|
||||
"total_links": 300,
|
||||
"total_documents": 10,
|
||||
"nodes_by_fact_type": {"fact": 100, "preference": 30, "observation": 20},
|
||||
"links_by_link_type": {"temporal": 150, "semantic": 100, "entity": 50},
|
||||
"links_by_fact_type": {"fact": 200, "preference": 60, "observation": 40},
|
||||
"links_breakdown": {"fact": {"temporal": 100, "semantic": 60, "entity": 40}},
|
||||
"pending_operations": 2,
|
||||
"failed_operations": 0,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
bank_id: str
|
||||
total_nodes: int
|
||||
total_links: int
|
||||
total_documents: int
|
||||
nodes_by_fact_type: dict[str, int]
|
||||
links_by_link_type: dict[str, int]
|
||||
links_by_fact_type: dict[str, int]
|
||||
links_breakdown: dict[str, dict[str, int]]
|
||||
pending_operations: int
|
||||
failed_operations: int
|
||||
|
||||
|
||||
class OperationResponse(BaseModel):
|
||||
"""Response model for a single async operation."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"task_type": "retain",
|
||||
"items_count": 5,
|
||||
"document_id": "meeting-notes-2024",
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"status": "pending",
|
||||
"error_message": None,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
id: str
|
||||
task_type: str
|
||||
items_count: int
|
||||
document_id: str | None
|
||||
created_at: str
|
||||
status: str
|
||||
error_message: str | None
|
||||
|
||||
|
||||
class OperationsListResponse(BaseModel):
|
||||
"""Response model for list operations endpoint."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"bank_id": "user123",
|
||||
"operations": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"task_type": "retain",
|
||||
"items_count": 5,
|
||||
"document_id": None,
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"status": "pending",
|
||||
"error_message": None,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
bank_id: str
|
||||
operations: list[OperationResponse]
|
||||
|
||||
|
||||
class CancelOperationResponse(BaseModel):
|
||||
"""Response model for cancel operation endpoint."""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Operation 550e8400-e29b-41d4-a716-446655440000 cancelled",
|
||||
"operation_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
success: bool
|
||||
message: str
|
||||
operation_id: str
|
||||
|
||||
|
||||
def create_app(
|
||||
memory: MemoryEngine,
|
||||
initialize_memory: bool = True,
|
||||
@@ -1003,16 +828,6 @@ def _register_routes(app: FastAPI):
|
||||
api_key = authorization.strip()
|
||||
return RequestContext(api_key=api_key)
|
||||
|
||||
# Global exception handler for authentication errors
|
||||
@app.exception_handler(AuthenticationError)
|
||||
async def authentication_error_handler(request, exc: AuthenticationError):
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
return JSONResponse(
|
||||
status_code=401,
|
||||
content={"detail": str(exc)},
|
||||
)
|
||||
|
||||
@app.get(
|
||||
"/health",
|
||||
summary="Health check endpoint",
|
||||
@@ -1060,8 +875,6 @@ def _register_routes(app: FastAPI):
|
||||
try:
|
||||
data = await app.state.memory.get_graph_data(bank_id, type, request_context=request_context)
|
||||
return data
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1108,8 +921,6 @@ def _register_routes(app: FastAPI):
|
||||
request_context=request_context,
|
||||
)
|
||||
return data
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1226,10 +1037,6 @@ def _register_routes(app: FastAPI):
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except OperationValidationError as e:
|
||||
raise HTTPException(status_code=e.status_code, detail=e.reason)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1265,8 +1072,6 @@ def _register_routes(app: FastAPI):
|
||||
query=request.query,
|
||||
budget=request.budget,
|
||||
context=request.context,
|
||||
max_tokens=request.max_tokens,
|
||||
response_schema=request.response_schema,
|
||||
request_context=request_context,
|
||||
)
|
||||
|
||||
@@ -1289,13 +1094,8 @@ def _register_routes(app: FastAPI):
|
||||
return ReflectResponse(
|
||||
text=core_result.text,
|
||||
based_on=based_on_facts,
|
||||
structured_output=core_result.structured_output,
|
||||
)
|
||||
|
||||
except OperationValidationError as e:
|
||||
raise HTTPException(status_code=e.status_code, detail=e.reason)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1316,8 +1116,6 @@ def _register_routes(app: FastAPI):
|
||||
try:
|
||||
banks = await app.state.memory.list_banks(request_context=request_context)
|
||||
return BankListResponse(banks=banks)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1327,7 +1125,6 @@ def _register_routes(app: FastAPI):
|
||||
|
||||
@app.get(
|
||||
"/v1/default/banks/{bank_id}/stats",
|
||||
response_model=BankStatsResponse,
|
||||
summary="Get statistics for memory bank",
|
||||
description="Get statistics about nodes and links for a specific agent",
|
||||
operation_id="get_agent_stats",
|
||||
@@ -1428,21 +1225,19 @@ def _register_routes(app: FastAPI):
|
||||
total_nodes = sum(nodes_by_type.values())
|
||||
total_links = sum(links_by_type.values())
|
||||
|
||||
return BankStatsResponse(
|
||||
bank_id=bank_id,
|
||||
total_nodes=total_nodes,
|
||||
total_links=total_links,
|
||||
total_documents=total_documents,
|
||||
nodes_by_fact_type=nodes_by_type,
|
||||
links_by_link_type=links_by_type,
|
||||
links_by_fact_type=links_by_fact_type,
|
||||
links_breakdown=links_breakdown,
|
||||
pending_operations=pending_operations,
|
||||
failed_operations=failed_operations,
|
||||
)
|
||||
return {
|
||||
"bank_id": bank_id,
|
||||
"total_nodes": total_nodes,
|
||||
"total_links": total_links,
|
||||
"total_documents": total_documents,
|
||||
"nodes_by_fact_type": nodes_by_type,
|
||||
"links_by_link_type": links_by_type,
|
||||
"links_by_fact_type": links_by_fact_type,
|
||||
"links_breakdown": links_breakdown,
|
||||
"pending_operations": pending_operations,
|
||||
"failed_operations": failed_operations,
|
||||
}
|
||||
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1467,8 +1262,6 @@ def _register_routes(app: FastAPI):
|
||||
try:
|
||||
entities = await app.state.memory.list_entities(bank_id, limit=limit, request_context=request_context)
|
||||
return EntityListResponse(items=[EntityListItem(**e) for e in entities])
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1506,7 +1299,7 @@ def _register_routes(app: FastAPI):
|
||||
for obs in entity["observations"]
|
||||
],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
@@ -1559,7 +1352,7 @@ def _register_routes(app: FastAPI):
|
||||
for obs in entity["observations"]
|
||||
],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
@@ -1597,8 +1390,6 @@ def _register_routes(app: FastAPI):
|
||||
bank_id=bank_id, search_query=q, limit=limit, offset=offset, request_context=request_context
|
||||
)
|
||||
return data
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1607,7 +1398,7 @@ def _register_routes(app: FastAPI):
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.get(
|
||||
"/v1/default/banks/{bank_id}/documents/{document_id:path}",
|
||||
"/v1/default/banks/{bank_id}/documents/{document_id}",
|
||||
response_model=DocumentResponse,
|
||||
summary="Get document details",
|
||||
description="Get a specific document including its original text",
|
||||
@@ -1629,7 +1420,7 @@ def _register_routes(app: FastAPI):
|
||||
if not document:
|
||||
raise HTTPException(status_code=404, detail="Document not found")
|
||||
return document
|
||||
except (AuthenticationError, HTTPException):
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
@@ -1639,7 +1430,7 @@ def _register_routes(app: FastAPI):
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.get(
|
||||
"/v1/default/chunks/{chunk_id:path}",
|
||||
"/v1/default/chunks/{chunk_id}",
|
||||
response_model=ChunkResponse,
|
||||
summary="Get chunk details",
|
||||
description="Get a specific chunk by its ID",
|
||||
@@ -1658,7 +1449,7 @@ def _register_routes(app: FastAPI):
|
||||
if not chunk:
|
||||
raise HTTPException(status_code=404, detail="Chunk not found")
|
||||
return chunk
|
||||
except (AuthenticationError, HTTPException):
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
@@ -1668,8 +1459,7 @@ def _register_routes(app: FastAPI):
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.delete(
|
||||
"/v1/default/banks/{bank_id}/documents/{document_id:path}",
|
||||
response_model=DeleteDocumentResponse,
|
||||
"/v1/default/banks/{bank_id}/documents/{document_id}",
|
||||
summary="Delete a document",
|
||||
description="Delete a document and all its associated memory units and links.\n\n"
|
||||
"This will cascade delete:\n"
|
||||
@@ -1696,13 +1486,13 @@ def _register_routes(app: FastAPI):
|
||||
if result["document_deleted"] == 0:
|
||||
raise HTTPException(status_code=404, detail="Document not found")
|
||||
|
||||
return DeleteDocumentResponse(
|
||||
success=True,
|
||||
message=f"Document '{document_id}' and {result['memory_units_deleted']} associated memory units deleted successfully",
|
||||
document_id=document_id,
|
||||
memory_units_deleted=result["memory_units_deleted"],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Document '{document_id}' and {result['memory_units_deleted']} associated memory units deleted successfully",
|
||||
"document_id": document_id,
|
||||
"memory_units_deleted": result["memory_units_deleted"],
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
@@ -1713,7 +1503,6 @@ def _register_routes(app: FastAPI):
|
||||
|
||||
@app.get(
|
||||
"/v1/default/banks/{bank_id}/operations",
|
||||
response_model=OperationsListResponse,
|
||||
summary="List async operations",
|
||||
description="Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations",
|
||||
operation_id="list_operations",
|
||||
@@ -1723,12 +1512,10 @@ def _register_routes(app: FastAPI):
|
||||
"""List all async operations (pending and failed) for a memory bank."""
|
||||
try:
|
||||
operations = await app.state.memory.list_operations(bank_id, request_context=request_context)
|
||||
return OperationsListResponse(
|
||||
bank_id=bank_id,
|
||||
operations=[OperationResponse(**op) for op in operations],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
return {
|
||||
"bank_id": bank_id,
|
||||
"operations": operations,
|
||||
}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1738,7 +1525,6 @@ def _register_routes(app: FastAPI):
|
||||
|
||||
@app.delete(
|
||||
"/v1/default/banks/{bank_id}/operations/{operation_id}",
|
||||
response_model=CancelOperationResponse,
|
||||
summary="Cancel a pending async operation",
|
||||
description="Cancel a pending async operation by removing it from the queue",
|
||||
operation_id="cancel_operation",
|
||||
@@ -1756,11 +1542,9 @@ def _register_routes(app: FastAPI):
|
||||
raise HTTPException(status_code=400, detail=f"Invalid operation_id format: {operation_id}")
|
||||
|
||||
result = await app.state.memory.cancel_operation(bank_id, operation_id, request_context=request_context)
|
||||
return CancelOperationResponse(**result)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1792,8 +1576,6 @@ def _register_routes(app: FastAPI):
|
||||
disposition=DispositionTraits(**disposition_dict),
|
||||
background=profile["background"],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1832,8 +1614,6 @@ def _register_routes(app: FastAPI):
|
||||
disposition=DispositionTraits(**disposition_dict),
|
||||
background=profile["background"],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1863,8 +1643,6 @@ def _register_routes(app: FastAPI):
|
||||
response.disposition = DispositionTraits(**result["disposition"])
|
||||
|
||||
return response
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1916,8 +1694,6 @@ def _register_routes(app: FastAPI):
|
||||
disposition=DispositionTraits(**disposition_dict),
|
||||
background=final_profile["background"],
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1945,8 +1721,6 @@ def _register_routes(app: FastAPI):
|
||||
+ result.get("entities_deleted", 0)
|
||||
+ result.get("documents_deleted", 0),
|
||||
)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -1998,8 +1772,6 @@ def _register_routes(app: FastAPI):
|
||||
content_dict["metadata"] = item.metadata
|
||||
if item.document_id:
|
||||
content_dict["document_id"] = item.document_id
|
||||
if item.entities:
|
||||
content_dict["entities"] = [{"text": e.text, "type": e.type or "CONCEPT"} for e in item.entities]
|
||||
contents.append(content_dict)
|
||||
|
||||
if request.async_:
|
||||
@@ -2023,10 +1795,6 @@ def _register_routes(app: FastAPI):
|
||||
return RetainResponse.model_validate(
|
||||
{"success": True, "bank_id": bank_id, "items_count": len(contents), "async": False}
|
||||
)
|
||||
except OperationValidationError as e:
|
||||
raise HTTPException(status_code=e.status_code, detail=e.reason)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
@@ -2065,8 +1833,6 @@ def _register_routes(app: FastAPI):
|
||||
await app.state.memory.delete_bank(bank_id, fact_type=type, request_context=request_context)
|
||||
|
||||
return DeleteResponse(success=True)
|
||||
except (AuthenticationError, HTTPException):
|
||||
raise
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from contextvars import ContextVar
|
||||
from fastmcp import FastMCP
|
||||
|
||||
from hindsight_api import MemoryEngine
|
||||
from hindsight_api.api.http import BankListItem, BankListResponse, BankProfileResponse, DispositionTraits
|
||||
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
|
||||
from hindsight_api.models import RequestContext
|
||||
|
||||
@@ -28,15 +27,12 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default bank_id from environment variable
|
||||
DEFAULT_BANK_ID = os.environ.get("HINDSIGHT_MCP_BANK_ID", "default")
|
||||
|
||||
# Context variable to hold the current bank_id
|
||||
# Context variable to hold the current bank_id from the URL path
|
||||
_current_bank_id: ContextVar[str | None] = ContextVar("current_bank_id", default=None)
|
||||
|
||||
|
||||
def get_current_bank_id() -> str | None:
|
||||
"""Get the current bank_id from context."""
|
||||
"""Get the current bank_id from context (set from URL path)."""
|
||||
return _current_bank_id.get()
|
||||
|
||||
|
||||
@@ -48,13 +44,12 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
||||
memory: MemoryEngine instance (required)
|
||||
|
||||
Returns:
|
||||
Configured FastMCP server instance with stateless_http enabled
|
||||
Configured FastMCP server instance
|
||||
"""
|
||||
# Use stateless_http=True for Claude Code compatibility
|
||||
mcp = FastMCP("hindsight-mcp-server", stateless_http=True)
|
||||
mcp = FastMCP("hindsight-mcp-server")
|
||||
|
||||
@mcp.tool()
|
||||
async def retain(content: str, context: str = "general", bank_id: str | None = None) -> str:
|
||||
async def retain(content: str, context: str = "general") -> str:
|
||||
"""
|
||||
Store important information to long-term memory.
|
||||
|
||||
@@ -70,24 +65,21 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
||||
Args:
|
||||
content: The fact/memory to store (be specific and include relevant details)
|
||||
context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
|
||||
bank_id: Optional bank to store in (defaults to session bank). Use for cross-bank operations.
|
||||
"""
|
||||
try:
|
||||
target_bank = bank_id or get_current_bank_id()
|
||||
if target_bank is None:
|
||||
bank_id = get_current_bank_id()
|
||||
if bank_id is None:
|
||||
return "Error: No bank_id configured"
|
||||
await memory.retain_batch_async(
|
||||
bank_id=target_bank,
|
||||
contents=[{"content": content, "context": context}],
|
||||
request_context=RequestContext(),
|
||||
bank_id=bank_id, contents=[{"content": content, "context": context}], request_context=RequestContext()
|
||||
)
|
||||
return f"Memory stored successfully in bank '{target_bank}'"
|
||||
return "Memory stored successfully"
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing memory: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
@mcp.tool()
|
||||
async def recall(query: str, max_tokens: int = 4096, bank_id: str | None = None) -> str:
|
||||
async def recall(query: str, max_results: int = 10) -> str:
|
||||
"""
|
||||
Search memories to provide personalized, context-aware responses.
|
||||
|
||||
@@ -99,184 +91,49 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
||||
|
||||
Args:
|
||||
query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
|
||||
max_tokens: Maximum tokens in the response (default: 4096)
|
||||
bank_id: Optional bank to search in (defaults to session bank). Use for cross-bank operations.
|
||||
max_results: Maximum number of results to return (default: 10)
|
||||
"""
|
||||
try:
|
||||
target_bank = bank_id or get_current_bank_id()
|
||||
if target_bank is None:
|
||||
bank_id = get_current_bank_id()
|
||||
if bank_id is None:
|
||||
return "Error: No bank_id configured"
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
|
||||
recall_result = await memory.recall_async(
|
||||
bank_id=target_bank,
|
||||
search_result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query=query,
|
||||
fact_type=list(VALID_RECALL_FACT_TYPES),
|
||||
budget=Budget.HIGH,
|
||||
max_tokens=max_tokens,
|
||||
budget=Budget.LOW,
|
||||
request_context=RequestContext(),
|
||||
)
|
||||
|
||||
# Use model's JSON serialization
|
||||
return recall_result.model_dump_json(indent=2)
|
||||
results = [
|
||||
{
|
||||
"id": fact.id,
|
||||
"text": fact.text,
|
||||
"type": fact.fact_type,
|
||||
"context": fact.context,
|
||||
"occurred_start": fact.occurred_start,
|
||||
}
|
||||
for fact in search_result.results[:max_results]
|
||||
]
|
||||
|
||||
return json.dumps({"results": results}, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching: {e}", exc_info=True)
|
||||
return f'{{"error": "{e}", "results": []}}'
|
||||
|
||||
@mcp.tool()
|
||||
async def reflect(query: str, context: str | None = None, budget: str = "low", bank_id: str | None = None) -> str:
|
||||
"""
|
||||
Generate thoughtful analysis by synthesizing stored memories with the bank's personality.
|
||||
|
||||
WHEN TO USE THIS TOOL:
|
||||
Use reflect when you need reasoned analysis, not just fact retrieval. This tool
|
||||
thinks through the question using everything the bank knows and its personality traits.
|
||||
|
||||
EXAMPLES OF GOOD QUERIES:
|
||||
- "What patterns have emerged in how I approach debugging?"
|
||||
- "Based on my past decisions, what architectural style do I prefer?"
|
||||
- "What might be the best approach for this problem given what you know about me?"
|
||||
- "How should I prioritize these tasks based on my goals?"
|
||||
|
||||
HOW IT DIFFERS FROM RECALL:
|
||||
- recall: Returns raw facts matching your search (fast lookup)
|
||||
- reflect: Reasons across memories to form a synthesized answer (deeper analysis)
|
||||
|
||||
Use recall for "what did I say about X?" and reflect for "what should I do about X?"
|
||||
|
||||
Args:
|
||||
query: The question or topic to reflect on
|
||||
context: Optional context about why this reflection is needed
|
||||
budget: Search budget - 'low', 'mid', or 'high' (default: 'low')
|
||||
bank_id: Optional bank to reflect in (defaults to session bank). Use for cross-bank operations.
|
||||
"""
|
||||
try:
|
||||
target_bank = bank_id or get_current_bank_id()
|
||||
if target_bank is None:
|
||||
return "Error: No bank_id configured"
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
|
||||
# Map string budget to enum
|
||||
budget_map = {"low": Budget.LOW, "mid": Budget.MID, "high": Budget.HIGH}
|
||||
budget_enum = budget_map.get(budget.lower(), Budget.LOW)
|
||||
|
||||
reflect_result = await memory.reflect_async(
|
||||
bank_id=target_bank,
|
||||
query=query,
|
||||
budget=budget_enum,
|
||||
context=context,
|
||||
request_context=RequestContext(),
|
||||
)
|
||||
|
||||
return reflect_result.model_dump_json(indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Error reflecting: {e}", exc_info=True)
|
||||
return f'{{"error": "{e}", "text": ""}}'
|
||||
|
||||
@mcp.tool()
|
||||
async def list_banks() -> str:
|
||||
"""
|
||||
List all available memory banks.
|
||||
|
||||
Use this to discover banks for orchestration or to find
|
||||
the correct bank_id for cross-bank operations.
|
||||
|
||||
Returns:
|
||||
JSON object with banks array containing bank_id, name, disposition, background, and timestamps
|
||||
"""
|
||||
try:
|
||||
banks = await memory.list_banks(request_context=RequestContext())
|
||||
bank_items = [
|
||||
BankListItem(
|
||||
bank_id=b.get("bank_id") or b.get("id"),
|
||||
name=b.get("name"),
|
||||
disposition=DispositionTraits(
|
||||
**b.get("disposition", {"skepticism": 3, "literalism": 3, "empathy": 3})
|
||||
),
|
||||
background=b.get("background"),
|
||||
created_at=str(b.get("created_at")) if b.get("created_at") else None,
|
||||
updated_at=str(b.get("updated_at")) if b.get("updated_at") else None,
|
||||
)
|
||||
for b in banks
|
||||
]
|
||||
return BankListResponse(banks=bank_items).model_dump_json(indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing banks: {e}", exc_info=True)
|
||||
return f'{{"error": "{e}", "banks": []}}'
|
||||
|
||||
@mcp.tool()
|
||||
async def create_bank(bank_id: str, name: str | None = None, background: str | None = None) -> str:
|
||||
"""
|
||||
Create or update a memory bank.
|
||||
|
||||
Use this to create new banks for different agents, sessions, or purposes.
|
||||
Banks are isolated memory stores - each bank has its own memories and personality.
|
||||
|
||||
Args:
|
||||
bank_id: Unique identifier for the bank (e.g., 'orchestrator-memory', 'agent-1')
|
||||
name: Human-readable name for the bank
|
||||
background: Context about what this bank stores or its purpose
|
||||
"""
|
||||
try:
|
||||
# Get or create the bank profile (auto-creates with defaults)
|
||||
await memory.get_bank_profile(bank_id, request_context=RequestContext())
|
||||
|
||||
# Update name and/or background if provided
|
||||
if name is not None or background is not None:
|
||||
await memory.update_bank(bank_id, name=name, background=background, request_context=RequestContext())
|
||||
|
||||
# Get final profile and return using BankProfileResponse model
|
||||
profile = await memory.get_bank_profile(bank_id, request_context=RequestContext())
|
||||
disposition = profile.get("disposition")
|
||||
if hasattr(disposition, "model_dump"):
|
||||
disposition_traits = DispositionTraits(**disposition.model_dump())
|
||||
else:
|
||||
disposition_traits = DispositionTraits(
|
||||
**dict(disposition or {"skepticism": 3, "literalism": 3, "empathy": 3})
|
||||
)
|
||||
|
||||
response = BankProfileResponse(
|
||||
bank_id=bank_id,
|
||||
name=profile.get("name") or "",
|
||||
disposition=disposition_traits,
|
||||
background=profile.get("background") or "",
|
||||
)
|
||||
return response.model_dump_json(indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating bank: {e}", exc_info=True)
|
||||
return json.dumps({"error": str(e)})
|
||||
return json.dumps({"error": str(e), "results": []})
|
||||
|
||||
return mcp
|
||||
|
||||
|
||||
class MCPMiddleware:
|
||||
"""ASGI middleware that extracts bank_id from header or path and sets context.
|
||||
|
||||
Bank ID can be provided via:
|
||||
1. X-Bank-Id header (recommended for Claude Code)
|
||||
2. URL path: /mcp/{bank_id}/
|
||||
3. Environment variable HINDSIGHT_MCP_BANK_ID (fallback default)
|
||||
|
||||
For Claude Code, configure with:
|
||||
claude mcp add --transport http hindsight http://localhost:8888/mcp \\
|
||||
--header "X-Bank-Id: my-bank"
|
||||
"""
|
||||
"""ASGI middleware that extracts bank_id from path and sets context."""
|
||||
|
||||
def __init__(self, app, memory: MemoryEngine):
|
||||
self.app = app
|
||||
self.memory = memory
|
||||
self.mcp_server = create_mcp_server(memory)
|
||||
self.mcp_app = self.mcp_server.http_app(path="/")
|
||||
# Expose the lifespan for the parent app to chain
|
||||
self.lifespan = self.mcp_app.lifespan_handler if hasattr(self.mcp_app, "lifespan_handler") else None
|
||||
|
||||
def _get_header(self, scope: dict, name: str) -> str | None:
|
||||
"""Extract a header value from ASGI scope."""
|
||||
name_lower = name.lower().encode()
|
||||
for header_name, header_value in scope.get("headers", []):
|
||||
if header_name.lower() == name_lower:
|
||||
return header_value.decode()
|
||||
return None
|
||||
self.mcp_app = self.mcp_server.http_app()
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
if scope["type"] != "http":
|
||||
@@ -293,39 +150,32 @@ class MCPMiddleware:
|
||||
# Also handle case where mount path wasn't stripped (e.g., /mcp/...)
|
||||
if path.startswith("/mcp/"):
|
||||
path = path[4:] # Remove /mcp prefix
|
||||
elif path == "/mcp":
|
||||
path = "/"
|
||||
|
||||
# Try to get bank_id from header first (for Claude Code compatibility)
|
||||
bank_id = self._get_header(scope, "X-Bank-Id")
|
||||
# Extract bank_id from path: /{bank_id}/ or /{bank_id}
|
||||
# http_app expects requests at /
|
||||
if not path.startswith("/") or len(path) <= 1:
|
||||
# No bank_id in path - return error
|
||||
await self._send_error(send, 400, "bank_id required in path: /mcp/{bank_id}/")
|
||||
return
|
||||
|
||||
# MCP endpoint paths that should not be treated as bank_ids
|
||||
MCP_ENDPOINTS = {"sse", "messages"}
|
||||
# Extract bank_id from first path segment
|
||||
parts = path[1:].split("/", 1)
|
||||
if not parts[0]:
|
||||
await self._send_error(send, 400, "bank_id required in path: /mcp/{bank_id}/")
|
||||
return
|
||||
|
||||
# If no header, try to extract from path: /{bank_id}/...
|
||||
new_path = path
|
||||
if not bank_id and path.startswith("/") and len(path) > 1:
|
||||
parts = path[1:].split("/", 1)
|
||||
# Don't treat MCP endpoints as bank_ids
|
||||
if parts[0] and parts[0] not in MCP_ENDPOINTS:
|
||||
# First segment looks like a bank_id
|
||||
bank_id = parts[0]
|
||||
new_path = "/" + parts[1] if len(parts) > 1 else "/"
|
||||
|
||||
# Fall back to default bank_id
|
||||
if not bank_id:
|
||||
bank_id = DEFAULT_BANK_ID
|
||||
logger.debug(f"Using default bank_id: {bank_id}")
|
||||
bank_id = parts[0]
|
||||
new_path = "/" + parts[1] if len(parts) > 1 else "/"
|
||||
|
||||
# Set bank_id context
|
||||
token = _current_bank_id.set(bank_id)
|
||||
try:
|
||||
new_scope = scope.copy()
|
||||
new_scope["path"] = new_path
|
||||
# Clear root_path since we're passing directly to the app
|
||||
new_scope["root_path"] = ""
|
||||
|
||||
# Wrap send to rewrite the SSE endpoint URL to include bank_id if using path-based routing
|
||||
# Wrap send to rewrite the SSE endpoint URL to include bank_id
|
||||
# The SSE app sends "event: endpoint\ndata: /messages\n" but we need
|
||||
# the client to POST to /{bank_id}/messages instead
|
||||
async def send_wrapper(message):
|
||||
if message["type"] == "http.response.body":
|
||||
body = message.get("body", b"")
|
||||
@@ -361,10 +211,9 @@ def create_mcp_app(memory: MemoryEngine):
|
||||
"""
|
||||
Create an ASGI app that handles MCP requests.
|
||||
|
||||
Bank ID can be provided via:
|
||||
1. X-Bank-Id header: claude mcp add --transport http hindsight http://localhost:8888/mcp --header "X-Bank-Id: my-bank"
|
||||
2. URL path: /mcp/{bank_id}/
|
||||
3. Environment variable HINDSIGHT_MCP_BANK_ID (fallback, default: "default")
|
||||
URL pattern: /mcp/{bank_id}/
|
||||
|
||||
The bank_id is extracted from the URL path and made available to tools.
|
||||
|
||||
Args:
|
||||
memory: MemoryEngine instance
|
||||
|
||||
@@ -16,8 +16,6 @@ ENV_LLM_PROVIDER = "HINDSIGHT_API_LLM_PROVIDER"
|
||||
ENV_LLM_API_KEY = "HINDSIGHT_API_LLM_API_KEY"
|
||||
ENV_LLM_MODEL = "HINDSIGHT_API_LLM_MODEL"
|
||||
ENV_LLM_BASE_URL = "HINDSIGHT_API_LLM_BASE_URL"
|
||||
ENV_LLM_MAX_CONCURRENT = "HINDSIGHT_API_LLM_MAX_CONCURRENT"
|
||||
ENV_LLM_TIMEOUT = "HINDSIGHT_API_LLM_TIMEOUT"
|
||||
|
||||
ENV_EMBEDDINGS_PROVIDER = "HINDSIGHT_API_EMBEDDINGS_PROVIDER"
|
||||
ENV_EMBEDDINGS_LOCAL_MODEL = "HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL"
|
||||
@@ -35,10 +33,6 @@ ENV_GRAPH_RETRIEVER = "HINDSIGHT_API_GRAPH_RETRIEVER"
|
||||
ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID"
|
||||
ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS"
|
||||
|
||||
# Observation thresholds
|
||||
ENV_OBSERVATION_MIN_FACTS = "HINDSIGHT_API_OBSERVATION_MIN_FACTS"
|
||||
ENV_OBSERVATION_TOP_ENTITIES = "HINDSIGHT_API_OBSERVATION_TOP_ENTITIES"
|
||||
|
||||
# Optimization flags
|
||||
ENV_SKIP_LLM_VERIFICATION = "HINDSIGHT_API_SKIP_LLM_VERIFICATION"
|
||||
ENV_LAZY_RERANKER = "HINDSIGHT_API_LAZY_RERANKER"
|
||||
@@ -47,8 +41,6 @@ ENV_LAZY_RERANKER = "HINDSIGHT_API_LAZY_RERANKER"
|
||||
DEFAULT_DATABASE_URL = "pg0"
|
||||
DEFAULT_LLM_PROVIDER = "openai"
|
||||
DEFAULT_LLM_MODEL = "gpt-5-mini"
|
||||
DEFAULT_LLM_MAX_CONCURRENT = 32
|
||||
DEFAULT_LLM_TIMEOUT = 120.0 # seconds
|
||||
|
||||
DEFAULT_EMBEDDINGS_PROVIDER = "local"
|
||||
DEFAULT_EMBEDDINGS_LOCAL_MODEL = "BAAI/bge-small-en-v1.5"
|
||||
@@ -63,10 +55,6 @@ DEFAULT_MCP_ENABLED = True
|
||||
DEFAULT_GRAPH_RETRIEVER = "bfs" # Options: "bfs", "mpfp"
|
||||
DEFAULT_MCP_LOCAL_BANK_ID = "mcp"
|
||||
|
||||
# Observation thresholds
|
||||
DEFAULT_OBSERVATION_MIN_FACTS = 5 # Min facts required to generate entity observations
|
||||
DEFAULT_OBSERVATION_TOP_ENTITIES = 5 # Max entities to process per retain batch
|
||||
|
||||
# Default MCP tool descriptions (can be customized via env vars)
|
||||
DEFAULT_MCP_RETAIN_DESCRIPTION = """Store important information to long-term memory.
|
||||
|
||||
@@ -103,8 +91,6 @@ class HindsightConfig:
|
||||
llm_api_key: str | None
|
||||
llm_model: str
|
||||
llm_base_url: str | None
|
||||
llm_max_concurrent: int
|
||||
llm_timeout: float
|
||||
|
||||
# Embeddings
|
||||
embeddings_provider: str
|
||||
@@ -125,10 +111,6 @@ class HindsightConfig:
|
||||
# Recall
|
||||
graph_retriever: str
|
||||
|
||||
# Observation thresholds
|
||||
observation_min_facts: int
|
||||
observation_top_entities: int
|
||||
|
||||
# Optimization flags
|
||||
skip_llm_verification: bool
|
||||
lazy_reranker: bool
|
||||
@@ -144,8 +126,6 @@ class HindsightConfig:
|
||||
llm_api_key=os.getenv(ENV_LLM_API_KEY),
|
||||
llm_model=os.getenv(ENV_LLM_MODEL, DEFAULT_LLM_MODEL),
|
||||
llm_base_url=os.getenv(ENV_LLM_BASE_URL) or None,
|
||||
llm_max_concurrent=int(os.getenv(ENV_LLM_MAX_CONCURRENT, str(DEFAULT_LLM_MAX_CONCURRENT))),
|
||||
llm_timeout=float(os.getenv(ENV_LLM_TIMEOUT, str(DEFAULT_LLM_TIMEOUT))),
|
||||
# Embeddings
|
||||
embeddings_provider=os.getenv(ENV_EMBEDDINGS_PROVIDER, DEFAULT_EMBEDDINGS_PROVIDER),
|
||||
embeddings_local_model=os.getenv(ENV_EMBEDDINGS_LOCAL_MODEL, DEFAULT_EMBEDDINGS_LOCAL_MODEL),
|
||||
@@ -164,11 +144,6 @@ class HindsightConfig:
|
||||
# Optimization flags
|
||||
skip_llm_verification=os.getenv(ENV_SKIP_LLM_VERIFICATION, "false").lower() == "true",
|
||||
lazy_reranker=os.getenv(ENV_LAZY_RERANKER, "false").lower() == "true",
|
||||
# Observation thresholds
|
||||
observation_min_facts=int(os.getenv(ENV_OBSERVATION_MIN_FACTS, str(DEFAULT_OBSERVATION_MIN_FACTS))),
|
||||
observation_top_entities=int(
|
||||
os.getenv(ENV_OBSERVATION_TOP_ENTITIES, str(DEFAULT_OBSERVATION_TOP_ENTITIES))
|
||||
),
|
||||
)
|
||||
|
||||
def get_llm_base_url(self) -> str:
|
||||
@@ -181,8 +156,6 @@ class HindsightConfig:
|
||||
return "https://api.groq.com/openai/v1"
|
||||
elif provider == "ollama":
|
||||
return "http://localhost:11434/v1"
|
||||
elif provider == "lmstudio":
|
||||
return "http://localhost:1234/v1"
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -110,8 +110,6 @@ class MemoryEngineInterface(ABC):
|
||||
*,
|
||||
budget: "Budget | None" = None,
|
||||
context: str | None = None,
|
||||
max_tokens: int = 4096,
|
||||
response_schema: dict | None = None,
|
||||
request_context: "RequestContext",
|
||||
) -> "ReflectResult":
|
||||
"""
|
||||
@@ -122,8 +120,6 @@ class MemoryEngineInterface(ABC):
|
||||
query: The question to reflect on.
|
||||
budget: Search budget for retrieving context.
|
||||
context: Additional context for the reflection.
|
||||
max_tokens: Maximum tokens for the response.
|
||||
response_schema: Optional JSON Schema for structured output.
|
||||
request_context: Request context for authentication.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -6,7 +6,6 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
@@ -16,13 +15,6 @@ from google.genai import errors as genai_errors
|
||||
from google.genai import types as genai_types
|
||||
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, LengthFinishReasonError
|
||||
|
||||
from ..config import (
|
||||
DEFAULT_LLM_MAX_CONCURRENT,
|
||||
DEFAULT_LLM_TIMEOUT,
|
||||
ENV_LLM_MAX_CONCURRENT,
|
||||
ENV_LLM_TIMEOUT,
|
||||
)
|
||||
|
||||
# Seed applied to every Groq request for deterministic behavior.
|
||||
DEFAULT_LLM_SEED = 4242
|
||||
|
||||
@@ -32,9 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
# Global semaphore to limit concurrent LLM requests across all instances
|
||||
# Set HINDSIGHT_API_LLM_MAX_CONCURRENT=1 for local LLMs (LM Studio, Ollama)
|
||||
_llm_max_concurrent = int(os.getenv(ENV_LLM_MAX_CONCURRENT, str(DEFAULT_LLM_MAX_CONCURRENT)))
|
||||
_global_llm_semaphore = asyncio.Semaphore(_llm_max_concurrent)
|
||||
_global_llm_semaphore = asyncio.Semaphore(32)
|
||||
|
||||
|
||||
class OutputTooLongError(Exception):
|
||||
@@ -68,7 +58,7 @@ class LLMProvider:
|
||||
Initialize LLM provider.
|
||||
|
||||
Args:
|
||||
provider: Provider name ("openai", "groq", "ollama", "gemini", "anthropic", "lmstudio").
|
||||
provider: Provider name ("openai", "groq", "ollama", "gemini").
|
||||
api_key: API key.
|
||||
base_url: Base URL for the API.
|
||||
model: Model name.
|
||||
@@ -81,7 +71,7 @@ class LLMProvider:
|
||||
self.reasoning_effort = reasoning_effort
|
||||
|
||||
# Validate provider
|
||||
valid_providers = ["openai", "groq", "ollama", "gemini", "anthropic", "lmstudio"]
|
||||
valid_providers = ["openai", "groq", "ollama", "gemini"]
|
||||
if self.provider not in valid_providers:
|
||||
raise ValueError(f"Invalid LLM provider: {self.provider}. Must be one of: {', '.join(valid_providers)}")
|
||||
|
||||
@@ -91,48 +81,25 @@ class LLMProvider:
|
||||
self.base_url = "https://api.groq.com/openai/v1"
|
||||
elif self.provider == "ollama":
|
||||
self.base_url = "http://localhost:11434/v1"
|
||||
elif self.provider == "lmstudio":
|
||||
self.base_url = "http://localhost:1234/v1"
|
||||
|
||||
# Validate API key (not needed for ollama or lmstudio)
|
||||
if self.provider not in ("ollama", "lmstudio") and not self.api_key:
|
||||
# Validate API key (not needed for ollama)
|
||||
if self.provider != "ollama" and not self.api_key:
|
||||
raise ValueError(f"API key not found for {self.provider}")
|
||||
|
||||
# Get timeout config (set HINDSIGHT_API_LLM_TIMEOUT for local LLMs that need longer timeouts)
|
||||
self.timeout = float(os.getenv(ENV_LLM_TIMEOUT, str(DEFAULT_LLM_TIMEOUT)))
|
||||
|
||||
# Create client based on provider
|
||||
self._client = None
|
||||
self._gemini_client = None
|
||||
self._anthropic_client = None
|
||||
|
||||
if self.provider == "gemini":
|
||||
self._gemini_client = genai.Client(api_key=self.api_key)
|
||||
elif self.provider == "anthropic":
|
||||
from anthropic import AsyncAnthropic
|
||||
|
||||
# Only pass base_url if it's set (Anthropic uses default URL otherwise)
|
||||
anthropic_kwargs = {"api_key": self.api_key}
|
||||
if self.base_url:
|
||||
anthropic_kwargs["base_url"] = self.base_url
|
||||
if self.timeout:
|
||||
anthropic_kwargs["timeout"] = self.timeout
|
||||
self._anthropic_client = AsyncAnthropic(**anthropic_kwargs)
|
||||
elif self.provider in ("ollama", "lmstudio"):
|
||||
# Use dummy key if not provided for local
|
||||
api_key = self.api_key or "local"
|
||||
client_kwargs = {"api_key": api_key, "base_url": self.base_url, "max_retries": 0}
|
||||
if self.timeout:
|
||||
client_kwargs["timeout"] = self.timeout
|
||||
self._client = AsyncOpenAI(**client_kwargs)
|
||||
self._client = None
|
||||
elif self.provider == "ollama":
|
||||
self._client = AsyncOpenAI(api_key="ollama", base_url=self.base_url, max_retries=0)
|
||||
self._gemini_client = None
|
||||
else:
|
||||
# Only pass base_url if it's set (OpenAI uses default URL otherwise)
|
||||
client_kwargs = {"api_key": self.api_key, "max_retries": 0}
|
||||
if self.base_url:
|
||||
client_kwargs["base_url"] = self.base_url
|
||||
if self.timeout:
|
||||
client_kwargs["timeout"] = self.timeout
|
||||
self._client = AsyncOpenAI(**client_kwargs)
|
||||
self._client = AsyncOpenAI(**client_kwargs) # type: ignore[invalid-argument-type] - dict kwargs
|
||||
self._gemini_client = None
|
||||
|
||||
async def verify_connection(self) -> None:
|
||||
"""
|
||||
@@ -168,7 +135,6 @@ class LLMProvider:
|
||||
initial_backoff: float = 1.0,
|
||||
max_backoff: float = 60.0,
|
||||
skip_validation: bool = False,
|
||||
strict_schema: bool = False,
|
||||
) -> Any:
|
||||
"""
|
||||
Make an LLM API call with retry logic.
|
||||
@@ -183,7 +149,6 @@ class LLMProvider:
|
||||
initial_backoff: Initial backoff time in seconds.
|
||||
max_backoff: Maximum backoff time in seconds.
|
||||
skip_validation: Return raw JSON without Pydantic validation.
|
||||
strict_schema: Use strict JSON schema enforcement (OpenAI only). Guarantees all required fields.
|
||||
|
||||
Returns:
|
||||
Parsed response if response_format is provided, otherwise text content.
|
||||
@@ -201,19 +166,6 @@ class LLMProvider:
|
||||
messages, response_format, max_retries, initial_backoff, max_backoff, skip_validation, start_time
|
||||
)
|
||||
|
||||
# Handle Anthropic provider separately
|
||||
if self.provider == "anthropic":
|
||||
return await self._call_anthropic(
|
||||
messages,
|
||||
response_format,
|
||||
max_completion_tokens,
|
||||
max_retries,
|
||||
initial_backoff,
|
||||
max_backoff,
|
||||
skip_validation,
|
||||
start_time,
|
||||
)
|
||||
|
||||
# Handle Ollama with native API for structured output (better schema enforcement)
|
||||
if self.provider == "ollama" and response_format is not None:
|
||||
return await self._call_ollama_native(
|
||||
@@ -274,93 +226,47 @@ class LLMProvider:
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
if response_format is not None:
|
||||
schema = None
|
||||
# Add schema to system message for JSON mode
|
||||
if hasattr(response_format, "model_json_schema"):
|
||||
schema = response_format.model_json_schema()
|
||||
schema_msg = f"\n\nYou must respond with valid JSON matching this schema:\n{json.dumps(schema, indent=2)}"
|
||||
|
||||
if strict_schema and schema is not None:
|
||||
# Use OpenAI's strict JSON schema enforcement
|
||||
# This guarantees all required fields are returned
|
||||
call_params["response_format"] = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "response",
|
||||
"strict": True,
|
||||
"schema": schema,
|
||||
},
|
||||
}
|
||||
else:
|
||||
# Soft enforcement: add schema to prompt and use json_object mode
|
||||
if schema is not None:
|
||||
schema_msg = f"\n\nYou must respond with valid JSON matching this schema:\n{json.dumps(schema, indent=2)}"
|
||||
if call_params["messages"] and call_params["messages"][0].get("role") == "system":
|
||||
call_params["messages"][0]["content"] += schema_msg
|
||||
elif call_params["messages"]:
|
||||
call_params["messages"][0]["content"] = (
|
||||
schema_msg + "\n\n" + call_params["messages"][0]["content"]
|
||||
)
|
||||
|
||||
if call_params["messages"] and call_params["messages"][0].get("role") == "system":
|
||||
call_params["messages"][0]["content"] += schema_msg
|
||||
elif call_params["messages"]:
|
||||
call_params["messages"][0]["content"] = (
|
||||
schema_msg + "\n\n" + call_params["messages"][0]["content"]
|
||||
)
|
||||
if self.provider not in ("lmstudio", "ollama"):
|
||||
# LM Studio and Ollama don't support json_object response format reliably
|
||||
# We rely on the schema in the system message instead
|
||||
call_params["response_format"] = {"type": "json_object"}
|
||||
|
||||
logger.debug(f"Sending request to {self.provider}/{self.model} (timeout={self.timeout})")
|
||||
call_params["response_format"] = {"type": "json_object"}
|
||||
response = await self._client.chat.completions.create(**call_params)
|
||||
logger.debug(f"Received response from {self.provider}/{self.model}")
|
||||
|
||||
content = response.choices[0].message.content
|
||||
|
||||
# Strip reasoning model thinking tags
|
||||
# Supports: <think>, <thinking>, <reasoning>, |startthink|/|endthink|
|
||||
# for reasoning models that embed thinking in their output (e.g., Qwen3, DeepSeek)
|
||||
if content:
|
||||
original_len = len(content)
|
||||
content = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL)
|
||||
content = re.sub(r"<thinking>.*?</thinking>", "", content, flags=re.DOTALL)
|
||||
content = re.sub(r"<reasoning>.*?</reasoning>", "", content, flags=re.DOTALL)
|
||||
content = re.sub(r"\|startthink\|.*?\|endthink\|", "", content, flags=re.DOTALL)
|
||||
content = content.strip()
|
||||
if len(content) < original_len:
|
||||
logger.debug(f"Stripped {original_len - len(content)} chars of reasoning tokens")
|
||||
|
||||
# For local models, they may wrap JSON in markdown code blocks
|
||||
if self.provider in ("lmstudio", "ollama"):
|
||||
clean_content = content
|
||||
if "```json" in content:
|
||||
clean_content = content.split("```json")[1].split("```")[0].strip()
|
||||
elif "```" in content:
|
||||
clean_content = content.split("```")[1].split("```")[0].strip()
|
||||
try:
|
||||
json_data = json.loads(clean_content)
|
||||
except json.JSONDecodeError:
|
||||
# Fallback to parsing raw content
|
||||
json_data = json.loads(content)
|
||||
else:
|
||||
# Log raw LLM response for debugging JSON parse issues
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
except json.JSONDecodeError as json_err:
|
||||
# Truncate content for logging (first 500 and last 200 chars)
|
||||
content_preview = content[:500] if content else "<empty>"
|
||||
if content and len(content) > 700:
|
||||
content_preview = f"{content[:500]}...TRUNCATED...{content[-200:]}"
|
||||
logger.warning(
|
||||
f"JSON parse error from LLM response (attempt {attempt + 1}/{max_retries + 1}): {json_err}\n"
|
||||
f" Model: {self.provider}/{self.model}\n"
|
||||
f" Content length: {len(content) if content else 0} chars\n"
|
||||
f" Content preview: {content_preview!r}\n"
|
||||
f" Finish reason: {response.choices[0].finish_reason if response.choices else 'unknown'}"
|
||||
)
|
||||
# Retry on JSON parse errors - LLM may return valid JSON on next attempt
|
||||
if attempt < max_retries:
|
||||
backoff = min(initial_backoff * (2**attempt), max_backoff)
|
||||
await asyncio.sleep(backoff)
|
||||
last_exception = json_err
|
||||
continue
|
||||
else:
|
||||
logger.error(f"JSON parse error after {max_retries + 1} attempts, giving up")
|
||||
raise
|
||||
# Log raw LLM response for debugging JSON parse issues
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
except json.JSONDecodeError as json_err:
|
||||
# Truncate content for logging (first 500 and last 200 chars)
|
||||
content_preview = content[:500] if content else "<empty>"
|
||||
if content and len(content) > 700:
|
||||
content_preview = f"{content[:500]}...TRUNCATED...{content[-200:]}"
|
||||
logger.warning(
|
||||
f"JSON parse error from LLM response (attempt {attempt + 1}/{max_retries + 1}): {json_err}\n"
|
||||
f" Model: {self.provider}/{self.model}\n"
|
||||
f" Content length: {len(content) if content else 0} chars\n"
|
||||
f" Content preview: {content_preview!r}\n"
|
||||
f" Finish reason: {response.choices[0].finish_reason if response.choices else 'unknown'}"
|
||||
)
|
||||
# Retry on JSON parse errors - LLM may return valid JSON on next attempt
|
||||
if attempt < max_retries:
|
||||
backoff = min(initial_backoff * (2**attempt), max_backoff)
|
||||
await asyncio.sleep(backoff)
|
||||
last_exception = json_err
|
||||
continue
|
||||
else:
|
||||
logger.error(f"JSON parse error after {max_retries + 1} attempts, giving up")
|
||||
raise
|
||||
|
||||
if skip_validation:
|
||||
result = json_data
|
||||
@@ -433,142 +339,6 @@ class LLMProvider:
|
||||
raise last_exception
|
||||
raise RuntimeError("LLM call failed after all retries with no exception captured")
|
||||
|
||||
async def _call_anthropic(
|
||||
self,
|
||||
messages: list[dict[str, str]],
|
||||
response_format: Any | None,
|
||||
max_completion_tokens: int | None,
|
||||
max_retries: int,
|
||||
initial_backoff: float,
|
||||
max_backoff: float,
|
||||
skip_validation: bool,
|
||||
start_time: float,
|
||||
) -> Any:
|
||||
"""Handle Anthropic-specific API calls."""
|
||||
from anthropic import APIConnectionError, APIStatusError, RateLimitError
|
||||
|
||||
# Convert OpenAI-style messages to Anthropic format
|
||||
system_prompt = None
|
||||
anthropic_messages = []
|
||||
|
||||
for msg in messages:
|
||||
role = msg.get("role", "user")
|
||||
content = msg.get("content", "")
|
||||
|
||||
if role == "system":
|
||||
if system_prompt:
|
||||
system_prompt += "\n\n" + content
|
||||
else:
|
||||
system_prompt = content
|
||||
else:
|
||||
anthropic_messages.append({"role": role, "content": content})
|
||||
|
||||
# Add JSON schema instruction if response_format is provided
|
||||
if response_format is not None and hasattr(response_format, "model_json_schema"):
|
||||
schema = response_format.model_json_schema()
|
||||
schema_msg = f"\n\nYou must respond with valid JSON matching this schema:\n{json.dumps(schema, indent=2)}"
|
||||
if system_prompt:
|
||||
system_prompt += schema_msg
|
||||
else:
|
||||
system_prompt = schema_msg
|
||||
|
||||
# Prepare parameters
|
||||
call_params = {
|
||||
"model": self.model,
|
||||
"messages": anthropic_messages,
|
||||
"max_tokens": max_completion_tokens if max_completion_tokens is not None else 4096,
|
||||
}
|
||||
|
||||
if system_prompt:
|
||||
call_params["system"] = system_prompt
|
||||
|
||||
last_exception = None
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
response = await self._anthropic_client.messages.create(**call_params)
|
||||
|
||||
# Anthropic response content is a list of blocks
|
||||
content = ""
|
||||
for block in response.content:
|
||||
if block.type == "text":
|
||||
content += block.text
|
||||
|
||||
if response_format is not None:
|
||||
# Models may wrap JSON in markdown code blocks
|
||||
clean_content = content
|
||||
if "```json" in content:
|
||||
clean_content = content.split("```json")[1].split("```")[0].strip()
|
||||
elif "```" in content:
|
||||
clean_content = content.split("```")[1].split("```")[0].strip()
|
||||
|
||||
try:
|
||||
json_data = json.loads(clean_content)
|
||||
except json.JSONDecodeError:
|
||||
# Fallback to parsing raw content if markdown stripping failed
|
||||
json_data = json.loads(content)
|
||||
|
||||
if skip_validation:
|
||||
result = json_data
|
||||
else:
|
||||
result = response_format.model_validate(json_data)
|
||||
else:
|
||||
result = content
|
||||
|
||||
# Log slow calls
|
||||
duration = time.time() - start_time
|
||||
if duration > 10.0:
|
||||
input_tokens = response.usage.input_tokens
|
||||
output_tokens = response.usage.output_tokens
|
||||
logger.info(
|
||||
f"slow llm call: model={self.provider}/{self.model}, "
|
||||
f"input_tokens={input_tokens}, output_tokens={output_tokens}, "
|
||||
f"time={duration:.3f}s"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
last_exception = e
|
||||
if attempt < max_retries:
|
||||
logger.warning("Anthropic returned invalid JSON, retrying...")
|
||||
backoff = min(initial_backoff * (2**attempt), max_backoff)
|
||||
await asyncio.sleep(backoff)
|
||||
continue
|
||||
else:
|
||||
logger.error(f"Anthropic returned invalid JSON after {max_retries + 1} attempts")
|
||||
raise
|
||||
|
||||
except (APIConnectionError, RateLimitError, APIStatusError) as e:
|
||||
# Fast fail on 401/403
|
||||
if isinstance(e, APIStatusError) and e.status_code in (401, 403):
|
||||
logger.error(f"Anthropic auth error (HTTP {e.status_code}), not retrying: {str(e)}")
|
||||
raise
|
||||
|
||||
last_exception = e
|
||||
if attempt < max_retries:
|
||||
# Check if it's a rate limit or server error
|
||||
should_retry = isinstance(e, (APIConnectionError, RateLimitError)) or (
|
||||
isinstance(e, APIStatusError) and e.status_code >= 500
|
||||
)
|
||||
|
||||
if should_retry:
|
||||
backoff = min(initial_backoff * (2**attempt), max_backoff)
|
||||
jitter = backoff * 0.2 * (2 * (time.time() % 1) - 1)
|
||||
await asyncio.sleep(backoff + jitter)
|
||||
continue
|
||||
|
||||
logger.error(f"Anthropic API error after {max_retries + 1} attempts: {str(e)}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during Anthropic call: {type(e).__name__}: {str(e)}")
|
||||
raise
|
||||
|
||||
if last_exception:
|
||||
raise last_exception
|
||||
raise RuntimeError("Anthropic call failed after all retries")
|
||||
|
||||
async def _call_ollama_native(
|
||||
self,
|
||||
messages: list[dict[str, str]],
|
||||
|
||||
@@ -17,8 +17,6 @@ import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..config import get_config
|
||||
|
||||
# Context variable for current schema (async-safe, per-task isolation)
|
||||
_current_schema: contextvars.ContextVar[str] = contextvars.ContextVar("current_schema", default="public")
|
||||
|
||||
@@ -374,7 +372,7 @@ class MemoryEngine(MemoryEngineInterface):
|
||||
|
||||
result = await validation_coro
|
||||
if not result.allowed:
|
||||
raise OperationValidationError(result.reason or "Operation not allowed", result.status_code)
|
||||
raise OperationValidationError(result.reason or "Operation not allowed")
|
||||
|
||||
async def _authenticate_tenant(self, request_context: "RequestContext | None") -> str:
|
||||
"""
|
||||
@@ -401,9 +399,7 @@ class MemoryEngine(MemoryEngineInterface):
|
||||
if request_context is None:
|
||||
raise AuthenticationError("RequestContext is required when tenant extension is configured")
|
||||
|
||||
# Let AuthenticationError propagate - HTTP layer will convert to 401
|
||||
tenant_context = await self._tenant_extension.authenticate(request_context)
|
||||
|
||||
_current_schema.set(tenant_context.schema_name)
|
||||
return tenant_context.schema_name
|
||||
|
||||
@@ -413,22 +409,22 @@ class MemoryEngine(MemoryEngineInterface):
|
||||
|
||||
Args:
|
||||
task_dict: Dict with 'node_ids' key containing list of node IDs to update
|
||||
|
||||
Raises:
|
||||
Exception: Any exception from database operations (propagates to execute_task for retry)
|
||||
"""
|
||||
node_ids = task_dict.get("node_ids", [])
|
||||
if not node_ids:
|
||||
return
|
||||
|
||||
pool = await self._get_pool()
|
||||
# Convert string UUIDs to UUID type for faster matching
|
||||
uuid_list = [uuid.UUID(nid) for nid in node_ids]
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
await conn.execute(
|
||||
f"UPDATE {fq_table('memory_units')} SET access_count = access_count + 1 WHERE id = ANY($1::uuid[])",
|
||||
uuid_list,
|
||||
)
|
||||
try:
|
||||
# Convert string UUIDs to UUID type for faster matching
|
||||
uuid_list = [uuid.UUID(nid) for nid in node_ids]
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
await conn.execute(
|
||||
f"UPDATE {fq_table('memory_units')} SET access_count = access_count + 1 WHERE id = ANY($1::uuid[])",
|
||||
uuid_list,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Access count handler: Error updating access counts: {e}")
|
||||
|
||||
async def _handle_batch_retain(self, task_dict: dict[str, Any]):
|
||||
"""
|
||||
@@ -436,27 +432,29 @@ class MemoryEngine(MemoryEngineInterface):
|
||||
|
||||
Args:
|
||||
task_dict: Dict with 'bank_id', 'contents'
|
||||
|
||||
Raises:
|
||||
ValueError: If bank_id is missing
|
||||
Exception: Any exception from retain_batch_async (propagates to execute_task for retry)
|
||||
"""
|
||||
bank_id = task_dict.get("bank_id")
|
||||
if not bank_id:
|
||||
raise ValueError("bank_id is required for batch retain task")
|
||||
contents = task_dict.get("contents", [])
|
||||
try:
|
||||
bank_id = task_dict.get("bank_id")
|
||||
if not bank_id:
|
||||
raise ValueError("bank_id is required for batch retain task")
|
||||
contents = task_dict.get("contents", [])
|
||||
|
||||
logger.info(
|
||||
f"[BATCH_RETAIN_TASK] Starting background batch retain for bank_id={bank_id}, {len(contents)} items"
|
||||
)
|
||||
logger.info(
|
||||
f"[BATCH_RETAIN_TASK] Starting background batch retain for bank_id={bank_id}, {len(contents)} items"
|
||||
)
|
||||
|
||||
# Use internal request context for background tasks
|
||||
from hindsight_api.models import RequestContext
|
||||
# Use internal request context for background tasks
|
||||
from hindsight_api.models import RequestContext
|
||||
|
||||
internal_context = RequestContext()
|
||||
await self.retain_batch_async(bank_id=bank_id, contents=contents, request_context=internal_context)
|
||||
internal_context = RequestContext()
|
||||
await self.retain_batch_async(bank_id=bank_id, contents=contents, request_context=internal_context)
|
||||
|
||||
logger.info(f"[BATCH_RETAIN_TASK] Completed background batch retain for bank_id={bank_id}")
|
||||
logger.info(f"[BATCH_RETAIN_TASK] Completed background batch retain for bank_id={bank_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Batch retain handler: Error processing batch retain: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
async def execute_task(self, task_dict: dict[str, Any]):
|
||||
"""
|
||||
@@ -699,14 +697,7 @@ class MemoryEngine(MemoryEngineInterface):
|
||||
|
||||
Returns:
|
||||
dict with status and optional error message
|
||||
|
||||
Note:
|
||||
Returns unhealthy until initialize() has completed successfully.
|
||||
"""
|
||||
# Not healthy until fully initialized
|
||||
if not self._initialized:
|
||||
return {"status": "unhealthy", "reason": "not_initialized"}
|
||||
|
||||
try:
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
@@ -2829,16 +2820,13 @@ Guidelines:
|
||||
Handler for form opinion tasks.
|
||||
|
||||
Args:
|
||||
task_dict: Dict with keys: 'bank_id', 'answer_text', 'query', 'tenant_id'
|
||||
task_dict: Dict with keys: 'bank_id', 'answer_text', 'query'
|
||||
"""
|
||||
bank_id = task_dict["bank_id"]
|
||||
answer_text = task_dict["answer_text"]
|
||||
query = task_dict["query"]
|
||||
tenant_id = task_dict.get("tenant_id")
|
||||
|
||||
await self._extract_and_store_opinions_async(
|
||||
bank_id=bank_id, answer_text=answer_text, query=query, tenant_id=tenant_id
|
||||
)
|
||||
await self._extract_and_store_opinions_async(bank_id=bank_id, answer_text=answer_text, query=query)
|
||||
|
||||
async def _handle_reinforce_opinion(self, task_dict: dict[str, Any]):
|
||||
"""
|
||||
@@ -3083,8 +3071,6 @@ Guidelines:
|
||||
*,
|
||||
budget: Budget | None = None,
|
||||
context: str | None = None,
|
||||
max_tokens: int = 4096,
|
||||
response_schema: dict | None = None,
|
||||
request_context: "RequestContext",
|
||||
) -> ReflectResult:
|
||||
"""
|
||||
@@ -3096,22 +3082,19 @@ Guidelines:
|
||||
3. Retrieves existing opinions (bank's formed perspectives)
|
||||
4. Uses LLM to formulate an answer
|
||||
5. Extracts and stores any new opinions formed during reflection
|
||||
6. Optionally generates structured output based on response_schema
|
||||
7. Returns plain text answer and the facts used
|
||||
6. Returns plain text answer and the facts used
|
||||
|
||||
Args:
|
||||
bank_id: bank identifier
|
||||
query: Question to answer
|
||||
budget: Budget level for memory exploration (low=100, mid=300, high=600 units)
|
||||
context: Additional context string to include in LLM prompt (not used in recall)
|
||||
response_schema: Optional JSON Schema for structured output
|
||||
|
||||
Returns:
|
||||
ReflectResult containing:
|
||||
- text: Plain text answer (no markdown)
|
||||
- based_on: Dict with 'world', 'experience', and 'opinion' fact lists (MemoryFact objects)
|
||||
- new_opinions: List of newly formed opinions
|
||||
- structured_output: Optional dict if response_schema was provided
|
||||
"""
|
||||
# Use cached LLM config
|
||||
if self._llm_config is None:
|
||||
@@ -3189,53 +3172,21 @@ Guidelines:
|
||||
log_buffer.append(f"[REFLECT {reflect_id}] Prompt: {len(prompt)} chars")
|
||||
|
||||
system_message = think_utils.get_system_message(disposition)
|
||||
messages = [{"role": "system", "content": system_message}, {"role": "user", "content": prompt}]
|
||||
|
||||
# Prepare response_format if schema provided
|
||||
response_format = None
|
||||
if response_schema is not None:
|
||||
# Wrapper class to provide Pydantic-like interface for raw JSON schemas
|
||||
class JsonSchemaWrapper:
|
||||
def __init__(self, schema: dict):
|
||||
self._schema = schema
|
||||
|
||||
def model_json_schema(self):
|
||||
return self._schema
|
||||
|
||||
response_format = JsonSchemaWrapper(response_schema)
|
||||
|
||||
llm_start = time.time()
|
||||
result = await self._llm_config.call(
|
||||
messages=messages,
|
||||
scope="memory_reflect",
|
||||
max_completion_tokens=max_tokens,
|
||||
response_format=response_format,
|
||||
skip_validation=True if response_format else False,
|
||||
# Don't enforce strict_schema - not all providers support it and may retry forever
|
||||
# Soft enforcement (schema in prompt + json_object mode) is sufficient
|
||||
strict_schema=False,
|
||||
answer_text = await self._llm_config.call(
|
||||
messages=[{"role": "system", "content": system_message}, {"role": "user", "content": prompt}],
|
||||
scope="memory_think",
|
||||
temperature=0.9,
|
||||
max_completion_tokens=1000,
|
||||
)
|
||||
llm_time = time.time() - llm_start
|
||||
|
||||
# Handle response based on whether structured output was requested
|
||||
if response_schema is not None:
|
||||
structured_output = result
|
||||
answer_text = "" # Empty for backward compatibility
|
||||
log_buffer.append(f"[REFLECT {reflect_id}] Structured output generated")
|
||||
else:
|
||||
structured_output = None
|
||||
answer_text = result.strip()
|
||||
answer_text = answer_text.strip()
|
||||
|
||||
# Submit form_opinion task for background processing
|
||||
# Pass tenant_id from request context for internal authentication in background task
|
||||
await self._task_backend.submit_task(
|
||||
{
|
||||
"type": "form_opinion",
|
||||
"bank_id": bank_id,
|
||||
"answer_text": answer_text,
|
||||
"query": query,
|
||||
"tenant_id": getattr(request_context, "tenant_id", None) if request_context else None,
|
||||
}
|
||||
{"type": "form_opinion", "bank_id": bank_id, "answer_text": answer_text, "query": query}
|
||||
)
|
||||
|
||||
total_time = time.time() - reflect_start
|
||||
@@ -3249,7 +3200,6 @@ Guidelines:
|
||||
text=answer_text,
|
||||
based_on={"world": world_results, "experience": agent_results, "opinion": opinion_results},
|
||||
new_opinions=[], # Opinions are being extracted asynchronously
|
||||
structured_output=structured_output,
|
||||
)
|
||||
|
||||
# Call post-operation hook if validator is configured
|
||||
@@ -3273,9 +3223,7 @@ Guidelines:
|
||||
|
||||
return result
|
||||
|
||||
async def _extract_and_store_opinions_async(
|
||||
self, bank_id: str, answer_text: str, query: str, tenant_id: str | None = None
|
||||
):
|
||||
async def _extract_and_store_opinions_async(self, bank_id: str, answer_text: str, query: str):
|
||||
"""
|
||||
Background task to extract and store opinions from think response.
|
||||
|
||||
@@ -3285,7 +3233,6 @@ Guidelines:
|
||||
bank_id: bank IDentifier
|
||||
answer_text: The generated answer text
|
||||
query: The original query
|
||||
tenant_id: Tenant identifier for internal authentication
|
||||
"""
|
||||
try:
|
||||
# Extract opinions from the answer
|
||||
@@ -3296,11 +3243,10 @@ Guidelines:
|
||||
from datetime import datetime
|
||||
|
||||
current_time = datetime.now(UTC)
|
||||
# Use internal context with tenant_id for background authentication
|
||||
# Extension can check internal=True to bypass normal auth
|
||||
# Use internal request context for background tasks
|
||||
from hindsight_api.models import RequestContext
|
||||
|
||||
internal_context = RequestContext(tenant_id=tenant_id, internal=True)
|
||||
internal_context = RequestContext()
|
||||
for opinion in new_opinions:
|
||||
await self.retain_async(
|
||||
bank_id=bank_id,
|
||||
@@ -3621,7 +3567,7 @@ Guidelines:
|
||||
self,
|
||||
bank_id: str,
|
||||
entity_ids: list[str],
|
||||
min_facts: int | None = None,
|
||||
min_facts: int = 5,
|
||||
conn=None,
|
||||
request_context: "RequestContext | None" = None,
|
||||
) -> None:
|
||||
@@ -3633,16 +3579,12 @@ Guidelines:
|
||||
Args:
|
||||
bank_id: Bank identifier
|
||||
entity_ids: List of entity IDs to process
|
||||
min_facts: Minimum facts required to regenerate observations (uses config default if None)
|
||||
min_facts: Minimum facts required to regenerate observations
|
||||
conn: Optional database connection (for transactional atomicity)
|
||||
"""
|
||||
if not bank_id or not entity_ids:
|
||||
return
|
||||
|
||||
# Use config default if min_facts not specified
|
||||
if min_facts is None:
|
||||
min_facts = get_config().observation_min_facts
|
||||
|
||||
# Convert to UUIDs
|
||||
entity_uuids = [uuid.UUID(eid) if isinstance(eid, str) else eid for eid in entity_ids]
|
||||
|
||||
@@ -3735,87 +3677,90 @@ Guidelines:
|
||||
task_dict: Dict with 'bank_id' and either:
|
||||
- 'entity_ids' (list): Process multiple entities
|
||||
- 'entity_id', 'entity_name': Process single entity (legacy)
|
||||
|
||||
Raises:
|
||||
ValueError: If required fields are missing
|
||||
Exception: Any exception from regenerate_entity_observations (propagates to execute_task for retry)
|
||||
"""
|
||||
bank_id = task_dict.get("bank_id")
|
||||
# Use internal request context for background tasks
|
||||
from hindsight_api.models import RequestContext
|
||||
try:
|
||||
bank_id = task_dict.get("bank_id")
|
||||
# Use internal request context for background tasks
|
||||
from hindsight_api.models import RequestContext
|
||||
|
||||
internal_context = RequestContext()
|
||||
internal_context = RequestContext()
|
||||
|
||||
# New format: multiple entity_ids
|
||||
if "entity_ids" in task_dict:
|
||||
entity_ids = task_dict.get("entity_ids", [])
|
||||
min_facts = task_dict.get("min_facts", 5)
|
||||
# New format: multiple entity_ids
|
||||
if "entity_ids" in task_dict:
|
||||
entity_ids = task_dict.get("entity_ids", [])
|
||||
min_facts = task_dict.get("min_facts", 5)
|
||||
|
||||
if not bank_id or not entity_ids:
|
||||
raise ValueError(f"[OBSERVATIONS] Missing required fields in task: {task_dict}")
|
||||
if not bank_id or not entity_ids:
|
||||
logger.error(f"[OBSERVATIONS] Missing required fields in task: {task_dict}")
|
||||
return
|
||||
|
||||
# Process each entity
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
for entity_id in entity_ids:
|
||||
try:
|
||||
# Fetch entity name and check fact count
|
||||
import uuid as uuid_module
|
||||
# Process each entity
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
for entity_id in entity_ids:
|
||||
try:
|
||||
# Fetch entity name and check fact count
|
||||
import uuid as uuid_module
|
||||
|
||||
entity_uuid = uuid_module.UUID(entity_id) if isinstance(entity_id, str) else entity_id
|
||||
entity_uuid = uuid_module.UUID(entity_id) if isinstance(entity_id, str) else entity_id
|
||||
|
||||
# First check if entity exists
|
||||
entity_exists = await conn.fetchrow(
|
||||
f"SELECT canonical_name FROM {fq_table('entities')} WHERE id = $1 AND bank_id = $2",
|
||||
entity_uuid,
|
||||
bank_id,
|
||||
)
|
||||
# First check if entity exists
|
||||
entity_exists = await conn.fetchrow(
|
||||
f"SELECT canonical_name FROM {fq_table('entities')} WHERE id = $1 AND bank_id = $2",
|
||||
entity_uuid,
|
||||
bank_id,
|
||||
)
|
||||
|
||||
if not entity_exists:
|
||||
logger.debug(f"[OBSERVATIONS] Entity {entity_id} not yet in bank {bank_id}, skipping")
|
||||
if not entity_exists:
|
||||
logger.debug(f"[OBSERVATIONS] Entity {entity_id} not yet in bank {bank_id}, skipping")
|
||||
continue
|
||||
|
||||
entity_name = entity_exists["canonical_name"]
|
||||
|
||||
# Count facts linked to this entity
|
||||
fact_count = (
|
||||
await conn.fetchval(
|
||||
f"SELECT COUNT(*) FROM {fq_table('unit_entities')} WHERE entity_id = $1",
|
||||
entity_uuid,
|
||||
)
|
||||
or 0
|
||||
)
|
||||
|
||||
# Only regenerate if entity has enough facts
|
||||
if fact_count >= min_facts:
|
||||
await self.regenerate_entity_observations(
|
||||
bank_id, entity_id, entity_name, version=None, request_context=internal_context
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"[OBSERVATIONS] Skipping {entity_name} ({fact_count} facts < {min_facts} threshold)"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[OBSERVATIONS] Error processing entity {entity_id}: {e}")
|
||||
continue
|
||||
|
||||
entity_name = entity_exists["canonical_name"]
|
||||
# Legacy format: single entity
|
||||
else:
|
||||
entity_id = task_dict.get("entity_id")
|
||||
entity_name = task_dict.get("entity_name")
|
||||
version = task_dict.get("version")
|
||||
|
||||
# Count facts linked to this entity
|
||||
fact_count = (
|
||||
await conn.fetchval(
|
||||
f"SELECT COUNT(*) FROM {fq_table('unit_entities')} WHERE entity_id = $1",
|
||||
entity_uuid,
|
||||
)
|
||||
or 0
|
||||
)
|
||||
if not all([bank_id, entity_id, entity_name]):
|
||||
logger.error(f"[OBSERVATIONS] Missing required fields in task: {task_dict}")
|
||||
return
|
||||
|
||||
# Only regenerate if entity has enough facts
|
||||
if fact_count >= min_facts:
|
||||
await self.regenerate_entity_observations(
|
||||
bank_id, entity_id, entity_name, version=None, request_context=internal_context
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"[OBSERVATIONS] Skipping {entity_name} ({fact_count} facts < {min_facts} threshold)"
|
||||
)
|
||||
# Type assertions after validation
|
||||
assert isinstance(bank_id, str) and isinstance(entity_id, str) and isinstance(entity_name, str)
|
||||
await self.regenerate_entity_observations(
|
||||
bank_id, entity_id, entity_name, version=version, request_context=internal_context
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Log but continue processing other entities - individual entity failures
|
||||
# shouldn't fail the whole batch
|
||||
logger.error(f"[OBSERVATIONS] Error processing entity {entity_id}: {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f"[OBSERVATIONS] Error regenerating observations: {e}")
|
||||
import traceback
|
||||
|
||||
# Legacy format: single entity
|
||||
else:
|
||||
entity_id = task_dict.get("entity_id")
|
||||
entity_name = task_dict.get("entity_name")
|
||||
version = task_dict.get("version")
|
||||
|
||||
if not all([bank_id, entity_id, entity_name]):
|
||||
raise ValueError(f"[OBSERVATIONS] Missing required fields in task: {task_dict}")
|
||||
|
||||
# Type assertions after validation
|
||||
assert isinstance(bank_id, str) and isinstance(entity_id, str) and isinstance(entity_name, str)
|
||||
await self.regenerate_entity_observations(
|
||||
bank_id, entity_id, entity_name, version=version, request_context=internal_context
|
||||
)
|
||||
traceback.print_exc()
|
||||
|
||||
# =========================================================================
|
||||
# Statistics & Operations (for HTTP API layer)
|
||||
|
||||
@@ -123,8 +123,7 @@ class ReflectResult(BaseModel):
|
||||
Result from a reflect operation.
|
||||
|
||||
Contains the formulated answer, the facts it was based on (organized by type),
|
||||
any new opinions that were formed during the reflection process, and optionally
|
||||
structured output if a response schema was provided.
|
||||
and any new opinions that were formed during the reflection process.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
@@ -146,7 +145,6 @@ class ReflectResult(BaseModel):
|
||||
"opinion": [],
|
||||
},
|
||||
"new_opinions": ["Machine learning has great potential in healthcare"],
|
||||
"structured_output": {"summary": "ML in healthcare", "confidence": 0.9},
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -156,10 +154,6 @@ class ReflectResult(BaseModel):
|
||||
description="Facts used to formulate the answer, organized by type (world, experience, opinion)"
|
||||
)
|
||||
new_opinions: list[str] = Field(default_factory=list, description="List of newly formed opinions during reflection")
|
||||
structured_output: dict[str, Any] | None = Field(
|
||||
default=None,
|
||||
description="Structured output parsed according to the provided response schema. Only present when response_schema was provided.",
|
||||
)
|
||||
|
||||
|
||||
class Opinion(BaseModel):
|
||||
|
||||
@@ -13,23 +13,16 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def process_entities_batch(
|
||||
entity_resolver,
|
||||
conn,
|
||||
bank_id: str,
|
||||
unit_ids: list[str],
|
||||
facts: list[ProcessedFact],
|
||||
log_buffer: list[str] = None,
|
||||
user_entities_per_content: dict[int, list[dict]] = None,
|
||||
entity_resolver, conn, bank_id: str, unit_ids: list[str], facts: list[ProcessedFact], log_buffer: list[str] = None
|
||||
) -> list[EntityLink]:
|
||||
"""
|
||||
Process entities for all facts and create entity links.
|
||||
|
||||
This function:
|
||||
1. Extracts entity mentions from fact texts
|
||||
2. Merges user-provided entities with LLM-extracted entities
|
||||
3. Resolves entity names to canonical entities
|
||||
4. Creates entity records in the database
|
||||
5. Returns entity links ready for insertion
|
||||
2. Resolves entity names to canonical entities
|
||||
3. Creates entity records in the database
|
||||
4. Returns entity links ready for insertion
|
||||
|
||||
Args:
|
||||
entity_resolver: EntityResolver instance for entity resolution
|
||||
@@ -38,7 +31,6 @@ async def process_entities_batch(
|
||||
unit_ids: List of unit IDs (same length as facts)
|
||||
facts: List of ProcessedFact objects
|
||||
log_buffer: Optional buffer for detailed logging
|
||||
user_entities_per_content: Dict mapping content_index to list of user-provided entities
|
||||
|
||||
Returns:
|
||||
List of EntityLink objects for batch insertion
|
||||
@@ -49,35 +41,14 @@ async def process_entities_batch(
|
||||
if len(unit_ids) != len(facts):
|
||||
raise ValueError(f"Mismatch between unit_ids ({len(unit_ids)}) and facts ({len(facts)})")
|
||||
|
||||
user_entities_per_content = user_entities_per_content or {}
|
||||
|
||||
# Extract data for link_utils function
|
||||
fact_texts = [fact.fact_text for fact in facts]
|
||||
# Use occurred_start if available, otherwise use mentioned_at for entity timestamps
|
||||
fact_dates = [fact.occurred_start if fact.occurred_start is not None else fact.mentioned_at for fact in facts]
|
||||
|
||||
# Convert EntityRef objects to dict format and merge with user-provided entities
|
||||
entities_per_fact = []
|
||||
for fact in facts:
|
||||
# Start with LLM-extracted entities
|
||||
llm_entities = [{"text": entity.name, "type": "CONCEPT"} for entity in (fact.entities or [])]
|
||||
|
||||
# Get user entities for this content (use content_index from fact)
|
||||
user_entities = user_entities_per_content.get(fact.content_index, [])
|
||||
|
||||
# Merge with case-insensitive deduplication
|
||||
seen_texts = {e["text"].lower() for e in llm_entities}
|
||||
for user_entity in user_entities:
|
||||
if user_entity["text"].lower() not in seen_texts:
|
||||
llm_entities.append(
|
||||
{
|
||||
"text": user_entity["text"],
|
||||
"type": user_entity.get("type", "CONCEPT"),
|
||||
}
|
||||
)
|
||||
seen_texts.add(user_entity["text"].lower())
|
||||
|
||||
entities_per_fact.append(llm_entities)
|
||||
# Convert EntityRef objects to dict format expected by link_utils
|
||||
entities_per_fact = [
|
||||
[{"text": entity.name, "type": "CONCEPT"} for entity in (fact.entities or [])] for fact in facts
|
||||
]
|
||||
|
||||
# Use existing link_utils function for entity processing
|
||||
entity_links = await link_utils.extract_entities_batch_optimized(
|
||||
|
||||
@@ -17,44 +17,6 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from ..llm_wrapper import LLMConfig, OutputTooLongError
|
||||
|
||||
|
||||
def _infer_temporal_date(fact_text: str, event_date: datetime) -> str | None:
|
||||
"""
|
||||
Infer a temporal date from fact text when LLM didn't provide occurred_start.
|
||||
|
||||
This is a fallback for when the LLM fails to extract temporal information
|
||||
from relative time expressions like "last night", "yesterday", etc.
|
||||
"""
|
||||
import re
|
||||
|
||||
fact_lower = fact_text.lower()
|
||||
|
||||
# Map relative time expressions to day offsets
|
||||
temporal_patterns = {
|
||||
r"\blast night\b": -1,
|
||||
r"\byesterday\b": -1,
|
||||
r"\btoday\b": 0,
|
||||
r"\bthis morning\b": 0,
|
||||
r"\bthis afternoon\b": 0,
|
||||
r"\bthis evening\b": 0,
|
||||
r"\btonigh?t\b": 0,
|
||||
r"\btomorrow\b": 1,
|
||||
r"\blast week\b": -7,
|
||||
r"\bthis week\b": 0,
|
||||
r"\bnext week\b": 7,
|
||||
r"\blast month\b": -30,
|
||||
r"\bthis month\b": 0,
|
||||
r"\bnext month\b": 30,
|
||||
}
|
||||
|
||||
for pattern, offset_days in temporal_patterns.items():
|
||||
if re.search(pattern, fact_lower):
|
||||
target_date = event_date + timedelta(days=offset_days)
|
||||
return target_date.replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
|
||||
|
||||
# If no relative time expression found, return None
|
||||
return None
|
||||
|
||||
|
||||
def _sanitize_text(text: str) -> str:
|
||||
"""
|
||||
Sanitize text by removing invalid Unicode surrogate characters.
|
||||
@@ -714,18 +676,13 @@ Text:
|
||||
if fact_kind == "event":
|
||||
occurred_start = get_value("occurred_start")
|
||||
occurred_end = get_value("occurred_end")
|
||||
|
||||
# If LLM didn't set temporal fields, try to extract them from the fact text
|
||||
if not occurred_start:
|
||||
fact_data["occurred_start"] = _infer_temporal_date(combined_text, event_date)
|
||||
else:
|
||||
if occurred_start:
|
||||
fact_data["occurred_start"] = occurred_start
|
||||
|
||||
# For point events: if occurred_end not set, default to occurred_start
|
||||
if occurred_end:
|
||||
fact_data["occurred_end"] = occurred_end
|
||||
elif fact_data.get("occurred_start"):
|
||||
fact_data["occurred_end"] = fact_data["occurred_start"]
|
||||
# For point events: if occurred_end not set, default to occurred_start
|
||||
if occurred_end:
|
||||
fact_data["occurred_end"] = occurred_end
|
||||
else:
|
||||
fact_data["occurred_end"] = occurred_start
|
||||
|
||||
# Add entities if present (validate as Entity objects)
|
||||
# LLM sometimes returns strings instead of {"text": "..."} format
|
||||
|
||||
@@ -9,7 +9,6 @@ import time
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from ...config import get_config
|
||||
from ..memory_engine import fq_table
|
||||
from ..search import observation_utils
|
||||
from . import embedding_utils
|
||||
@@ -50,9 +49,8 @@ async def regenerate_observations_batch(
|
||||
entity_links: Entity links from this batch
|
||||
log_buffer: Optional log buffer for timing
|
||||
"""
|
||||
config = get_config()
|
||||
TOP_N_ENTITIES = config.observation_top_entities
|
||||
MIN_FACTS_THRESHOLD = config.observation_min_facts
|
||||
TOP_N_ENTITIES = 5
|
||||
MIN_FACTS_THRESHOLD = 5
|
||||
|
||||
if not entity_links:
|
||||
return
|
||||
|
||||
@@ -91,7 +91,6 @@ async def retain_batch(
|
||||
context=item.get("context", ""),
|
||||
event_date=item.get("event_date") or utcnow(),
|
||||
metadata=item.get("metadata", {}),
|
||||
entities=item.get("entities", []),
|
||||
)
|
||||
contents.append(content)
|
||||
|
||||
@@ -107,62 +106,9 @@ async def retain_batch(
|
||||
)
|
||||
|
||||
if not extracted_facts:
|
||||
# Still need to create document if document_id was provided
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
async with conn.transaction():
|
||||
await fact_storage.ensure_bank_exists(conn, bank_id)
|
||||
|
||||
# Handle document tracking even with no facts
|
||||
if document_id:
|
||||
combined_content = "\n".join([c.get("content", "") for c in contents_dicts])
|
||||
retain_params = {}
|
||||
if contents_dicts:
|
||||
first_item = contents_dicts[0]
|
||||
if first_item.get("context"):
|
||||
retain_params["context"] = first_item["context"]
|
||||
if first_item.get("event_date"):
|
||||
retain_params["event_date"] = (
|
||||
first_item["event_date"].isoformat()
|
||||
if hasattr(first_item["event_date"], "isoformat")
|
||||
else str(first_item["event_date"])
|
||||
)
|
||||
if first_item.get("metadata"):
|
||||
retain_params["metadata"] = first_item["metadata"]
|
||||
await fact_storage.handle_document_tracking(
|
||||
conn, bank_id, document_id, combined_content, is_first_batch, retain_params
|
||||
)
|
||||
else:
|
||||
# Check for per-item document_ids
|
||||
from collections import defaultdict
|
||||
|
||||
contents_by_doc = defaultdict(list)
|
||||
for idx, content_dict in enumerate(contents_dicts):
|
||||
doc_id = content_dict.get("document_id")
|
||||
if doc_id:
|
||||
contents_by_doc[doc_id].append((idx, content_dict))
|
||||
|
||||
for doc_id, doc_contents in contents_by_doc.items():
|
||||
combined_content = "\n".join([c.get("content", "") for _, c in doc_contents])
|
||||
retain_params = {}
|
||||
if doc_contents:
|
||||
first_item = doc_contents[0][1]
|
||||
if first_item.get("context"):
|
||||
retain_params["context"] = first_item["context"]
|
||||
if first_item.get("event_date"):
|
||||
retain_params["event_date"] = (
|
||||
first_item["event_date"].isoformat()
|
||||
if hasattr(first_item["event_date"], "isoformat")
|
||||
else str(first_item["event_date"])
|
||||
)
|
||||
if first_item.get("metadata"):
|
||||
retain_params["metadata"] = first_item["metadata"]
|
||||
await fact_storage.handle_document_tracking(
|
||||
conn, bank_id, doc_id, combined_content, is_first_batch, retain_params
|
||||
)
|
||||
|
||||
total_time = time.time() - start_time
|
||||
logger.info(
|
||||
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (document tracked, no facts)"
|
||||
f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (nothing to store)"
|
||||
)
|
||||
return [[] for _ in contents]
|
||||
|
||||
@@ -353,18 +299,8 @@ async def retain_batch(
|
||||
|
||||
# Process entities
|
||||
step_start = time.time()
|
||||
# Build map of content_index -> user entities for merging
|
||||
user_entities_per_content = {
|
||||
idx: content.entities for idx, content in enumerate(contents) if content.entities
|
||||
}
|
||||
entity_links = await entity_processing.process_entities_batch(
|
||||
entity_resolver,
|
||||
conn,
|
||||
bank_id,
|
||||
unit_ids,
|
||||
non_duplicate_facts,
|
||||
log_buffer,
|
||||
user_entities_per_content=user_entities_per_content,
|
||||
entity_resolver, conn, bank_id, unit_ids, non_duplicate_facts, log_buffer
|
||||
)
|
||||
log_buffer.append(f"[6] Process entities: {len(entity_links)} links in {time.time() - step_start:.3f}s")
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ class RetainContentDict(TypedDict, total=False):
|
||||
event_date: When the content occurred (optional, defaults to now)
|
||||
metadata: Custom key-value metadata (optional)
|
||||
document_id: Document ID for this content item (optional)
|
||||
entities: User-provided entities to merge with extracted entities (optional)
|
||||
"""
|
||||
|
||||
content: str # Required
|
||||
@@ -28,7 +27,6 @@ class RetainContentDict(TypedDict, total=False):
|
||||
event_date: datetime
|
||||
metadata: dict[str, str]
|
||||
document_id: str
|
||||
entities: list[dict[str, str]] # [{"text": "...", "type": "..."}]
|
||||
|
||||
|
||||
def _now_utc() -> datetime:
|
||||
@@ -48,7 +46,6 @@ class RetainContent:
|
||||
context: str = ""
|
||||
event_date: datetime = field(default_factory=_now_utc)
|
||||
metadata: dict[str, str] = field(default_factory=dict)
|
||||
entities: list[dict[str, str]] = field(default_factory=list) # User-provided entities
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -155,9 +152,6 @@ class ProcessedFact:
|
||||
# DB fields (set after insertion)
|
||||
unit_id: UUID | None = None
|
||||
|
||||
# Track which content this fact came from (for user entity merging)
|
||||
content_index: int = 0
|
||||
|
||||
@property
|
||||
def is_duplicate(self) -> bool:
|
||||
"""Check if this fact was marked as a duplicate."""
|
||||
@@ -200,7 +194,6 @@ class ProcessedFact:
|
||||
entities=entities,
|
||||
causal_relations=extracted_fact.causal_relations,
|
||||
chunk_id=chunk_id,
|
||||
content_index=extracted_fact.content_index,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -98,14 +98,7 @@ class DefaultExtensionContext(ExtensionContext):
|
||||
"""Run migrations for a specific schema."""
|
||||
from hindsight_api.migrations import run_migrations
|
||||
|
||||
# Prefer getting URL from memory engine (handles pg0 case where URL is set after init)
|
||||
db_url = self._database_url
|
||||
if self._memory_engine is not None:
|
||||
engine_url = getattr(self._memory_engine, "db_url", None)
|
||||
if engine_url:
|
||||
db_url = engine_url
|
||||
|
||||
run_migrations(db_url, schema=schema)
|
||||
run_migrations(self._database_url, schema=schema)
|
||||
|
||||
def get_memory_engine(self) -> "MemoryEngineInterface":
|
||||
"""Get the memory engine interface."""
|
||||
|
||||
@@ -17,9 +17,8 @@ if TYPE_CHECKING:
|
||||
class OperationValidationError(Exception):
|
||||
"""Raised when an operation fails validation."""
|
||||
|
||||
def __init__(self, reason: str, status_code: int = 403):
|
||||
def __init__(self, reason: str):
|
||||
self.reason = reason
|
||||
self.status_code = status_code
|
||||
super().__init__(f"Operation validation failed: {reason}")
|
||||
|
||||
|
||||
@@ -29,7 +28,6 @@ class ValidationResult:
|
||||
|
||||
allowed: bool
|
||||
reason: str | None = None
|
||||
status_code: int = 403 # Default to Forbidden
|
||||
|
||||
@classmethod
|
||||
def accept(cls) -> "ValidationResult":
|
||||
@@ -37,9 +35,9 @@ class ValidationResult:
|
||||
return cls(allowed=True)
|
||||
|
||||
@classmethod
|
||||
def reject(cls, reason: str, status_code: int = 403) -> "ValidationResult":
|
||||
"""Create a rejected validation result with a reason and HTTP status code."""
|
||||
return cls(allowed=False, reason=reason, status_code=status_code)
|
||||
def reject(cls, reason: str) -> "ValidationResult":
|
||||
"""Create a rejected validation result with a reason."""
|
||||
return cls(allowed=False, reason=reason)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -31,7 +31,6 @@ from .daemon import (
|
||||
IdleTimeoutMiddleware,
|
||||
daemonize,
|
||||
)
|
||||
from .extensions import DefaultExtensionContext, OperationValidatorExtension, TenantExtension, load_extension
|
||||
|
||||
# Filter deprecation warnings from third-party libraries
|
||||
warnings.filterwarnings("ignore", message="websockets.legacy is deprecated")
|
||||
@@ -169,8 +168,6 @@ def main():
|
||||
llm_api_key=config.llm_api_key,
|
||||
llm_model=config.llm_model,
|
||||
llm_base_url=config.llm_base_url,
|
||||
llm_max_concurrent=config.llm_max_concurrent,
|
||||
llm_timeout=config.llm_timeout,
|
||||
embeddings_provider=config.embeddings_provider,
|
||||
embeddings_local_model=config.embeddings_local_model,
|
||||
embeddings_tei_url=config.embeddings_tei_url,
|
||||
@@ -182,8 +179,6 @@ def main():
|
||||
log_level=args.log_level,
|
||||
mcp_enabled=config.mcp_enabled,
|
||||
graph_retriever=config.graph_retriever,
|
||||
observation_min_facts=config.observation_min_facts,
|
||||
observation_top_entities=config.observation_top_entities,
|
||||
skip_llm_verification=config.skip_llm_verification,
|
||||
lazy_reranker=config.lazy_reranker,
|
||||
)
|
||||
@@ -196,31 +191,8 @@ def main():
|
||||
signal.signal(signal.SIGINT, _signal_handler)
|
||||
signal.signal(signal.SIGTERM, _signal_handler)
|
||||
|
||||
# Load operation validator extension if configured
|
||||
operation_validator = load_extension("OPERATION_VALIDATOR", OperationValidatorExtension)
|
||||
if operation_validator:
|
||||
import logging
|
||||
|
||||
logging.info(f"Loaded operation validator: {operation_validator.__class__.__name__}")
|
||||
|
||||
# Load tenant extension if configured
|
||||
tenant_extension = load_extension("TENANT", TenantExtension)
|
||||
if tenant_extension:
|
||||
import logging
|
||||
|
||||
logging.info(f"Loaded tenant extension: {tenant_extension.__class__.__name__}")
|
||||
|
||||
# Create MemoryEngine (reads configuration from environment)
|
||||
_memory = MemoryEngine(operation_validator=operation_validator, tenant_extension=tenant_extension)
|
||||
|
||||
# Set extension context on tenant extension (needed for schema provisioning)
|
||||
if tenant_extension:
|
||||
extension_context = DefaultExtensionContext(
|
||||
database_url=config.database_url,
|
||||
memory_engine=_memory,
|
||||
)
|
||||
tenant_extension.set_context(extension_context)
|
||||
logging.info("Extension context set on tenant extension")
|
||||
_memory = MemoryEngine()
|
||||
|
||||
# Create FastAPI app
|
||||
app = create_app(
|
||||
|
||||
@@ -18,9 +18,6 @@ class RequestContext:
|
||||
"""
|
||||
|
||||
api_key: str | None = None
|
||||
api_key_id: str | None = None # UUID of the API key used for authentication
|
||||
tenant_id: str | None = None # Tenant identifier (set by extension after auth)
|
||||
internal: bool = False # True for background/internal operations (not user-visible)
|
||||
|
||||
|
||||
from pgvector.sqlalchemy import Vector
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "hindsight-api"
|
||||
version = "0.1.16"
|
||||
version = "0.1.12"
|
||||
description = "Hindsight: Agent Memory That Works Like Human Memory"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
@@ -37,7 +37,6 @@ dependencies = [
|
||||
"opentelemetry-exporter-prometheus>=0.41b0",
|
||||
"dateparser>=1.2.2",
|
||||
"google-genai>=1.0.0",
|
||||
"anthropic>=0.40.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -402,8 +402,6 @@ I'm planning to visit Tokyo next month.
|
||||
|
||||
Ideally: If conversation is on August 14, 2023 and text says "last night",
|
||||
the date field should be August 13. We accept 13 or 14 as LLM may vary.
|
||||
|
||||
Retries up to 3 times to account for LLM inconsistencies.
|
||||
"""
|
||||
text = """
|
||||
Melanie: Hey Caroline! Last night was amazing! We celebrated my daughter's birthday
|
||||
@@ -412,69 +410,41 @@ with a concert surrounded by music, joy and the warm summer breeze.
|
||||
|
||||
context = "Conversation between Melanie and Caroline"
|
||||
llm_config = LLMConfig.for_memory()
|
||||
|
||||
event_date = datetime(2023, 8, 14, 14, 24)
|
||||
|
||||
last_error = None
|
||||
max_retries = 3
|
||||
facts, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
event_date=event_date,
|
||||
context=context,
|
||||
llm_config=llm_config,
|
||||
agent_name="Melanie"
|
||||
)
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
facts, _ = await extract_facts_from_text(
|
||||
text=text,
|
||||
event_date=event_date,
|
||||
context=context,
|
||||
llm_config=llm_config,
|
||||
agent_name="Melanie"
|
||||
)
|
||||
assert len(facts) > 0, "Should extract at least one fact"
|
||||
|
||||
assert len(facts) > 0, "Should extract at least one fact"
|
||||
birthday_fact = None
|
||||
for fact in facts:
|
||||
if "birthday" in fact.fact.lower() or "concert" in fact.fact.lower():
|
||||
birthday_fact = fact
|
||||
break
|
||||
|
||||
birthday_fact = None
|
||||
for fact in facts:
|
||||
if "birthday" in fact.fact.lower() or "concert" in fact.fact.lower():
|
||||
birthday_fact = fact
|
||||
break
|
||||
assert birthday_fact is not None, "Should extract fact about birthday celebration"
|
||||
|
||||
assert birthday_fact is not None, "Should extract fact about birthday celebration"
|
||||
fact_date_str = birthday_fact.occurred_start
|
||||
assert fact_date_str is not None, "occurred_start should not be None for temporal events"
|
||||
|
||||
fact_date_str = birthday_fact.occurred_start
|
||||
assert fact_date_str is not None, "occurred_start should not be None for temporal events"
|
||||
if 'T' in fact_date_str:
|
||||
fact_date = datetime.fromisoformat(fact_date_str.replace('Z', '+00:00'))
|
||||
else:
|
||||
fact_date = datetime.fromisoformat(fact_date_str)
|
||||
|
||||
if 'T' in fact_date_str:
|
||||
fact_date = datetime.fromisoformat(fact_date_str.replace('Z', '+00:00'))
|
||||
else:
|
||||
fact_date = datetime.fromisoformat(fact_date_str)
|
||||
|
||||
assert fact_date.year == 2023, "Year should be 2023"
|
||||
assert fact_date.month == 8, "Month should be August"
|
||||
# Accept day 13 (ideal: last night) or 14 (conversation date) as valid
|
||||
assert fact_date.day in (13, 14), (
|
||||
f"Day should be 13 or 14 (around Aug 14 event), but got {fact_date.day}."
|
||||
)
|
||||
|
||||
# If we reach here, test passed
|
||||
return
|
||||
|
||||
except AssertionError as e:
|
||||
last_error = e
|
||||
if attempt < max_retries - 1:
|
||||
print(f"Test attempt {attempt + 1} failed: {e}. Retrying...")
|
||||
continue
|
||||
else:
|
||||
# Last attempt failed, re-raise the error
|
||||
raise e
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
if attempt < max_retries - 1:
|
||||
print(f"Test attempt {attempt + 1} failed with exception: {e}. Retrying...")
|
||||
continue
|
||||
else:
|
||||
# Last attempt failed, re-raise the error
|
||||
raise e
|
||||
|
||||
# Should not reach here, but just in case
|
||||
if last_error:
|
||||
raise last_error
|
||||
assert fact_date.year == 2023, "Year should be 2023"
|
||||
assert fact_date.month == 8, "Month should be August"
|
||||
# Accept day 13 (ideal: last night) or 14 (conversation date) as valid
|
||||
assert fact_date.day in (13, 14), (
|
||||
f"Day should be 13 or 14 (around Aug 14 event), but got {fact_date.day}."
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_date_field_calculation_yesterday(self):
|
||||
|
||||
@@ -428,66 +428,6 @@ async def test_document_deletion(api_client):
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_document_deletion_with_slashes_in_id(api_client):
|
||||
"""
|
||||
Test document deletion when document_id contains forward slashes.
|
||||
|
||||
Regression test for https://github.com/vectorize-io/hindsight/issues/92
|
||||
|
||||
Document IDs with slashes (e.g., "folder/file.md") should work correctly
|
||||
for all operations including creation, listing, retrieval, and deletion.
|
||||
"""
|
||||
import urllib.parse
|
||||
|
||||
test_bank_id = f"doc_slash_test_{datetime.now().timestamp()}"
|
||||
document_id_with_slash = "reports/quarterly/q1-2024.md"
|
||||
|
||||
try:
|
||||
# 1. Create a document with slashes in its ID
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
"content": "The Q1 2024 report shows significant growth in user engagement.",
|
||||
"context": "quarterly report",
|
||||
"document_id": document_id_with_slash
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to create document: {response.text}"
|
||||
|
||||
# 2. Verify document exists via list endpoint
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/documents")
|
||||
assert response.status_code == 200
|
||||
documents = response.json()
|
||||
doc_ids = [doc["id"] for doc in documents["items"]]
|
||||
assert document_id_with_slash in doc_ids, f"Document should be in list: {doc_ids}"
|
||||
|
||||
# 3. Delete the document (slashes in document_id should work with :path converter)
|
||||
encoded_doc_id = urllib.parse.quote(document_id_with_slash, safe="")
|
||||
response = await api_client.delete(
|
||||
f"/v1/default/banks/{test_bank_id}/documents/{encoded_doc_id}"
|
||||
)
|
||||
assert response.status_code == 200, (
|
||||
f"Failed to delete document with slashes in ID. "
|
||||
f"Status: {response.status_code}, Response: {response.text}"
|
||||
)
|
||||
|
||||
# Verify document is deleted
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/documents")
|
||||
assert response.status_code == 200
|
||||
documents = response.json()
|
||||
doc_ids = [doc["id"] for doc in documents["items"]]
|
||||
assert document_id_with_slash not in doc_ids, "Document should be deleted"
|
||||
|
||||
finally:
|
||||
# Cleanup - delete the bank
|
||||
await api_client.delete(f"/v1/default/banks/{test_bank_id}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_retain(api_client):
|
||||
"""Test asynchronous retain functionality.
|
||||
@@ -668,167 +608,3 @@ async def test_async_retain_parallel(api_client):
|
||||
assert response.status_code == 200
|
||||
results = response.json()["results"]
|
||||
assert len(results) > 0, f"Should find memories for document {i}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reflect_structured_output(api_client):
|
||||
"""Test reflect endpoint with structured output via response_schema.
|
||||
|
||||
When response_schema is provided, the reflect endpoint should return
|
||||
both the natural language text response and a structured_output field
|
||||
containing the response parsed according to the provided JSON schema.
|
||||
"""
|
||||
test_bank_id = f"reflect_structured_test_{datetime.now().timestamp()}"
|
||||
|
||||
# Store some memories to reflect on
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
"content": "Alice is a senior machine learning engineer with 8 years of experience.",
|
||||
"context": "team member info"
|
||||
},
|
||||
{
|
||||
"content": "Bob is a junior data scientist who joined last month.",
|
||||
"context": "team member info"
|
||||
},
|
||||
{
|
||||
"content": "The team uses Python and TensorFlow for most projects.",
|
||||
"context": "tech stack"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Define a JSON schema for structured output
|
||||
response_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"role": {"type": "string"},
|
||||
"experience_level": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"technologies": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"summary": {"type": "string"}
|
||||
},
|
||||
"required": ["team_members", "summary"]
|
||||
}
|
||||
|
||||
# Call reflect with response_schema
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/reflect",
|
||||
json={
|
||||
"query": "Give me an overview of the team and their tech stack",
|
||||
"response_schema": response_schema
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
|
||||
# Verify text field exists (empty when using structured output)
|
||||
assert "text" in result
|
||||
assert result["text"] == ""
|
||||
|
||||
# Verify structured output exists and has expected structure
|
||||
assert "structured_output" in result
|
||||
assert result["structured_output"] is not None
|
||||
|
||||
structured = result["structured_output"]
|
||||
assert "team_members" in structured
|
||||
assert "summary" in structured
|
||||
assert isinstance(structured["team_members"], list)
|
||||
assert isinstance(structured["summary"], str)
|
||||
|
||||
# Verify team members have the expected fields
|
||||
if len(structured["team_members"]) > 0:
|
||||
member = structured["team_members"][0]
|
||||
assert "name" in member or "role" in member # At least some fields should be present
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reflect_without_structured_output(api_client):
|
||||
"""Test that reflect works normally without response_schema.
|
||||
|
||||
When response_schema is not provided, the structured_output field
|
||||
should be null/None in the response.
|
||||
"""
|
||||
test_bank_id = f"reflect_no_structured_test_{datetime.now().timestamp()}"
|
||||
|
||||
# Store a memory
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
"content": "The project deadline is next Friday.",
|
||||
"context": "project timeline"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Call reflect without response_schema
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/reflect",
|
||||
json={
|
||||
"query": "When is the project deadline?"
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
|
||||
# Verify response has text but structured_output is null
|
||||
assert "text" in result
|
||||
assert len(result["text"]) > 0
|
||||
assert result.get("structured_output") is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reflect_with_max_tokens(api_client):
|
||||
"""Test reflect endpoint with custom max_tokens parameter.
|
||||
|
||||
The max_tokens parameter controls the maximum tokens for the LLM response.
|
||||
"""
|
||||
test_bank_id = f"reflect_max_tokens_test_{datetime.now().timestamp()}"
|
||||
|
||||
# Store a memory
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
"content": "Python is a popular programming language for data science and machine learning.",
|
||||
"context": "tech"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Call reflect with custom max_tokens
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/reflect",
|
||||
json={
|
||||
"query": "What is Python used for?",
|
||||
"max_tokens": 500
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
|
||||
# Verify response has text
|
||||
assert "text" in result
|
||||
assert len(result["text"]) > 0
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Integration test for the MCP (Model Context Protocol) server.
|
||||
|
||||
Tests MCP endpoints by starting a FastAPI server with MCP enabled and using the MCP client.
|
||||
|
||||
Note: MCP server is integrated with the web server. These tests require HINDSIGHT_API_MCP_ENABLED=true.
|
||||
"""
|
||||
import asyncio
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
import httpx
|
||||
from mcp import ClientSession
|
||||
from mcp.client.sse import sse_client
|
||||
from hindsight_api.api import create_app
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def mcp_server(memory):
|
||||
"""Start the FastAPI app with MCP enabled and return the SSE URL."""
|
||||
# Memory is already initialized by the conftest fixture (with migrations)
|
||||
app = create_app(
|
||||
memory,
|
||||
initialize_memory=False,
|
||||
mcp_api_enabled=True
|
||||
)
|
||||
|
||||
# Use httpx to create a test server
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
# The MCP SSE endpoint is at /mcp/sse
|
||||
# We need to yield the base URL for sse_client to connect
|
||||
# However, sse_client expects a real URL, not a test client
|
||||
# So we'll start a real server on a random port
|
||||
pass
|
||||
|
||||
# For now, skip these tests as they require a real server
|
||||
# The sse_client doesn't work with ASGI test transport
|
||||
pytest.skip("MCP tests require a real running server. Run: HINDSIGHT_API_MCP_ENABLED=true uvicorn hindsight_api.api:app")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_server_tools_via_sse(mcp_server):
|
||||
"""Test MCP server tools via SSE transport using proper MCP client."""
|
||||
sse_url = mcp_server
|
||||
|
||||
async with sse_client(sse_url) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
|
||||
# Test 1: List tools
|
||||
tools_list = await session.list_tools()
|
||||
print(f"Tools: {tools_list}")
|
||||
tool_names = [t.name for t in tools_list.tools]
|
||||
assert "hindsight_search" in tool_names
|
||||
assert "hindsight_put" in tool_names
|
||||
|
||||
# Test 2: Call hindsight_put
|
||||
put_result = await session.call_tool(
|
||||
"hindsight_put",
|
||||
arguments={
|
||||
"content": "User loves Python programming",
|
||||
"context": "programming_preferences",
|
||||
"explanation": "Storing user's programming language preference"
|
||||
}
|
||||
)
|
||||
print(f"Put result: {put_result}")
|
||||
assert put_result is not None
|
||||
|
||||
# Wait a bit for indexing
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Test 3: Call hindsight_search
|
||||
search_result = await session.call_tool(
|
||||
"hindsight_search",
|
||||
arguments={
|
||||
"query": "What programming languages does the user like?",
|
||||
"max_tokens": 4096,
|
||||
"explanation": "Searching for programming preferences"
|
||||
}
|
||||
)
|
||||
print(f"Search result: {search_result}")
|
||||
assert search_result is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_concurrent_requests(mcp_server):
|
||||
"""Test multiple concurrent requests from a single session."""
|
||||
sse_url = mcp_server
|
||||
|
||||
async with sse_client(sse_url) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
|
||||
# Fire off 10 concurrent search requests from same session
|
||||
async def make_search(idx):
|
||||
try:
|
||||
result = await session.call_tool(
|
||||
"hindsight_search",
|
||||
arguments={
|
||||
"query": f"test query {idx}",
|
||||
"explanation": f"Concurrent test {idx}"
|
||||
}
|
||||
)
|
||||
return idx, "success", result
|
||||
except Exception as e:
|
||||
return idx, "error", str(e)
|
||||
|
||||
tasks = [make_search(i) for i in range(10)]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# Check results
|
||||
successes = 0
|
||||
failures = 0
|
||||
|
||||
for result in results:
|
||||
if isinstance(result, Exception):
|
||||
print(f"Request failed with exception: {result}")
|
||||
failures += 1
|
||||
else:
|
||||
idx, status, data = result
|
||||
if status == "success":
|
||||
successes += 1
|
||||
else:
|
||||
print(f"Request {idx} failed: {data}")
|
||||
failures += 1
|
||||
|
||||
print(f"Successes: {successes}, Failures: {failures}")
|
||||
|
||||
# We expect all requests to succeed
|
||||
assert successes >= 8, f"Too many failures: {failures}/10"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_race_condition_with_rapid_requests(mcp_server):
|
||||
"""Test rapid-fire requests with multiple sessions to trigger race condition."""
|
||||
sse_url = mcp_server
|
||||
|
||||
async def rapid_session_search(idx):
|
||||
"""Create a new session and immediately make a request."""
|
||||
try:
|
||||
async with sse_client(sse_url) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
|
||||
# Make request immediately after initialization
|
||||
result = await session.call_tool(
|
||||
"hindsight_search",
|
||||
arguments={
|
||||
"query": f"rapid query {idx}",
|
||||
"max_tokens": 2048
|
||||
}
|
||||
)
|
||||
return idx, "success", result
|
||||
except Exception as e:
|
||||
return idx, "error", str(e)
|
||||
|
||||
# Fire 20 requests with minimal delay, each with its own session
|
||||
tasks = [rapid_session_search(i) for i in range(20)]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
# Analyze results
|
||||
errors = []
|
||||
for idx, status, data in results:
|
||||
if status == "error":
|
||||
errors.append((idx, data))
|
||||
|
||||
if errors:
|
||||
print(f"Found {len(errors)} errors:")
|
||||
for idx, error_msg in errors:
|
||||
print(f" Request {idx}: {error_msg}")
|
||||
|
||||
# Most requests should succeed
|
||||
assert len(errors) < 5, f"Too many errors: {len(errors)}/20"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v", "-s"])
|
||||
@@ -173,107 +173,6 @@ async def test_regenerate_entity_observations(memory, request_context):
|
||||
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_manual_regenerate_with_few_facts(memory, request_context):
|
||||
"""
|
||||
Test that manual regeneration works even with fewer than 5 facts.
|
||||
|
||||
This is important because:
|
||||
- Automatic generation during retain requires MIN_FACTS_THRESHOLD (5)
|
||||
- But manual regeneration via API should work with any number of facts
|
||||
- The UI triggers manual regeneration, so it should work regardless of fact count
|
||||
"""
|
||||
bank_id = f"test_manual_regen_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store only 2 facts - below the automatic threshold
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Google as a senior software engineer.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
|
||||
request_context=request_context,
|
||||
)
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice loves hiking and outdoor photography.",
|
||||
context="hobbies",
|
||||
event_date=datetime(2024, 1, 16, tzinfo=timezone.utc),
|
||||
request_context=request_context,
|
||||
)
|
||||
|
||||
# Find the Alice entity
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
entity_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT id, canonical_name
|
||||
FROM entities
|
||||
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%alice%'
|
||||
LIMIT 1
|
||||
""",
|
||||
bank_id
|
||||
)
|
||||
|
||||
assert entity_row is not None, "Alice entity should have been extracted"
|
||||
|
||||
entity_id = str(entity_row['id'])
|
||||
entity_name = entity_row['canonical_name']
|
||||
|
||||
# Check fact count - should be < 5
|
||||
async with pool.acquire() as conn:
|
||||
fact_count = await conn.fetchval(
|
||||
"SELECT COUNT(*) FROM unit_entities WHERE entity_id = $1",
|
||||
entity_row['id']
|
||||
)
|
||||
|
||||
print(f"\n=== Manual Regeneration Test ===")
|
||||
print(f"Entity: {entity_name} (id: {entity_id})")
|
||||
print(f"Linked facts: {fact_count}")
|
||||
|
||||
# Verify we're testing with fewer than the automatic threshold
|
||||
assert fact_count < 5, f"Test requires < 5 facts, but entity has {fact_count}"
|
||||
|
||||
# Before regeneration - should have no observations (auto threshold not met)
|
||||
obs_before = await memory.get_entity_observations(bank_id, entity_id, limit=10, request_context=request_context)
|
||||
print(f"Observations before manual regenerate: {len(obs_before)}")
|
||||
|
||||
# Manually regenerate observations - this should work regardless of fact count
|
||||
created_ids = await memory.regenerate_entity_observations(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
entity_name=entity_name,
|
||||
request_context=request_context,
|
||||
)
|
||||
|
||||
print(f"Observations created by manual regenerate: {len(created_ids)}")
|
||||
|
||||
# Get observations after regeneration
|
||||
observations = await memory.get_entity_observations(bank_id, entity_id, limit=10, request_context=request_context)
|
||||
print(f"Observations after manual regenerate: {len(observations)}")
|
||||
for obs in observations:
|
||||
print(f" - {obs.text}")
|
||||
|
||||
# Manual regeneration should create observations even with < 5 facts
|
||||
assert len(observations) > 0, \
|
||||
f"Manual regeneration should create observations even with only {fact_count} facts. " \
|
||||
f"The LLM should synthesize at least 1 observation from the available facts."
|
||||
|
||||
# Verify observations contain relevant content
|
||||
obs_texts = " ".join([o.text.lower() for o in observations])
|
||||
assert any(keyword in obs_texts for keyword in ["google", "engineer", "hiking", "photography", "alice"]), \
|
||||
"Observations should contain relevant information about Alice"
|
||||
|
||||
print(f"✓ Manual regeneration works with {fact_count} facts (below automatic threshold of 5)")
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE bank_id = $1", bank_id)
|
||||
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_with_include_entities(memory, request_context):
|
||||
"""
|
||||
|
||||
@@ -1778,71 +1778,3 @@ async def test_temporal_links_within_same_batch(memory, request_context):
|
||||
|
||||
finally:
|
||||
await memory.delete_bank(bank_id, request_context=request_context)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_provided_entities(memory, request_context):
|
||||
"""
|
||||
Test that user-provided entities are merged with auto-extracted entities.
|
||||
|
||||
This tests the feature added in PR #91 where users can provide entities
|
||||
via the 'entities' field in the retain request. These should be combined
|
||||
with LLM-extracted entities, with case-insensitive deduplication.
|
||||
"""
|
||||
bank_id = f"test_user_entities_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store content with user-provided entities
|
||||
# The content mentions "Alice" which LLM might extract,
|
||||
# but we also provide "ProjectX" and "ACME Corp" which may not be in the text
|
||||
contents = [
|
||||
{
|
||||
"content": "Alice completed the quarterly report.",
|
||||
"context": "work update",
|
||||
"entities": [
|
||||
{"text": "ProjectX", "type": "PROJECT"},
|
||||
{"text": "ACME Corp", "type": "ORG"},
|
||||
{"text": "Alice"}, # May also be extracted by LLM (dedup test)
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
result = await memory.retain_batch_async(
|
||||
bank_id=bank_id,
|
||||
contents=contents,
|
||||
request_context=request_context,
|
||||
)
|
||||
|
||||
# Flatten the list of lists
|
||||
unit_ids = [uid for sublist in result for uid in sublist]
|
||||
assert len(unit_ids) > 0, "Should have created at least one fact"
|
||||
|
||||
logger.info(f"Created {len(unit_ids)} facts with user-provided entities")
|
||||
|
||||
# Query entity links to verify user-provided entities were stored
|
||||
async with memory._pool.acquire() as conn:
|
||||
# Get all entities linked to our facts via the unit_entities junction table
|
||||
entity_rows = await conn.fetch(
|
||||
"""
|
||||
SELECT DISTINCT e.canonical_name
|
||||
FROM entities e
|
||||
JOIN unit_entities ue ON e.id = ue.entity_id
|
||||
WHERE ue.unit_id::text = ANY($1)
|
||||
""",
|
||||
unit_ids
|
||||
)
|
||||
|
||||
entity_names = {row['canonical_name'].lower() for row in entity_rows}
|
||||
logger.info(f"Found entities linked to facts: {[row['canonical_name'] for row in entity_rows]}")
|
||||
|
||||
# Verify user-provided entities are present
|
||||
assert "projectx" in entity_names, "User-provided entity 'ProjectX' should be linked"
|
||||
assert "acme corp" in entity_names, "User-provided entity 'ACME Corp' should be linked"
|
||||
|
||||
# Alice should be present (either from LLM extraction or user-provided)
|
||||
assert "alice" in entity_names, "Entity 'Alice' should be linked"
|
||||
|
||||
logger.info("✓ User-provided entities successfully merged with extracted entities")
|
||||
|
||||
finally:
|
||||
await memory.delete_bank(bank_id, request_context=request_context)
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact_name: memora
|
||||
release_name: memora-linux-x86_64
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
artifact_name: memora
|
||||
release_name: memora-linux-arm64
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
artifact_name: memora
|
||||
release_name: memora-macos-x86_64
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
artifact_name: memora
|
||||
release_name: memora-macos-arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Install cross-compilation tools (Linux ARM64)
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Strip binary (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Strip binary (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.release_name }}
|
||||
path: target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
cd artifacts
|
||||
for dir in */; do
|
||||
cd "$dir"
|
||||
sha256sum * > SHA256SUMS
|
||||
cd ..
|
||||
done
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
artifacts/memora-linux-x86_64/memora
|
||||
artifacts/memora-linux-arm64/memora
|
||||
artifacts/memora-macos-x86_64/memora
|
||||
artifacts/memora-macos-arm64/memora
|
||||
artifacts/*/SHA256SUMS
|
||||
draft: false
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hindsight-cli"
|
||||
version = "0.1.16"
|
||||
version = "0.1.12"
|
||||
edition = "2021"
|
||||
authors = ["Hindsight Team"]
|
||||
description = "A beautiful CLI for Hindsight - semantic memory system"
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
#!/bin/bash
|
||||
# CLI smoke test - verifies basic CLI functionality against a running API server
|
||||
#
|
||||
# Prerequisites:
|
||||
# - hindsight CLI must be in PATH or HINDSIGHT_CLI env var set
|
||||
# - API server must be running at HINDSIGHT_API_URL (default: http://localhost:8888)
|
||||
#
|
||||
# Usage:
|
||||
# ./hindsight-cli/smoke-test.sh
|
||||
# HINDSIGHT_CLI=/path/to/hindsight ./hindsight-cli/smoke-test.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
HINDSIGHT_CLI="${HINDSIGHT_CLI:-hindsight}"
|
||||
export HINDSIGHT_API_URL="${HINDSIGHT_API_URL:-http://localhost:8888}"
|
||||
TEST_BANK="cli-smoke-test-$(date +%s)"
|
||||
|
||||
echo "=== Hindsight CLI Smoke Test ==="
|
||||
echo "CLI: $HINDSIGHT_CLI"
|
||||
echo "API URL: $HINDSIGHT_API_URL"
|
||||
echo "Test bank: $TEST_BANK"
|
||||
echo ""
|
||||
|
||||
# Helper function
|
||||
run_test() {
|
||||
local name="$1"
|
||||
shift
|
||||
echo -n "Testing: $name... "
|
||||
if "$@" > /tmp/cli-test-output.txt 2>&1; then
|
||||
echo "OK"
|
||||
return 0
|
||||
else
|
||||
echo "FAILED"
|
||||
echo " Command: $*"
|
||||
echo " Output:"
|
||||
cat /tmp/cli-test-output.txt | sed 's/^/ /'
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_test_output() {
|
||||
local name="$1"
|
||||
local expected="$2"
|
||||
shift 2
|
||||
echo -n "Testing: $name... "
|
||||
if "$@" > /tmp/cli-test-output.txt 2>&1; then
|
||||
if grep -qi "$expected" /tmp/cli-test-output.txt; then
|
||||
echo "OK"
|
||||
return 0
|
||||
else
|
||||
echo "FAILED (expected '$expected' not found)"
|
||||
echo " Command: $*"
|
||||
echo " Output:"
|
||||
cat /tmp/cli-test-output.txt | sed 's/^/ /'
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "FAILED"
|
||||
echo " Command: $*"
|
||||
echo " Output:"
|
||||
cat /tmp/cli-test-output.txt | sed 's/^/ /'
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "Cleaning up test bank..."
|
||||
"$HINDSIGHT_CLI" bank delete "$TEST_BANK" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
FAILED=0
|
||||
|
||||
# Test 1: Version
|
||||
run_test "version" "$HINDSIGHT_CLI" --version || FAILED=1
|
||||
|
||||
# Test 2: Help
|
||||
run_test "help" "$HINDSIGHT_CLI" --help || FAILED=1
|
||||
|
||||
# Test 3: Configure help
|
||||
run_test "configure help" "$HINDSIGHT_CLI" configure --help || FAILED=1
|
||||
|
||||
# Test 4: List banks (JSON output)
|
||||
run_test "list banks" "$HINDSIGHT_CLI" bank list -o json || FAILED=1
|
||||
|
||||
# Test 5: Set bank name (creates the bank)
|
||||
run_test "set bank name" "$HINDSIGHT_CLI" bank name "$TEST_BANK" "CLI Smoke Test Bank" || FAILED=1
|
||||
|
||||
# Test 6: Get bank disposition
|
||||
run_test_output "get bank disposition" "CLI Smoke Test Bank" "$HINDSIGHT_CLI" bank disposition "$TEST_BANK" || FAILED=1
|
||||
|
||||
# Test 7: Retain memory
|
||||
run_test "retain memory" "$HINDSIGHT_CLI" memory retain "$TEST_BANK" "Alice is a software engineer who loves Rust programming" || FAILED=1
|
||||
|
||||
# Test 8: Retain more memories
|
||||
run_test "retain more memories" "$HINDSIGHT_CLI" memory retain "$TEST_BANK" "Bob is Alice's colleague who prefers Python" || FAILED=1
|
||||
|
||||
# Test 9: Recall memories
|
||||
run_test_output "recall memories" "Alice" "$HINDSIGHT_CLI" memory recall "$TEST_BANK" "Who is Alice?" || FAILED=1
|
||||
|
||||
# Test 10: Reflect on memories
|
||||
run_test_output "reflect" "Alice" "$HINDSIGHT_CLI" memory reflect "$TEST_BANK" "What do you know about Alice?" || FAILED=1
|
||||
|
||||
# Test 11: Get bank stats
|
||||
run_test "bank stats" "$HINDSIGHT_CLI" bank stats "$TEST_BANK" || FAILED=1
|
||||
|
||||
# Test 12: List entities
|
||||
run_test "list entities" "$HINDSIGHT_CLI" entity list "$TEST_BANK" || FAILED=1
|
||||
|
||||
# Test 13: List documents
|
||||
run_test "list documents" "$HINDSIGHT_CLI" document list "$TEST_BANK" || FAILED=1
|
||||
|
||||
# Test 14: Clear memories
|
||||
run_test "clear memories" "$HINDSIGHT_CLI" memory clear "$TEST_BANK" || FAILED=1
|
||||
|
||||
# Test 15: Delete bank
|
||||
run_test "delete bank" "$HINDSIGHT_CLI" bank delete "$TEST_BANK" || FAILED=1
|
||||
|
||||
echo ""
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "=== All smoke tests passed! ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Some smoke tests failed ==="
|
||||
exit 1
|
||||
fi
|
||||
+24
-37
@@ -89,14 +89,14 @@ impl ApiClient {
|
||||
|
||||
pub fn list_agents(&self, _verbose: bool) -> Result<Vec<types::BankListItem>> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_banks(None).await?;
|
||||
let response = self.client.list_banks().await?;
|
||||
Ok(response.into_inner().banks)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_profile(&self, agent_id: &str, _verbose: bool) -> Result<types::BankProfileResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_bank_profile(agent_id, None).await?;
|
||||
let response = self.client.get_bank_profile(agent_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
@@ -105,9 +105,7 @@ impl ApiClient {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_agent_stats(agent_id).await?;
|
||||
let value = response.into_inner();
|
||||
// Convert to JSON Value first, then parse into our type
|
||||
let json_value = serde_json::to_value(&value)?;
|
||||
let stats: AgentStats = serde_json::from_value(json_value)?;
|
||||
let stats: AgentStats = serde_json::from_value(value)?;
|
||||
Ok(stats)
|
||||
})
|
||||
}
|
||||
@@ -119,7 +117,7 @@ impl ApiClient {
|
||||
background: None,
|
||||
disposition: None,
|
||||
};
|
||||
let response = self.client.create_or_update_bank(agent_id, None, &request).await?;
|
||||
let response = self.client.create_or_update_bank(agent_id, &request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
@@ -130,7 +128,7 @@ impl ApiClient {
|
||||
content: content.to_string(),
|
||||
update_disposition,
|
||||
};
|
||||
let response = self.client.add_bank_background(agent_id, None, &request).await?;
|
||||
let response = self.client.add_bank_background(agent_id, &request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
@@ -140,21 +138,21 @@ impl ApiClient {
|
||||
eprintln!("Request body: {}", serde_json::to_string_pretty(request).unwrap_or_default());
|
||||
}
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.recall_memories(agent_id, None, request).await?;
|
||||
let response = self.client.recall_memories(agent_id, request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reflect(&self, agent_id: &str, request: &types::ReflectRequest, _verbose: bool) -> Result<types::ReflectResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.reflect(agent_id, None, request).await?;
|
||||
let response = self.client.reflect(agent_id, request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn retain(&self, agent_id: &str, request: &types::RetainRequest, _async_mode: bool, _verbose: bool) -> Result<MemoryPutResult> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.retain_memories(agent_id, None, request).await?;
|
||||
let response = self.client.retain_memories(agent_id, request).await?;
|
||||
let result = response.into_inner();
|
||||
Ok(MemoryPutResult {
|
||||
success: result.success,
|
||||
@@ -172,7 +170,7 @@ impl ApiClient {
|
||||
|
||||
pub fn clear_memories(&self, agent_id: &str, fact_type: Option<&str>, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.clear_bank_memories(agent_id, None, Some(fact_type)).await?;
|
||||
let response = self.client.clear_bank_memories(agent_id, fact_type).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
@@ -183,8 +181,7 @@ impl ApiClient {
|
||||
agent_id,
|
||||
limit.map(|l| l as i64),
|
||||
offset.map(|o| o as i64),
|
||||
q,
|
||||
None,
|
||||
q
|
||||
).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
@@ -192,79 +189,69 @@ impl ApiClient {
|
||||
|
||||
pub fn get_document(&self, agent_id: &str, document_id: &str, _verbose: bool) -> Result<types::DocumentResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_document(agent_id, document_id, None).await?;
|
||||
let response = self.client.get_document(agent_id, document_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_document(&self, agent_id: &str, document_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.delete_document(agent_id, document_id, None).await?;
|
||||
let response = self.client.delete_document(agent_id, document_id).await?;
|
||||
let value = response.into_inner();
|
||||
// Convert typed response to DeleteResponse
|
||||
Ok(types::DeleteResponse {
|
||||
deleted_count: Some(value.memory_units_deleted),
|
||||
message: Some(value.message),
|
||||
success: value.success,
|
||||
})
|
||||
let result: types::DeleteResponse = serde_json::from_value(value)?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_operations(&self, agent_id: &str, _verbose: bool) -> Result<OperationsResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_operations(agent_id, None).await?;
|
||||
let response = self.client.list_operations(agent_id).await?;
|
||||
let value = response.into_inner();
|
||||
// Convert to JSON Value first, then parse into our type
|
||||
let json_value = serde_json::to_value(&value)?;
|
||||
let ops: OperationsResponse = serde_json::from_value(json_value)?;
|
||||
let ops: OperationsResponse = serde_json::from_value(value)?;
|
||||
Ok(ops)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_operation(&self, agent_id: &str, operation_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.cancel_operation(agent_id, operation_id, None).await?;
|
||||
let response = self.client.cancel_operation(agent_id, operation_id).await?;
|
||||
let value = response.into_inner();
|
||||
// Convert typed response to DeleteResponse
|
||||
Ok(types::DeleteResponse {
|
||||
deleted_count: None,
|
||||
message: Some(value.message),
|
||||
success: value.success,
|
||||
})
|
||||
let result: types::DeleteResponse = serde_json::from_value(value)?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_memories(&self, bank_id: &str, type_filter: Option<&str>, q: Option<&str>, limit: Option<i64>, offset: Option<i64>, _verbose: bool) -> Result<types::ListMemoryUnitsResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_memories(bank_id, limit, offset, q, type_filter, None).await?;
|
||||
let response = self.client.list_memories(bank_id, limit, offset, q, type_filter).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_entities(&self, bank_id: &str, limit: Option<i64>, _verbose: bool) -> Result<types::EntityListResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_entities(bank_id, limit, None).await?;
|
||||
let response = self.client.list_entities(bank_id, limit).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_entity(&self, bank_id: &str, entity_id: &str, _verbose: bool) -> Result<types::EntityDetailResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_entity(bank_id, entity_id, None).await?;
|
||||
let response = self.client.get_entity(bank_id, entity_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn regenerate_entity(&self, bank_id: &str, entity_id: &str, _verbose: bool) -> Result<types::EntityDetailResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.regenerate_entity_observations(bank_id, entity_id, None).await?;
|
||||
let response = self.client.regenerate_entity_observations(bank_id, entity_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_bank(&self, bank_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.delete_bank(bank_id, None).await?;
|
||||
let response = self.client.delete_bank(bank_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -354,9 +354,7 @@ impl App {
|
||||
query: query_text,
|
||||
budget: Some(query_budget),
|
||||
context: None,
|
||||
max_tokens: 4096,
|
||||
include: None,
|
||||
response_schema: None,
|
||||
};
|
||||
|
||||
let result = client.reflect(&bank_id, &request, false)
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::ui;
|
||||
|
||||
// Import types from generated client
|
||||
use hindsight_client::types::{Budget, ChunkIncludeOptions, IncludeOptions};
|
||||
use serde_json;
|
||||
|
||||
// Helper function to parse budget string to Budget enum
|
||||
fn parse_budget(budget: &str) -> Budget {
|
||||
@@ -87,8 +86,6 @@ pub fn reflect(
|
||||
query: String,
|
||||
budget: String,
|
||||
context: Option<String>,
|
||||
max_tokens: Option<i64>,
|
||||
schema_path: Option<PathBuf>,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
@@ -98,24 +95,11 @@ pub fn reflect(
|
||||
None
|
||||
};
|
||||
|
||||
// Load and parse schema if provided
|
||||
let response_schema = if let Some(path) = schema_path {
|
||||
let schema_content = fs::read_to_string(&path)
|
||||
.with_context(|| format!("Failed to read schema file: {}", path.display()))?;
|
||||
let schema: serde_json::Map<String, serde_json::Value> = serde_json::from_str(&schema_content)
|
||||
.with_context(|| format!("Failed to parse JSON schema from: {}", path.display()))?;
|
||||
Some(schema)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = ReflectRequest {
|
||||
query,
|
||||
budget: Some(parse_budget(&budget)),
|
||||
context,
|
||||
max_tokens: max_tokens.unwrap_or(4096),
|
||||
include: None,
|
||||
response_schema,
|
||||
};
|
||||
|
||||
let response = client.reflect(agent_id, &request, verbose);
|
||||
|
||||
@@ -206,14 +206,6 @@ enum MemoryCommands {
|
||||
/// Additional context
|
||||
#[arg(short = 'c', long)]
|
||||
context: Option<String>,
|
||||
|
||||
/// Maximum tokens for the response (server default: 4096)
|
||||
#[arg(short = 'm', long)]
|
||||
max_tokens: Option<i64>,
|
||||
|
||||
/// Path to JSON schema file for structured output
|
||||
#[arg(short = 's', long)]
|
||||
schema: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Store (retain) a single memory
|
||||
@@ -429,8 +421,8 @@ fn run() -> Result<()> {
|
||||
MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens } => {
|
||||
commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, verbose, output_format)
|
||||
}
|
||||
MemoryCommands::Reflect { bank_id, query, budget, context, max_tokens, schema } => {
|
||||
commands::memory::reflect(&client, &bank_id, query, budget, context, max_tokens, schema, verbose, output_format)
|
||||
MemoryCommands::Reflect { bank_id, query, budget, context } => {
|
||||
commands::memory::reflect(&client, &bank_id, query, budget, context, verbose, output_format)
|
||||
}
|
||||
MemoryCommands::Retain { bank_id, content, doc_id, context, r#async } => {
|
||||
commands::memory::retain(&client, &bank_id, content, doc_id, context, r#async, verbose, output_format)
|
||||
|
||||
@@ -175,16 +175,6 @@ pub fn print_think_response(response: &ReflectResponse) {
|
||||
if !response.based_on.is_empty() {
|
||||
println!("{}", dim(&format!("Based on {} memory units", response.based_on.len())));
|
||||
}
|
||||
|
||||
// Display structured output if present
|
||||
if let Some(structured) = &response.structured_output {
|
||||
println!();
|
||||
println!("{}", gradient_text("─── Structured Output ───"));
|
||||
println!();
|
||||
if let Ok(json) = serde_json::to_string_pretty(structured) {
|
||||
println!("{}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_trace_info(trace: &serde_json::Map<String, serde_json::Value>) {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
hindsight_client_api/__init__.py
|
||||
hindsight_client_api/api/__init__.py
|
||||
hindsight_client_api/api/banks_api.py
|
||||
hindsight_client_api/api/documents_api.py
|
||||
hindsight_client_api/api/entities_api.py
|
||||
hindsight_client_api/api/memory_api.py
|
||||
hindsight_client_api/api/default_api.py
|
||||
hindsight_client_api/api/monitoring_api.py
|
||||
hindsight_client_api/api/operations_api.py
|
||||
hindsight_client_api/api_client.py
|
||||
hindsight_client_api/api_response.py
|
||||
hindsight_client_api/configuration.py
|
||||
@@ -14,20 +10,15 @@ hindsight_client_api/docs/BackgroundResponse.md
|
||||
hindsight_client_api/docs/BankListItem.md
|
||||
hindsight_client_api/docs/BankListResponse.md
|
||||
hindsight_client_api/docs/BankProfileResponse.md
|
||||
hindsight_client_api/docs/BankStatsResponse.md
|
||||
hindsight_client_api/docs/BanksApi.md
|
||||
hindsight_client_api/docs/Budget.md
|
||||
hindsight_client_api/docs/CancelOperationResponse.md
|
||||
hindsight_client_api/docs/ChunkData.md
|
||||
hindsight_client_api/docs/ChunkIncludeOptions.md
|
||||
hindsight_client_api/docs/ChunkResponse.md
|
||||
hindsight_client_api/docs/CreateBankRequest.md
|
||||
hindsight_client_api/docs/DeleteDocumentResponse.md
|
||||
hindsight_client_api/docs/DefaultApi.md
|
||||
hindsight_client_api/docs/DeleteResponse.md
|
||||
hindsight_client_api/docs/DispositionTraits.md
|
||||
hindsight_client_api/docs/DocumentResponse.md
|
||||
hindsight_client_api/docs/DocumentsApi.md
|
||||
hindsight_client_api/docs/EntitiesApi.md
|
||||
hindsight_client_api/docs/EntityDetailResponse.md
|
||||
hindsight_client_api/docs/EntityIncludeOptions.md
|
||||
hindsight_client_api/docs/EntityListItem.md
|
||||
@@ -39,12 +30,8 @@ hindsight_client_api/docs/HTTPValidationError.md
|
||||
hindsight_client_api/docs/IncludeOptions.md
|
||||
hindsight_client_api/docs/ListDocumentsResponse.md
|
||||
hindsight_client_api/docs/ListMemoryUnitsResponse.md
|
||||
hindsight_client_api/docs/MemoryApi.md
|
||||
hindsight_client_api/docs/MemoryItem.md
|
||||
hindsight_client_api/docs/MonitoringApi.md
|
||||
hindsight_client_api/docs/OperationResponse.md
|
||||
hindsight_client_api/docs/OperationsApi.md
|
||||
hindsight_client_api/docs/OperationsListResponse.md
|
||||
hindsight_client_api/docs/RecallRequest.md
|
||||
hindsight_client_api/docs/RecallResponse.md
|
||||
hindsight_client_api/docs/RecallResult.md
|
||||
@@ -64,14 +51,11 @@ hindsight_client_api/models/background_response.py
|
||||
hindsight_client_api/models/bank_list_item.py
|
||||
hindsight_client_api/models/bank_list_response.py
|
||||
hindsight_client_api/models/bank_profile_response.py
|
||||
hindsight_client_api/models/bank_stats_response.py
|
||||
hindsight_client_api/models/budget.py
|
||||
hindsight_client_api/models/cancel_operation_response.py
|
||||
hindsight_client_api/models/chunk_data.py
|
||||
hindsight_client_api/models/chunk_include_options.py
|
||||
hindsight_client_api/models/chunk_response.py
|
||||
hindsight_client_api/models/create_bank_request.py
|
||||
hindsight_client_api/models/delete_document_response.py
|
||||
hindsight_client_api/models/delete_response.py
|
||||
hindsight_client_api/models/disposition_traits.py
|
||||
hindsight_client_api/models/document_response.py
|
||||
@@ -87,8 +71,6 @@ hindsight_client_api/models/include_options.py
|
||||
hindsight_client_api/models/list_documents_response.py
|
||||
hindsight_client_api/models/list_memory_units_response.py
|
||||
hindsight_client_api/models/memory_item.py
|
||||
hindsight_client_api/models/operation_response.py
|
||||
hindsight_client_api/models/operations_list_response.py
|
||||
hindsight_client_api/models/recall_request.py
|
||||
hindsight_client_api/models/recall_response.py
|
||||
hindsight_client_api/models/recall_result.py
|
||||
@@ -102,4 +84,44 @@ hindsight_client_api/models/update_disposition_request.py
|
||||
hindsight_client_api/models/validation_error.py
|
||||
hindsight_client_api/models/validation_error_loc_inner.py
|
||||
hindsight_client_api/rest.py
|
||||
hindsight_client_api/test/__init__.py
|
||||
hindsight_client_api/test/test_add_background_request.py
|
||||
hindsight_client_api/test/test_background_response.py
|
||||
hindsight_client_api/test/test_bank_list_item.py
|
||||
hindsight_client_api/test/test_bank_list_response.py
|
||||
hindsight_client_api/test/test_bank_profile_response.py
|
||||
hindsight_client_api/test/test_budget.py
|
||||
hindsight_client_api/test/test_chunk_data.py
|
||||
hindsight_client_api/test/test_chunk_include_options.py
|
||||
hindsight_client_api/test/test_chunk_response.py
|
||||
hindsight_client_api/test/test_create_bank_request.py
|
||||
hindsight_client_api/test/test_default_api.py
|
||||
hindsight_client_api/test/test_delete_response.py
|
||||
hindsight_client_api/test/test_disposition_traits.py
|
||||
hindsight_client_api/test/test_document_response.py
|
||||
hindsight_client_api/test/test_entity_detail_response.py
|
||||
hindsight_client_api/test/test_entity_include_options.py
|
||||
hindsight_client_api/test/test_entity_list_item.py
|
||||
hindsight_client_api/test/test_entity_list_response.py
|
||||
hindsight_client_api/test/test_entity_observation_response.py
|
||||
hindsight_client_api/test/test_entity_state_response.py
|
||||
hindsight_client_api/test/test_graph_data_response.py
|
||||
hindsight_client_api/test/test_http_validation_error.py
|
||||
hindsight_client_api/test/test_include_options.py
|
||||
hindsight_client_api/test/test_list_documents_response.py
|
||||
hindsight_client_api/test/test_list_memory_units_response.py
|
||||
hindsight_client_api/test/test_memory_item.py
|
||||
hindsight_client_api/test/test_monitoring_api.py
|
||||
hindsight_client_api/test/test_recall_request.py
|
||||
hindsight_client_api/test/test_recall_response.py
|
||||
hindsight_client_api/test/test_recall_result.py
|
||||
hindsight_client_api/test/test_reflect_fact.py
|
||||
hindsight_client_api/test/test_reflect_include_options.py
|
||||
hindsight_client_api/test/test_reflect_request.py
|
||||
hindsight_client_api/test/test_reflect_response.py
|
||||
hindsight_client_api/test/test_retain_request.py
|
||||
hindsight_client_api/test/test_retain_response.py
|
||||
hindsight_client_api/test/test_update_disposition_request.py
|
||||
hindsight_client_api/test/test_validation_error.py
|
||||
hindsight_client_api/test/test_validation_error_loc_inner.py
|
||||
hindsight_client_api_README.md
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.api import memory_api, banks_api
|
||||
from hindsight_client_api.api import default_api
|
||||
from hindsight_client_api.models import (
|
||||
recall_request,
|
||||
retain_request,
|
||||
@@ -74,8 +74,7 @@ class Hindsight:
|
||||
"""
|
||||
config = hindsight_client_api.Configuration(host=base_url, access_token=api_key)
|
||||
self._api_client = hindsight_client_api.ApiClient(config)
|
||||
self._memory_api = memory_api.MemoryApi(self._api_client)
|
||||
self._banks_api = banks_api.BanksApi(self._api_client)
|
||||
self._api = default_api.DefaultApi(self._api_client)
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
@@ -112,7 +111,6 @@ class Hindsight:
|
||||
context: Optional[str] = None,
|
||||
document_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
entities: Optional[List[Dict[str, str]]] = None,
|
||||
) -> RetainResponse:
|
||||
"""
|
||||
Store a single memory (simplified interface).
|
||||
@@ -124,14 +122,13 @@ class Hindsight:
|
||||
context: Optional context description
|
||||
document_id: Optional document ID for grouping
|
||||
metadata: Optional user-defined metadata
|
||||
entities: Optional list of entities [{"text": "...", "type": "..."}]
|
||||
|
||||
Returns:
|
||||
RetainResponse with success status
|
||||
"""
|
||||
return self.retain_batch(
|
||||
bank_id=bank_id,
|
||||
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata, "entities": entities}],
|
||||
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata}],
|
||||
document_id=document_id,
|
||||
)
|
||||
|
||||
@@ -147,41 +144,31 @@ class Hindsight:
|
||||
|
||||
Args:
|
||||
bank_id: The memory bank ID
|
||||
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata', 'document_id', 'entities'
|
||||
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata', 'document_id'
|
||||
document_id: Optional document ID for grouping memories (applied to items that don't have their own)
|
||||
retain_async: If True, process asynchronously in background (default: False)
|
||||
|
||||
Returns:
|
||||
RetainResponse with success status and item count
|
||||
"""
|
||||
from hindsight_client_api.models.entity_input import EntityInput
|
||||
|
||||
memory_items = []
|
||||
for item in items:
|
||||
entities = None
|
||||
if item.get("entities"):
|
||||
entities = [
|
||||
EntityInput(text=e["text"], type=e.get("type"))
|
||||
for e in item["entities"]
|
||||
]
|
||||
memory_items.append(
|
||||
memory_item.MemoryItem(
|
||||
content=item["content"],
|
||||
timestamp=item.get("timestamp"),
|
||||
context=item.get("context"),
|
||||
metadata=item.get("metadata"),
|
||||
# Use item's document_id if provided, otherwise fall back to batch-level document_id
|
||||
document_id=item.get("document_id") or document_id,
|
||||
entities=entities,
|
||||
)
|
||||
memory_items = [
|
||||
memory_item.MemoryItem(
|
||||
content=item["content"],
|
||||
timestamp=item.get("timestamp"),
|
||||
context=item.get("context"),
|
||||
metadata=item.get("metadata"),
|
||||
# Use item's document_id if provided, otherwise fall back to batch-level document_id
|
||||
document_id=item.get("document_id") or document_id,
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
request_obj = retain_request.RetainRequest(
|
||||
items=memory_items,
|
||||
async_=retain_async,
|
||||
)
|
||||
|
||||
return _run_async(self._memory_api.retain_memories(bank_id, request_obj))
|
||||
return _run_async(self._api.retain_memories(bank_id, request_obj))
|
||||
|
||||
def recall(
|
||||
self,
|
||||
@@ -233,7 +220,7 @@ class Hindsight:
|
||||
include=include_opts,
|
||||
)
|
||||
|
||||
return _run_async(self._memory_api.recall_memories(bank_id, request_obj))
|
||||
return _run_async(self._api.recall_memories(bank_id, request_obj))
|
||||
|
||||
def reflect(
|
||||
self,
|
||||
@@ -241,8 +228,6 @@ class Hindsight:
|
||||
query: str,
|
||||
budget: str = "low",
|
||||
context: Optional[str] = None,
|
||||
max_tokens: Optional[int] = None,
|
||||
response_schema: Optional[Dict[str, Any]] = None,
|
||||
) -> ReflectResponse:
|
||||
"""
|
||||
Generate a contextual answer based on bank identity and memories.
|
||||
@@ -252,24 +237,17 @@ class Hindsight:
|
||||
query: The question or prompt
|
||||
budget: Budget level for reflection - "low", "mid", or "high" (default: "low")
|
||||
context: Optional additional context
|
||||
max_tokens: Maximum tokens for the response (server default: 4096)
|
||||
response_schema: Optional JSON Schema for structured output. When provided,
|
||||
the response will include a 'structured_output' field with the LLM
|
||||
response parsed according to this schema.
|
||||
|
||||
Returns:
|
||||
ReflectResponse with answer text, optionally facts used, and optionally
|
||||
structured_output if response_schema was provided
|
||||
ReflectResponse with answer text and optionally facts used
|
||||
"""
|
||||
request_obj = reflect_request.ReflectRequest(
|
||||
query=query,
|
||||
budget=budget,
|
||||
context=context,
|
||||
max_tokens=max_tokens,
|
||||
response_schema=response_schema,
|
||||
)
|
||||
|
||||
return _run_async(self._memory_api.reflect(bank_id, request_obj))
|
||||
return _run_async(self._api.reflect(bank_id, request_obj))
|
||||
|
||||
def list_memories(
|
||||
self,
|
||||
@@ -280,7 +258,7 @@ class Hindsight:
|
||||
offset: int = 0,
|
||||
) -> ListMemoryUnitsResponse:
|
||||
"""List memory units with pagination."""
|
||||
return _run_async(self._memory_api.list_memories(
|
||||
return _run_async(self._api.list_memories(
|
||||
bank_id=bank_id,
|
||||
type=type,
|
||||
q=search_query,
|
||||
@@ -308,7 +286,7 @@ class Hindsight:
|
||||
disposition=disposition_obj,
|
||||
)
|
||||
|
||||
return _run_async(self._banks_api.create_or_update_bank(bank_id, request_obj))
|
||||
return _run_async(self._api.create_or_update_bank(bank_id, request_obj))
|
||||
|
||||
# Async methods (native async, no _run_async wrapper)
|
||||
|
||||
@@ -324,41 +302,31 @@ class Hindsight:
|
||||
|
||||
Args:
|
||||
bank_id: The memory bank ID
|
||||
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata', 'document_id', 'entities'
|
||||
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata', 'document_id'
|
||||
document_id: Optional document ID for grouping memories (applied to items that don't have their own)
|
||||
retain_async: If True, process asynchronously in background (default: False)
|
||||
|
||||
Returns:
|
||||
RetainResponse with success status and item count
|
||||
"""
|
||||
from hindsight_client_api.models.entity_input import EntityInput
|
||||
|
||||
memory_items = []
|
||||
for item in items:
|
||||
entities = None
|
||||
if item.get("entities"):
|
||||
entities = [
|
||||
EntityInput(text=e["text"], type=e.get("type"))
|
||||
for e in item["entities"]
|
||||
]
|
||||
memory_items.append(
|
||||
memory_item.MemoryItem(
|
||||
content=item["content"],
|
||||
timestamp=item.get("timestamp"),
|
||||
context=item.get("context"),
|
||||
metadata=item.get("metadata"),
|
||||
# Use item's document_id if provided, otherwise fall back to batch-level document_id
|
||||
document_id=item.get("document_id") or document_id,
|
||||
entities=entities,
|
||||
)
|
||||
memory_items = [
|
||||
memory_item.MemoryItem(
|
||||
content=item["content"],
|
||||
timestamp=item.get("timestamp"),
|
||||
context=item.get("context"),
|
||||
metadata=item.get("metadata"),
|
||||
# Use item's document_id if provided, otherwise fall back to batch-level document_id
|
||||
document_id=item.get("document_id") or document_id,
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
request_obj = retain_request.RetainRequest(
|
||||
items=memory_items,
|
||||
async_=retain_async,
|
||||
)
|
||||
|
||||
return await self._memory_api.retain_memories(bank_id, request_obj)
|
||||
return await self._api.retain_memories(bank_id, request_obj)
|
||||
|
||||
async def aretain(
|
||||
self,
|
||||
@@ -368,7 +336,6 @@ class Hindsight:
|
||||
context: Optional[str] = None,
|
||||
document_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
entities: Optional[List[Dict[str, str]]] = None,
|
||||
) -> RetainResponse:
|
||||
"""
|
||||
Store a single memory (async).
|
||||
@@ -380,14 +347,13 @@ class Hindsight:
|
||||
context: Optional context description
|
||||
document_id: Optional document ID for grouping
|
||||
metadata: Optional user-defined metadata
|
||||
entities: Optional list of entities [{"text": "...", "type": "..."}]
|
||||
|
||||
Returns:
|
||||
RetainResponse with success status
|
||||
"""
|
||||
return await self.aretain_batch(
|
||||
bank_id=bank_id,
|
||||
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata, "entities": entities}],
|
||||
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata}],
|
||||
document_id=document_id,
|
||||
)
|
||||
|
||||
@@ -420,7 +386,7 @@ class Hindsight:
|
||||
trace=False,
|
||||
)
|
||||
|
||||
response = await self._memory_api.recall_memories(bank_id, request_obj)
|
||||
response = await self._api.recall_memories(bank_id, request_obj)
|
||||
return response.results if hasattr(response, 'results') else []
|
||||
|
||||
async def areflect(
|
||||
@@ -448,4 +414,4 @@ class Hindsight:
|
||||
context=context,
|
||||
)
|
||||
|
||||
return await self._memory_api.reflect(bank_id, request_obj)
|
||||
return await self._api.reflect(bank_id, request_obj)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
@@ -18,12 +18,8 @@ __version__ = "0.0.7"
|
||||
|
||||
# Define package exports
|
||||
__all__ = [
|
||||
"BanksApi",
|
||||
"DocumentsApi",
|
||||
"EntitiesApi",
|
||||
"MemoryApi",
|
||||
"MonitoringApi",
|
||||
"OperationsApi",
|
||||
"DefaultApi",
|
||||
"ApiResponse",
|
||||
"ApiClient",
|
||||
"Configuration",
|
||||
@@ -38,14 +34,11 @@ __all__ = [
|
||||
"BankListItem",
|
||||
"BankListResponse",
|
||||
"BankProfileResponse",
|
||||
"BankStatsResponse",
|
||||
"Budget",
|
||||
"CancelOperationResponse",
|
||||
"ChunkData",
|
||||
"ChunkIncludeOptions",
|
||||
"ChunkResponse",
|
||||
"CreateBankRequest",
|
||||
"DeleteDocumentResponse",
|
||||
"DeleteResponse",
|
||||
"DispositionTraits",
|
||||
"DocumentResponse",
|
||||
@@ -61,8 +54,6 @@ __all__ = [
|
||||
"ListDocumentsResponse",
|
||||
"ListMemoryUnitsResponse",
|
||||
"MemoryItem",
|
||||
"OperationResponse",
|
||||
"OperationsListResponse",
|
||||
"RecallRequest",
|
||||
"RecallResponse",
|
||||
"RecallResult",
|
||||
@@ -78,12 +69,8 @@ __all__ = [
|
||||
]
|
||||
|
||||
# import apis into sdk package
|
||||
from hindsight_client_api.api.banks_api import BanksApi as BanksApi
|
||||
from hindsight_client_api.api.documents_api import DocumentsApi as DocumentsApi
|
||||
from hindsight_client_api.api.entities_api import EntitiesApi as EntitiesApi
|
||||
from hindsight_client_api.api.memory_api import MemoryApi as MemoryApi
|
||||
from hindsight_client_api.api.monitoring_api import MonitoringApi as MonitoringApi
|
||||
from hindsight_client_api.api.operations_api import OperationsApi as OperationsApi
|
||||
from hindsight_client_api.api.default_api import DefaultApi as DefaultApi
|
||||
|
||||
# import ApiClient
|
||||
from hindsight_client_api.api_response import ApiResponse as ApiResponse
|
||||
@@ -102,14 +89,11 @@ from hindsight_client_api.models.background_response import BackgroundResponse a
|
||||
from hindsight_client_api.models.bank_list_item import BankListItem as BankListItem
|
||||
from hindsight_client_api.models.bank_list_response import BankListResponse as BankListResponse
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse as BankProfileResponse
|
||||
from hindsight_client_api.models.bank_stats_response import BankStatsResponse as BankStatsResponse
|
||||
from hindsight_client_api.models.budget import Budget as Budget
|
||||
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse as CancelOperationResponse
|
||||
from hindsight_client_api.models.chunk_data import ChunkData as ChunkData
|
||||
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions as ChunkIncludeOptions
|
||||
from hindsight_client_api.models.chunk_response import ChunkResponse as ChunkResponse
|
||||
from hindsight_client_api.models.create_bank_request import CreateBankRequest as CreateBankRequest
|
||||
from hindsight_client_api.models.delete_document_response import DeleteDocumentResponse as DeleteDocumentResponse
|
||||
from hindsight_client_api.models.delete_response import DeleteResponse as DeleteResponse
|
||||
from hindsight_client_api.models.disposition_traits import DispositionTraits as DispositionTraits
|
||||
from hindsight_client_api.models.document_response import DocumentResponse as DocumentResponse
|
||||
@@ -125,8 +109,6 @@ from hindsight_client_api.models.include_options import IncludeOptions as Includ
|
||||
from hindsight_client_api.models.list_documents_response import ListDocumentsResponse as ListDocumentsResponse
|
||||
from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse as ListMemoryUnitsResponse
|
||||
from hindsight_client_api.models.memory_item import MemoryItem as MemoryItem
|
||||
from hindsight_client_api.models.operation_response import OperationResponse as OperationResponse
|
||||
from hindsight_client_api.models.operations_list_response import OperationsListResponse as OperationsListResponse
|
||||
from hindsight_client_api.models.recall_request import RecallRequest as RecallRequest
|
||||
from hindsight_client_api.models.recall_response import RecallResponse as RecallResponse
|
||||
from hindsight_client_api.models.recall_result import RecallResult as RecallResult
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
# flake8: noqa
|
||||
|
||||
# import apis into api package
|
||||
from hindsight_client_api.api.banks_api import BanksApi
|
||||
from hindsight_client_api.api.documents_api import DocumentsApi
|
||||
from hindsight_client_api.api.entities_api import EntitiesApi
|
||||
from hindsight_client_api.api.memory_api import MemoryApi
|
||||
from hindsight_client_api.api.monitoring_api import MonitoringApi
|
||||
from hindsight_client_api.api.operations_api import OperationsApi
|
||||
from hindsight_client_api.api.default_api import DefaultApi
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,921 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
import warnings
|
||||
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import Field, StrictInt, StrictStr
|
||||
from typing import Optional
|
||||
from typing_extensions import Annotated
|
||||
from hindsight_client_api.models.entity_detail_response import EntityDetailResponse
|
||||
from hindsight_client_api.models.entity_list_response import EntityListResponse
|
||||
|
||||
from hindsight_client_api.api_client import ApiClient, RequestSerialized
|
||||
from hindsight_client_api.api_response import ApiResponse
|
||||
from hindsight_client_api.rest import RESTResponseType
|
||||
|
||||
|
||||
class EntitiesApi:
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, api_client=None) -> None:
|
||||
if api_client is None:
|
||||
api_client = ApiClient.get_default()
|
||||
self.api_client = api_client
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_entity(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
entity_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> EntityDetailResponse:
|
||||
"""Get entity details
|
||||
|
||||
Get detailed information about an entity including observations (mental model).
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param entity_id: (required)
|
||||
:type entity_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_entity_serialize(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityDetailResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_entity_with_http_info(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
entity_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[EntityDetailResponse]:
|
||||
"""Get entity details
|
||||
|
||||
Get detailed information about an entity including observations (mental model).
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param entity_id: (required)
|
||||
:type entity_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_entity_serialize(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityDetailResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_entity_without_preload_content(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
entity_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Get entity details
|
||||
|
||||
Get detailed information about an entity including observations (mental model).
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param entity_id: (required)
|
||||
:type entity_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_entity_serialize(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityDetailResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _get_entity_serialize(
|
||||
self,
|
||||
bank_id,
|
||||
entity_id,
|
||||
authorization,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if bank_id is not None:
|
||||
_path_params['bank_id'] = bank_id
|
||||
if entity_id is not None:
|
||||
_path_params['entity_id'] = entity_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
if authorization is not None:
|
||||
_header_params['authorization'] = authorization
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/v1/default/banks/{bank_id}/entities/{entity_id}',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_entities(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
limit: Annotated[Optional[StrictInt], Field(description="Maximum number of entities to return")] = None,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> EntityListResponse:
|
||||
"""List entities
|
||||
|
||||
List all entities (people, organizations, etc.) known by the bank, ordered by mention count.
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param limit: Maximum number of entities to return
|
||||
:type limit: int
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_entities_serialize(
|
||||
bank_id=bank_id,
|
||||
limit=limit,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityListResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_entities_with_http_info(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
limit: Annotated[Optional[StrictInt], Field(description="Maximum number of entities to return")] = None,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[EntityListResponse]:
|
||||
"""List entities
|
||||
|
||||
List all entities (people, organizations, etc.) known by the bank, ordered by mention count.
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param limit: Maximum number of entities to return
|
||||
:type limit: int
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_entities_serialize(
|
||||
bank_id=bank_id,
|
||||
limit=limit,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityListResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_entities_without_preload_content(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
limit: Annotated[Optional[StrictInt], Field(description="Maximum number of entities to return")] = None,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""List entities
|
||||
|
||||
List all entities (people, organizations, etc.) known by the bank, ordered by mention count.
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param limit: Maximum number of entities to return
|
||||
:type limit: int
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_entities_serialize(
|
||||
bank_id=bank_id,
|
||||
limit=limit,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityListResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _list_entities_serialize(
|
||||
self,
|
||||
bank_id,
|
||||
limit,
|
||||
authorization,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if bank_id is not None:
|
||||
_path_params['bank_id'] = bank_id
|
||||
# process the query parameters
|
||||
if limit is not None:
|
||||
|
||||
_query_params.append(('limit', limit))
|
||||
|
||||
# process the header parameters
|
||||
if authorization is not None:
|
||||
_header_params['authorization'] = authorization
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/v1/default/banks/{bank_id}/entities',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@validate_call
|
||||
async def regenerate_entity_observations(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
entity_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> EntityDetailResponse:
|
||||
"""Regenerate entity observations
|
||||
|
||||
Regenerate observations for an entity based on all facts mentioning it.
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param entity_id: (required)
|
||||
:type entity_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._regenerate_entity_observations_serialize(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityDetailResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def regenerate_entity_observations_with_http_info(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
entity_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[EntityDetailResponse]:
|
||||
"""Regenerate entity observations
|
||||
|
||||
Regenerate observations for an entity based on all facts mentioning it.
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param entity_id: (required)
|
||||
:type entity_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._regenerate_entity_observations_serialize(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityDetailResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def regenerate_entity_observations_without_preload_content(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
entity_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Regenerate entity observations
|
||||
|
||||
Regenerate observations for an entity based on all facts mentioning it.
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param entity_id: (required)
|
||||
:type entity_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._regenerate_entity_observations_serialize(
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "EntityDetailResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _regenerate_entity_observations_serialize(
|
||||
self,
|
||||
bank_id,
|
||||
entity_id,
|
||||
authorization,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if bank_id is not None:
|
||||
_path_params['bank_id'] = bank_id
|
||||
if entity_id is not None:
|
||||
_path_params['entity_id'] = entity_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
if authorization is not None:
|
||||
_header_params['authorization'] = authorization
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='POST',
|
||||
resource_path='/v1/default/banks/{bank_id}/entities/{entity_id}/regenerate',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -1,610 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
import warnings
|
||||
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import StrictStr
|
||||
from typing import Optional
|
||||
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse
|
||||
from hindsight_client_api.models.operations_list_response import OperationsListResponse
|
||||
|
||||
from hindsight_client_api.api_client import ApiClient, RequestSerialized
|
||||
from hindsight_client_api.api_response import ApiResponse
|
||||
from hindsight_client_api.rest import RESTResponseType
|
||||
|
||||
|
||||
class OperationsApi:
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, api_client=None) -> None:
|
||||
if api_client is None:
|
||||
api_client = ApiClient.get_default()
|
||||
self.api_client = api_client
|
||||
|
||||
|
||||
@validate_call
|
||||
async def cancel_operation(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
operation_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> CancelOperationResponse:
|
||||
"""Cancel a pending async operation
|
||||
|
||||
Cancel a pending async operation by removing it from the queue
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param operation_id: (required)
|
||||
:type operation_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._cancel_operation_serialize(
|
||||
bank_id=bank_id,
|
||||
operation_id=operation_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "CancelOperationResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def cancel_operation_with_http_info(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
operation_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[CancelOperationResponse]:
|
||||
"""Cancel a pending async operation
|
||||
|
||||
Cancel a pending async operation by removing it from the queue
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param operation_id: (required)
|
||||
:type operation_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._cancel_operation_serialize(
|
||||
bank_id=bank_id,
|
||||
operation_id=operation_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "CancelOperationResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def cancel_operation_without_preload_content(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
operation_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Cancel a pending async operation
|
||||
|
||||
Cancel a pending async operation by removing it from the queue
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param operation_id: (required)
|
||||
:type operation_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._cancel_operation_serialize(
|
||||
bank_id=bank_id,
|
||||
operation_id=operation_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "CancelOperationResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _cancel_operation_serialize(
|
||||
self,
|
||||
bank_id,
|
||||
operation_id,
|
||||
authorization,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if bank_id is not None:
|
||||
_path_params['bank_id'] = bank_id
|
||||
if operation_id is not None:
|
||||
_path_params['operation_id'] = operation_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
if authorization is not None:
|
||||
_header_params['authorization'] = authorization
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='DELETE',
|
||||
resource_path='/v1/default/banks/{bank_id}/operations/{operation_id}',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_operations(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> OperationsListResponse:
|
||||
"""List async operations
|
||||
|
||||
Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_operations_serialize(
|
||||
bank_id=bank_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "OperationsListResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_operations_with_http_info(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[OperationsListResponse]:
|
||||
"""List async operations
|
||||
|
||||
Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_operations_serialize(
|
||||
bank_id=bank_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "OperationsListResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_operations_without_preload_content(
|
||||
self,
|
||||
bank_id: StrictStr,
|
||||
authorization: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""List async operations
|
||||
|
||||
Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
:param authorization:
|
||||
:type authorization: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_operations_serialize(
|
||||
bank_id=bank_id,
|
||||
authorization=authorization,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "OperationsListResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _list_operations_serialize(
|
||||
self,
|
||||
bank_id,
|
||||
authorization,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if bank_id is not None:
|
||||
_path_params['bank_id'] = bank_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
if authorization is not None:
|
||||
_header_params['authorization'] = authorization
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/v1/default/banks/{bank_id}/operations',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
@@ -500,7 +500,7 @@ class Configuration:
|
||||
return "Python SDK Debug Report:\n"\
|
||||
"OS: {env}\n"\
|
||||
"Python Version: {pyversion}\n"\
|
||||
"Version of the API: 0.1.0\n"\
|
||||
"Version of the API: 1.0.0\n"\
|
||||
"SDK Package Version: 0.0.7".\
|
||||
format(env=sys.platform, pyversion=sys.version)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ Bank list item with profile summary.
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**bank_id** | **str** | |
|
||||
**name** | **str** | | [optional]
|
||||
**name** | **str** | |
|
||||
**disposition** | [**DispositionTraits**](DispositionTraits.md) | |
|
||||
**background** | **str** | | [optional]
|
||||
**background** | **str** | |
|
||||
**created_at** | **str** | | [optional]
|
||||
**updated_at** | **str** | | [optional]
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# BankStatsResponse
|
||||
|
||||
Response model for bank statistics endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**bank_id** | **str** | |
|
||||
**total_nodes** | **int** | |
|
||||
**total_links** | **int** | |
|
||||
**total_documents** | **int** | |
|
||||
**nodes_by_fact_type** | **Dict[str, int]** | |
|
||||
**links_by_link_type** | **Dict[str, int]** | |
|
||||
**links_by_fact_type** | **Dict[str, int]** | |
|
||||
**links_breakdown** | **Dict[str, Dict[str, int]]** | |
|
||||
**pending_operations** | **int** | |
|
||||
**failed_operations** | **int** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.bank_stats_response import BankStatsResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of BankStatsResponse from a JSON string
|
||||
bank_stats_response_instance = BankStatsResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(BankStatsResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
bank_stats_response_dict = bank_stats_response_instance.to_dict()
|
||||
# create an instance of BankStatsResponse from a dict
|
||||
bank_stats_response_from_dict = BankStatsResponse.from_dict(bank_stats_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
# hindsight_client_api.BanksApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**add_bank_background**](BanksApi.md#add_bank_background) | **POST** /v1/default/banks/{bank_id}/background | Add/merge memory bank background
|
||||
[**create_or_update_bank**](BanksApi.md#create_or_update_bank) | **PUT** /v1/default/banks/{bank_id} | Create or update memory bank
|
||||
[**delete_bank**](BanksApi.md#delete_bank) | **DELETE** /v1/default/banks/{bank_id} | Delete memory bank
|
||||
[**get_agent_stats**](BanksApi.md#get_agent_stats) | **GET** /v1/default/banks/{bank_id}/stats | Get statistics for memory bank
|
||||
[**get_bank_profile**](BanksApi.md#get_bank_profile) | **GET** /v1/default/banks/{bank_id}/profile | Get memory bank profile
|
||||
[**list_banks**](BanksApi.md#list_banks) | **GET** /v1/default/banks | List all memory banks
|
||||
[**update_bank_disposition**](BanksApi.md#update_bank_disposition) | **PUT** /v1/default/banks/{bank_id}/profile | Update memory bank disposition
|
||||
|
||||
|
||||
# **add_bank_background**
|
||||
> BackgroundResponse add_bank_background(bank_id, add_background_request, authorization=authorization)
|
||||
|
||||
Add/merge memory bank background
|
||||
|
||||
Add new background information or merge with existing. LLM intelligently resolves conflicts, normalizes to first person, and optionally infers disposition traits.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.add_background_request import AddBackgroundRequest
|
||||
from hindsight_client_api.models.background_response import BackgroundResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
add_background_request = hindsight_client_api.AddBackgroundRequest() # AddBackgroundRequest |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Add/merge memory bank background
|
||||
api_response = await api_instance.add_bank_background(bank_id, add_background_request, authorization=authorization)
|
||||
print("The response of BanksApi->add_bank_background:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->add_bank_background: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**add_background_request** | [**AddBackgroundRequest**](AddBackgroundRequest.md)| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**BackgroundResponse**](BackgroundResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **create_or_update_bank**
|
||||
> BankProfileResponse create_or_update_bank(bank_id, create_bank_request, authorization=authorization)
|
||||
|
||||
Create or update memory bank
|
||||
|
||||
Create a new agent or update existing agent with disposition and background. Auto-fills missing fields with defaults.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse
|
||||
from hindsight_client_api.models.create_bank_request import CreateBankRequest
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
create_bank_request = hindsight_client_api.CreateBankRequest() # CreateBankRequest |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Create or update memory bank
|
||||
api_response = await api_instance.create_or_update_bank(bank_id, create_bank_request, authorization=authorization)
|
||||
print("The response of BanksApi->create_or_update_bank:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->create_or_update_bank: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**create_bank_request** | [**CreateBankRequest**](CreateBankRequest.md)| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**BankProfileResponse**](BankProfileResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **delete_bank**
|
||||
> DeleteResponse delete_bank(bank_id, authorization=authorization)
|
||||
|
||||
Delete memory bank
|
||||
|
||||
Delete an entire memory bank including all memories, entities, documents, and the bank profile itself. This is a destructive operation that cannot be undone.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.delete_response import DeleteResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Delete memory bank
|
||||
api_response = await api_instance.delete_bank(bank_id, authorization=authorization)
|
||||
print("The response of BanksApi->delete_bank:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->delete_bank: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**DeleteResponse**](DeleteResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_agent_stats**
|
||||
> BankStatsResponse get_agent_stats(bank_id)
|
||||
|
||||
Get statistics for memory bank
|
||||
|
||||
Get statistics about nodes and links for a specific agent
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.bank_stats_response import BankStatsResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
|
||||
try:
|
||||
# Get statistics for memory bank
|
||||
api_response = await api_instance.get_agent_stats(bank_id)
|
||||
print("The response of BanksApi->get_agent_stats:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->get_agent_stats: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**BankStatsResponse**](BankStatsResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_bank_profile**
|
||||
> BankProfileResponse get_bank_profile(bank_id, authorization=authorization)
|
||||
|
||||
Get memory bank profile
|
||||
|
||||
Get disposition traits and background for a memory bank. Auto-creates agent with defaults if not exists.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Get memory bank profile
|
||||
api_response = await api_instance.get_bank_profile(bank_id, authorization=authorization)
|
||||
print("The response of BanksApi->get_bank_profile:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->get_bank_profile: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**BankProfileResponse**](BankProfileResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_banks**
|
||||
> BankListResponse list_banks(authorization=authorization)
|
||||
|
||||
List all memory banks
|
||||
|
||||
Get a list of all agents with their profiles
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.bank_list_response import BankListResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# List all memory banks
|
||||
api_response = await api_instance.list_banks(authorization=authorization)
|
||||
print("The response of BanksApi->list_banks:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->list_banks: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**BankListResponse**](BankListResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **update_bank_disposition**
|
||||
> BankProfileResponse update_bank_disposition(bank_id, update_disposition_request, authorization=authorization)
|
||||
|
||||
Update memory bank disposition
|
||||
|
||||
Update bank's disposition traits (skepticism, literalism, empathy)
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse
|
||||
from hindsight_client_api.models.update_disposition_request import UpdateDispositionRequest
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.BanksApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
update_disposition_request = hindsight_client_api.UpdateDispositionRequest() # UpdateDispositionRequest |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Update memory bank disposition
|
||||
api_response = await api_instance.update_bank_disposition(bank_id, update_disposition_request, authorization=authorization)
|
||||
print("The response of BanksApi->update_bank_disposition:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling BanksApi->update_bank_disposition: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**update_disposition_request** | [**UpdateDispositionRequest**](UpdateDispositionRequest.md)| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**BankProfileResponse**](BankProfileResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# CancelOperationResponse
|
||||
|
||||
Response model for cancel operation endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**success** | **bool** | |
|
||||
**message** | **str** | |
|
||||
**operation_id** | **str** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of CancelOperationResponse from a JSON string
|
||||
cancel_operation_response_instance = CancelOperationResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(CancelOperationResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
cancel_operation_response_dict = cancel_operation_response_instance.to_dict()
|
||||
# create an instance of CancelOperationResponse from a dict
|
||||
cancel_operation_response_from_dict = CancelOperationResponse.from_dict(cancel_operation_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
# DeleteDocumentResponse
|
||||
|
||||
Response model for delete document endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**success** | **bool** | |
|
||||
**message** | **str** | |
|
||||
**document_id** | **str** | |
|
||||
**memory_units_deleted** | **int** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.delete_document_response import DeleteDocumentResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of DeleteDocumentResponse from a JSON string
|
||||
delete_document_response_instance = DeleteDocumentResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(DeleteDocumentResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
delete_document_response_dict = delete_document_response_instance.to_dict()
|
||||
# create an instance of DeleteDocumentResponse from a dict
|
||||
delete_document_response_from_dict = DeleteDocumentResponse.from_dict(delete_document_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ Response model for delete operations.
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**success** | **bool** | |
|
||||
**message** | **str** | | [optional]
|
||||
**deleted_count** | **int** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
# hindsight_client_api.DocumentsApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**delete_document**](DocumentsApi.md#delete_document) | **DELETE** /v1/default/banks/{bank_id}/documents/{document_id} | Delete a document
|
||||
[**get_chunk**](DocumentsApi.md#get_chunk) | **GET** /v1/default/chunks/{chunk_id} | Get chunk details
|
||||
[**get_document**](DocumentsApi.md#get_document) | **GET** /v1/default/banks/{bank_id}/documents/{document_id} | Get document details
|
||||
[**list_documents**](DocumentsApi.md#list_documents) | **GET** /v1/default/banks/{bank_id}/documents | List documents
|
||||
|
||||
|
||||
# **delete_document**
|
||||
> DeleteDocumentResponse delete_document(bank_id, document_id, authorization=authorization)
|
||||
|
||||
Delete a document
|
||||
|
||||
Delete a document and all its associated memory units and links.
|
||||
|
||||
This will cascade delete:
|
||||
- The document itself
|
||||
- All memory units extracted from this document
|
||||
- All links (temporal, semantic, entity) associated with those memory units
|
||||
|
||||
This operation cannot be undone.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.delete_document_response import DeleteDocumentResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
document_id = 'document_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Delete a document
|
||||
api_response = await api_instance.delete_document(bank_id, document_id, authorization=authorization)
|
||||
print("The response of DocumentsApi->delete_document:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->delete_document: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**document_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**DeleteDocumentResponse**](DeleteDocumentResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_chunk**
|
||||
> ChunkResponse get_chunk(chunk_id, authorization=authorization)
|
||||
|
||||
Get chunk details
|
||||
|
||||
Get a specific chunk by its ID
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.chunk_response import ChunkResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
chunk_id = 'chunk_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Get chunk details
|
||||
api_response = await api_instance.get_chunk(chunk_id, authorization=authorization)
|
||||
print("The response of DocumentsApi->get_chunk:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->get_chunk: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**chunk_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**ChunkResponse**](ChunkResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_document**
|
||||
> DocumentResponse get_document(bank_id, document_id, authorization=authorization)
|
||||
|
||||
Get document details
|
||||
|
||||
Get a specific document including its original text
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.document_response import DocumentResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
document_id = 'document_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Get document details
|
||||
api_response = await api_instance.get_document(bank_id, document_id, authorization=authorization)
|
||||
print("The response of DocumentsApi->get_document:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->get_document: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**document_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**DocumentResponse**](DocumentResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_documents**
|
||||
> ListDocumentsResponse list_documents(bank_id, q=q, limit=limit, offset=offset, authorization=authorization)
|
||||
|
||||
List documents
|
||||
|
||||
List documents with pagination and optional search. Documents are the source content from which memory units are extracted.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.list_documents_response import ListDocumentsResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
q = 'q_example' # str | (optional)
|
||||
limit = 100 # int | (optional) (default to 100)
|
||||
offset = 0 # int | (optional) (default to 0)
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# List documents
|
||||
api_response = await api_instance.list_documents(bank_id, q=q, limit=limit, offset=offset, authorization=authorization)
|
||||
print("The response of DocumentsApi->list_documents:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->list_documents: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**q** | **str**| | [optional]
|
||||
**limit** | **int**| | [optional] [default to 100]
|
||||
**offset** | **int**| | [optional] [default to 0]
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**ListDocumentsResponse**](ListDocumentsResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
# hindsight_client_api.EntitiesApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**get_entity**](EntitiesApi.md#get_entity) | **GET** /v1/default/banks/{bank_id}/entities/{entity_id} | Get entity details
|
||||
[**list_entities**](EntitiesApi.md#list_entities) | **GET** /v1/default/banks/{bank_id}/entities | List entities
|
||||
[**regenerate_entity_observations**](EntitiesApi.md#regenerate_entity_observations) | **POST** /v1/default/banks/{bank_id}/entities/{entity_id}/regenerate | Regenerate entity observations
|
||||
|
||||
|
||||
# **get_entity**
|
||||
> EntityDetailResponse get_entity(bank_id, entity_id, authorization=authorization)
|
||||
|
||||
Get entity details
|
||||
|
||||
Get detailed information about an entity including observations (mental model).
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.entity_detail_response import EntityDetailResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.EntitiesApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
entity_id = 'entity_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Get entity details
|
||||
api_response = await api_instance.get_entity(bank_id, entity_id, authorization=authorization)
|
||||
print("The response of EntitiesApi->get_entity:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling EntitiesApi->get_entity: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**entity_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**EntityDetailResponse**](EntityDetailResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_entities**
|
||||
> EntityListResponse list_entities(bank_id, limit=limit, authorization=authorization)
|
||||
|
||||
List entities
|
||||
|
||||
List all entities (people, organizations, etc.) known by the bank, ordered by mention count.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.entity_list_response import EntityListResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.EntitiesApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
limit = 100 # int | Maximum number of entities to return (optional) (default to 100)
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# List entities
|
||||
api_response = await api_instance.list_entities(bank_id, limit=limit, authorization=authorization)
|
||||
print("The response of EntitiesApi->list_entities:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling EntitiesApi->list_entities: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**limit** | **int**| Maximum number of entities to return | [optional] [default to 100]
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**EntityListResponse**](EntityListResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **regenerate_entity_observations**
|
||||
> EntityDetailResponse regenerate_entity_observations(bank_id, entity_id, authorization=authorization)
|
||||
|
||||
Regenerate entity observations
|
||||
|
||||
Regenerate observations for an entity based on all facts mentioning it.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.entity_detail_response import EntityDetailResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.EntitiesApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
entity_id = 'entity_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Regenerate entity observations
|
||||
api_response = await api_instance.regenerate_entity_observations(bank_id, entity_id, authorization=authorization)
|
||||
print("The response of EntitiesApi->regenerate_entity_observations:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling EntitiesApi->regenerate_entity_observations: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**entity_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**EntityDetailResponse**](EntityDetailResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
@@ -1,499 +0,0 @@
|
||||
# hindsight_client_api.MemoryApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**clear_bank_memories**](MemoryApi.md#clear_bank_memories) | **DELETE** /v1/default/banks/{bank_id}/memories | Clear memory bank memories
|
||||
[**get_graph**](MemoryApi.md#get_graph) | **GET** /v1/default/banks/{bank_id}/graph | Get memory graph data
|
||||
[**list_memories**](MemoryApi.md#list_memories) | **GET** /v1/default/banks/{bank_id}/memories/list | List memory units
|
||||
[**recall_memories**](MemoryApi.md#recall_memories) | **POST** /v1/default/banks/{bank_id}/memories/recall | Recall memory
|
||||
[**reflect**](MemoryApi.md#reflect) | **POST** /v1/default/banks/{bank_id}/reflect | Reflect and generate answer
|
||||
[**retain_memories**](MemoryApi.md#retain_memories) | **POST** /v1/default/banks/{bank_id}/memories | Retain memories
|
||||
|
||||
|
||||
# **clear_bank_memories**
|
||||
> DeleteResponse clear_bank_memories(bank_id, type=type, authorization=authorization)
|
||||
|
||||
Clear memory bank memories
|
||||
|
||||
Delete memory units for a memory bank. Optionally filter by type (world, experience, opinion) to delete only specific types. This is a destructive operation that cannot be undone. The bank profile (disposition and background) will be preserved.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.delete_response import DeleteResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.MemoryApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
type = 'type_example' # str | Optional fact type filter (world, experience, opinion) (optional)
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Clear memory bank memories
|
||||
api_response = await api_instance.clear_bank_memories(bank_id, type=type, authorization=authorization)
|
||||
print("The response of MemoryApi->clear_bank_memories:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling MemoryApi->clear_bank_memories: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**type** | **str**| Optional fact type filter (world, experience, opinion) | [optional]
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**DeleteResponse**](DeleteResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_graph**
|
||||
> GraphDataResponse get_graph(bank_id, type=type, authorization=authorization)
|
||||
|
||||
Get memory graph data
|
||||
|
||||
Retrieve graph data for visualization, optionally filtered by type (world/experience/opinion). Limited to 1000 most recent items.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.graph_data_response import GraphDataResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.MemoryApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
type = 'type_example' # str | (optional)
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Get memory graph data
|
||||
api_response = await api_instance.get_graph(bank_id, type=type, authorization=authorization)
|
||||
print("The response of MemoryApi->get_graph:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling MemoryApi->get_graph: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**type** | **str**| | [optional]
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**GraphDataResponse**](GraphDataResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_memories**
|
||||
> ListMemoryUnitsResponse list_memories(bank_id, type=type, q=q, limit=limit, offset=offset, authorization=authorization)
|
||||
|
||||
List memory units
|
||||
|
||||
List memory units with pagination and optional full-text search. Supports filtering by type. Results are sorted by most recent first (mentioned_at DESC, then created_at DESC).
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.MemoryApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
type = 'type_example' # str | (optional)
|
||||
q = 'q_example' # str | (optional)
|
||||
limit = 100 # int | (optional) (default to 100)
|
||||
offset = 0 # int | (optional) (default to 0)
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# List memory units
|
||||
api_response = await api_instance.list_memories(bank_id, type=type, q=q, limit=limit, offset=offset, authorization=authorization)
|
||||
print("The response of MemoryApi->list_memories:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling MemoryApi->list_memories: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**type** | **str**| | [optional]
|
||||
**q** | **str**| | [optional]
|
||||
**limit** | **int**| | [optional] [default to 100]
|
||||
**offset** | **int**| | [optional] [default to 0]
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**ListMemoryUnitsResponse**](ListMemoryUnitsResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **recall_memories**
|
||||
> RecallResponse recall_memories(bank_id, recall_request, authorization=authorization)
|
||||
|
||||
Recall memory
|
||||
|
||||
Recall memory using semantic similarity and spreading activation.
|
||||
|
||||
The type parameter is optional and must be one of:
|
||||
- `world`: General knowledge about people, places, events, and things that happen
|
||||
- `experience`: Memories about experience, conversations, actions taken, and tasks performed
|
||||
- `opinion`: The bank's formed beliefs, perspectives, and viewpoints
|
||||
|
||||
Set `include_entities=true` to get entity observations alongside recall results.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.recall_request import RecallRequest
|
||||
from hindsight_client_api.models.recall_response import RecallResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.MemoryApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
recall_request = hindsight_client_api.RecallRequest() # RecallRequest |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Recall memory
|
||||
api_response = await api_instance.recall_memories(bank_id, recall_request, authorization=authorization)
|
||||
print("The response of MemoryApi->recall_memories:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling MemoryApi->recall_memories: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**recall_request** | [**RecallRequest**](RecallRequest.md)| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**RecallResponse**](RecallResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **reflect**
|
||||
> ReflectResponse reflect(bank_id, reflect_request, authorization=authorization)
|
||||
|
||||
Reflect and generate answer
|
||||
|
||||
Reflect and formulate an answer using bank identity, world facts, and opinions.
|
||||
|
||||
This endpoint:
|
||||
1. Retrieves experience (conversations and events)
|
||||
2. Retrieves world facts relevant to the query
|
||||
3. Retrieves existing opinions (bank's perspectives)
|
||||
4. Uses LLM to formulate a contextual answer
|
||||
5. Extracts and stores any new opinions formed
|
||||
6. Returns plain text answer, the facts used, and new opinions
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.reflect_request import ReflectRequest
|
||||
from hindsight_client_api.models.reflect_response import ReflectResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.MemoryApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
reflect_request = hindsight_client_api.ReflectRequest() # ReflectRequest |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Reflect and generate answer
|
||||
api_response = await api_instance.reflect(bank_id, reflect_request, authorization=authorization)
|
||||
print("The response of MemoryApi->reflect:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling MemoryApi->reflect: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**reflect_request** | [**ReflectRequest**](ReflectRequest.md)| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**ReflectResponse**](ReflectResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **retain_memories**
|
||||
> RetainResponse retain_memories(bank_id, retain_request, authorization=authorization)
|
||||
|
||||
Retain memories
|
||||
|
||||
Retain memory items with automatic fact extraction.
|
||||
|
||||
This is the main endpoint for storing memories. It supports both synchronous and asynchronous processing via the `async` parameter.
|
||||
|
||||
**Features:**
|
||||
- Efficient batch processing
|
||||
- Automatic fact extraction from natural language
|
||||
- Entity recognition and linking
|
||||
- Document tracking with automatic upsert (when document_id is provided)
|
||||
- Temporal and semantic linking
|
||||
- Optional asynchronous processing
|
||||
|
||||
**The system automatically:**
|
||||
1. Extracts semantic facts from the content
|
||||
2. Generates embeddings
|
||||
3. Deduplicates similar facts
|
||||
4. Creates temporal, semantic, and entity links
|
||||
5. Tracks document metadata
|
||||
|
||||
**When `async=true`:** Returns immediately after queuing. Use the operations endpoint to monitor progress.
|
||||
|
||||
**When `async=false` (default):** Waits for processing to complete.
|
||||
|
||||
**Note:** If a memory item has a `document_id` that already exists, the old document and its memory units will be deleted before creating new ones (upsert behavior).
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.retain_request import RetainRequest
|
||||
from hindsight_client_api.models.retain_response import RetainResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.MemoryApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
retain_request = hindsight_client_api.RetainRequest() # RetainRequest |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Retain memories
|
||||
api_response = await api_instance.retain_memories(bank_id, retain_request, authorization=authorization)
|
||||
print("The response of MemoryApi->retain_memories:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling MemoryApi->retain_memories: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**retain_request** | [**RetainRequest**](RetainRequest.md)| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**RetainResponse**](RetainResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# OperationResponse
|
||||
|
||||
Response model for a single async operation.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **str** | |
|
||||
**task_type** | **str** | |
|
||||
**items_count** | **int** | |
|
||||
**document_id** | **str** | |
|
||||
**created_at** | **str** | |
|
||||
**status** | **str** | |
|
||||
**error_message** | **str** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.operation_response import OperationResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of OperationResponse from a JSON string
|
||||
operation_response_instance = OperationResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(OperationResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
operation_response_dict = operation_response_instance.to_dict()
|
||||
# create an instance of OperationResponse from a dict
|
||||
operation_response_from_dict = OperationResponse.from_dict(operation_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
# hindsight_client_api.OperationsApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**cancel_operation**](OperationsApi.md#cancel_operation) | **DELETE** /v1/default/banks/{bank_id}/operations/{operation_id} | Cancel a pending async operation
|
||||
[**list_operations**](OperationsApi.md#list_operations) | **GET** /v1/default/banks/{bank_id}/operations | List async operations
|
||||
|
||||
|
||||
# **cancel_operation**
|
||||
> CancelOperationResponse cancel_operation(bank_id, operation_id, authorization=authorization)
|
||||
|
||||
Cancel a pending async operation
|
||||
|
||||
Cancel a pending async operation by removing it from the queue
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.OperationsApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
operation_id = 'operation_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# Cancel a pending async operation
|
||||
api_response = await api_instance.cancel_operation(bank_id, operation_id, authorization=authorization)
|
||||
print("The response of OperationsApi->cancel_operation:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling OperationsApi->cancel_operation: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**operation_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**CancelOperationResponse**](CancelOperationResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_operations**
|
||||
> OperationsListResponse list_operations(bank_id, authorization=authorization)
|
||||
|
||||
List async operations
|
||||
|
||||
Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.operations_list_response import OperationsListResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.OperationsApi(api_client)
|
||||
bank_id = 'bank_id_example' # str |
|
||||
authorization = 'authorization_example' # str | (optional)
|
||||
|
||||
try:
|
||||
# List async operations
|
||||
api_response = await api_instance.list_operations(bank_id, authorization=authorization)
|
||||
print("The response of OperationsApi->list_operations:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling OperationsApi->list_operations: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bank_id** | **str**| |
|
||||
**authorization** | **str**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**OperationsListResponse**](OperationsListResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# OperationsListResponse
|
||||
|
||||
Response model for list operations endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**bank_id** | **str** | |
|
||||
**operations** | [**List[OperationResponse]**](OperationResponse.md) | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.operations_list_response import OperationsListResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of OperationsListResponse from a JSON string
|
||||
operations_list_response_instance = OperationsListResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(OperationsListResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
operations_list_response_dict = operations_list_response_instance.to_dict()
|
||||
# create an instance of OperationsListResponse from a dict
|
||||
operations_list_response_from_dict = OperationsListResponse.from_dict(operations_list_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ Name | Type | Description | Notes
|
||||
**query** | **str** | |
|
||||
**budget** | [**Budget**](Budget.md) | | [optional]
|
||||
**context** | **str** | | [optional]
|
||||
**max_tokens** | **int** | Maximum tokens for the response | [optional] [default to 4096]
|
||||
**include** | [**ReflectIncludeOptions**](ReflectIncludeOptions.md) | Options for including additional data (disabled by default) | [optional]
|
||||
**response_schema** | **Dict[str, object]** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**text** | **str** | |
|
||||
**based_on** | [**List[ReflectFact]**](ReflectFact.md) | | [optional] [default to []]
|
||||
**structured_output** | **Dict[str, object]** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
@@ -18,20 +18,16 @@ from hindsight_client_api.models.background_response import BackgroundResponse
|
||||
from hindsight_client_api.models.bank_list_item import BankListItem
|
||||
from hindsight_client_api.models.bank_list_response import BankListResponse
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse
|
||||
from hindsight_client_api.models.bank_stats_response import BankStatsResponse
|
||||
from hindsight_client_api.models.budget import Budget
|
||||
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse
|
||||
from hindsight_client_api.models.chunk_data import ChunkData
|
||||
from hindsight_client_api.models.chunk_include_options import ChunkIncludeOptions
|
||||
from hindsight_client_api.models.chunk_response import ChunkResponse
|
||||
from hindsight_client_api.models.create_bank_request import CreateBankRequest
|
||||
from hindsight_client_api.models.delete_document_response import DeleteDocumentResponse
|
||||
from hindsight_client_api.models.delete_response import DeleteResponse
|
||||
from hindsight_client_api.models.disposition_traits import DispositionTraits
|
||||
from hindsight_client_api.models.document_response import DocumentResponse
|
||||
from hindsight_client_api.models.entity_detail_response import EntityDetailResponse
|
||||
from hindsight_client_api.models.entity_include_options import EntityIncludeOptions
|
||||
from hindsight_client_api.models.entity_input import EntityInput
|
||||
from hindsight_client_api.models.entity_list_item import EntityListItem
|
||||
from hindsight_client_api.models.entity_list_response import EntityListResponse
|
||||
from hindsight_client_api.models.entity_observation_response import EntityObservationResponse
|
||||
@@ -42,8 +38,6 @@ from hindsight_client_api.models.include_options import IncludeOptions
|
||||
from hindsight_client_api.models.list_documents_response import ListDocumentsResponse
|
||||
from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse
|
||||
from hindsight_client_api.models.memory_item import MemoryItem
|
||||
from hindsight_client_api.models.operation_response import OperationResponse
|
||||
from hindsight_client_api.models.operations_list_response import OperationsListResponse
|
||||
from hindsight_client_api.models.recall_request import RecallRequest
|
||||
from hindsight_client_api.models.recall_response import RecallResponse
|
||||
from hindsight_client_api.models.recall_result import RecallResult
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
@@ -28,9 +28,9 @@ class BankListItem(BaseModel):
|
||||
Bank list item with profile summary.
|
||||
""" # noqa: E501
|
||||
bank_id: StrictStr
|
||||
name: Optional[StrictStr] = None
|
||||
name: StrictStr
|
||||
disposition: DispositionTraits
|
||||
background: Optional[StrictStr] = None
|
||||
background: StrictStr
|
||||
created_at: Optional[StrictStr] = None
|
||||
updated_at: Optional[StrictStr] = None
|
||||
__properties: ClassVar[List[str]] = ["bank_id", "name", "disposition", "background", "created_at", "updated_at"]
|
||||
@@ -77,16 +77,6 @@ class BankListItem(BaseModel):
|
||||
# override the default output from pydantic by calling `to_dict()` of disposition
|
||||
if self.disposition:
|
||||
_dict['disposition'] = self.disposition.to_dict()
|
||||
# set to None if name (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.name is None and "name" in self.model_fields_set:
|
||||
_dict['name'] = None
|
||||
|
||||
# set to None if background (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.background is None and "background" in self.model_fields_set:
|
||||
_dict['background'] = None
|
||||
|
||||
# set to None if created_at (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.created_at is None and "created_at" in self.model_fields_set:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import pprint
|
||||
import re # noqa: F401
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
class BankStatsResponse(BaseModel):
|
||||
"""
|
||||
Response model for bank statistics endpoint.
|
||||
""" # noqa: E501
|
||||
bank_id: StrictStr
|
||||
total_nodes: StrictInt
|
||||
total_links: StrictInt
|
||||
total_documents: StrictInt
|
||||
nodes_by_fact_type: Dict[str, StrictInt]
|
||||
links_by_link_type: Dict[str, StrictInt]
|
||||
links_by_fact_type: Dict[str, StrictInt]
|
||||
links_breakdown: Dict[str, Dict[str, StrictInt]]
|
||||
pending_operations: StrictInt
|
||||
failed_operations: StrictInt
|
||||
__properties: ClassVar[List[str]] = ["bank_id", "total_nodes", "total_links", "total_documents", "nodes_by_fact_type", "links_by_link_type", "links_by_fact_type", "links_breakdown", "pending_operations", "failed_operations"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
validate_assignment=True,
|
||||
protected_namespaces=(),
|
||||
)
|
||||
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Returns the string representation of the model using alias"""
|
||||
return pprint.pformat(self.model_dump(by_alias=True))
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Returns the JSON representation of the model using alias"""
|
||||
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> Optional[Self]:
|
||||
"""Create an instance of BankStatsResponse from a JSON string"""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Return the dictionary representation of the model using alias.
|
||||
|
||||
This has the following differences from calling pydantic's
|
||||
`self.model_dump(by_alias=True)`:
|
||||
|
||||
* `None` is only added to the output dict for nullable fields that
|
||||
were set at model initialization. Other fields with value `None`
|
||||
are ignored.
|
||||
"""
|
||||
excluded_fields: Set[str] = set([
|
||||
])
|
||||
|
||||
_dict = self.model_dump(
|
||||
by_alias=True,
|
||||
exclude=excluded_fields,
|
||||
exclude_none=True,
|
||||
)
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
||||
"""Create an instance of BankStatsResponse from a dict"""
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"bank_id": obj.get("bank_id"),
|
||||
"total_nodes": obj.get("total_nodes"),
|
||||
"total_links": obj.get("total_links"),
|
||||
"total_documents": obj.get("total_documents"),
|
||||
"nodes_by_fact_type": obj.get("nodes_by_fact_type"),
|
||||
"links_by_link_type": obj.get("links_by_link_type"),
|
||||
"links_by_fact_type": obj.get("links_by_fact_type"),
|
||||
"links_breakdown": obj.get("links_breakdown"),
|
||||
"pending_operations": obj.get("pending_operations"),
|
||||
"failed_operations": obj.get("failed_operations")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import pprint
|
||||
import re # noqa: F401
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, StrictBool, StrictStr
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
class CancelOperationResponse(BaseModel):
|
||||
"""
|
||||
Response model for cancel operation endpoint.
|
||||
""" # noqa: E501
|
||||
success: StrictBool
|
||||
message: StrictStr
|
||||
operation_id: StrictStr
|
||||
__properties: ClassVar[List[str]] = ["success", "message", "operation_id"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
validate_assignment=True,
|
||||
protected_namespaces=(),
|
||||
)
|
||||
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Returns the string representation of the model using alias"""
|
||||
return pprint.pformat(self.model_dump(by_alias=True))
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Returns the JSON representation of the model using alias"""
|
||||
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> Optional[Self]:
|
||||
"""Create an instance of CancelOperationResponse from a JSON string"""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Return the dictionary representation of the model using alias.
|
||||
|
||||
This has the following differences from calling pydantic's
|
||||
`self.model_dump(by_alias=True)`:
|
||||
|
||||
* `None` is only added to the output dict for nullable fields that
|
||||
were set at model initialization. Other fields with value `None`
|
||||
are ignored.
|
||||
"""
|
||||
excluded_fields: Set[str] = set([
|
||||
])
|
||||
|
||||
_dict = self.model_dump(
|
||||
by_alias=True,
|
||||
exclude=excluded_fields,
|
||||
exclude_none=True,
|
||||
)
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
||||
"""Create an instance of CancelOperationResponse from a dict"""
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"success": obj.get("success"),
|
||||
"message": obj.get("message"),
|
||||
"operation_id": obj.get("operation_id")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import pprint
|
||||
import re # noqa: F401
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, StrictBool, StrictInt, StrictStr
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
class DeleteDocumentResponse(BaseModel):
|
||||
"""
|
||||
Response model for delete document endpoint.
|
||||
""" # noqa: E501
|
||||
success: StrictBool
|
||||
message: StrictStr
|
||||
document_id: StrictStr
|
||||
memory_units_deleted: StrictInt
|
||||
__properties: ClassVar[List[str]] = ["success", "message", "document_id", "memory_units_deleted"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
validate_assignment=True,
|
||||
protected_namespaces=(),
|
||||
)
|
||||
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Returns the string representation of the model using alias"""
|
||||
return pprint.pformat(self.model_dump(by_alias=True))
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Returns the JSON representation of the model using alias"""
|
||||
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> Optional[Self]:
|
||||
"""Create an instance of DeleteDocumentResponse from a JSON string"""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Return the dictionary representation of the model using alias.
|
||||
|
||||
This has the following differences from calling pydantic's
|
||||
`self.model_dump(by_alias=True)`:
|
||||
|
||||
* `None` is only added to the output dict for nullable fields that
|
||||
were set at model initialization. Other fields with value `None`
|
||||
are ignored.
|
||||
"""
|
||||
excluded_fields: Set[str] = set([
|
||||
])
|
||||
|
||||
_dict = self.model_dump(
|
||||
by_alias=True,
|
||||
exclude=excluded_fields,
|
||||
exclude_none=True,
|
||||
)
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
||||
"""Create an instance of DeleteDocumentResponse from a dict"""
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"success": obj.get("success"),
|
||||
"message": obj.get("message"),
|
||||
"document_id": obj.get("document_id"),
|
||||
"memory_units_deleted": obj.get("memory_units_deleted")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
@@ -17,8 +17,8 @@ import pprint
|
||||
import re # noqa: F401
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, StrictBool, StrictInt, StrictStr
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
from pydantic import BaseModel, ConfigDict, StrictBool
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
@@ -27,9 +27,7 @@ class DeleteResponse(BaseModel):
|
||||
Response model for delete operations.
|
||||
""" # noqa: E501
|
||||
success: StrictBool
|
||||
message: Optional[StrictStr] = None
|
||||
deleted_count: Optional[StrictInt] = None
|
||||
__properties: ClassVar[List[str]] = ["success", "message", "deleted_count"]
|
||||
__properties: ClassVar[List[str]] = ["success"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@@ -70,16 +68,6 @@ class DeleteResponse(BaseModel):
|
||||
exclude=excluded_fields,
|
||||
exclude_none=True,
|
||||
)
|
||||
# set to None if message (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.message is None and "message" in self.model_fields_set:
|
||||
_dict['message'] = None
|
||||
|
||||
# set to None if deleted_count (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.deleted_count is None and "deleted_count" in self.model_fields_set:
|
||||
_dict['deleted_count'] = None
|
||||
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
@@ -92,9 +80,7 @@ class DeleteResponse(BaseModel):
|
||||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"success": obj.get("success"),
|
||||
"message": obj.get("message"),
|
||||
"deleted_count": obj.get("deleted_count")
|
||||
"success": obj.get("success")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Hindsight HTTP API
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import pprint
|
||||
import re # noqa: F401
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class EntityInput(BaseModel):
|
||||
"""
|
||||
Entity to associate with retained content.
|
||||
""" # noqa: E501
|
||||
|
||||
text: StrictStr = Field(description="The entity name/text")
|
||||
type: Optional[StrictStr] = Field(
|
||||
default=None, description="Optional entity type (e.g., 'PERSON', 'ORG', 'CONCEPT')"
|
||||
)
|
||||
__properties: ClassVar[List[str]] = ["text", "type"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
validate_assignment=True,
|
||||
protected_namespaces=(),
|
||||
)
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Returns the string representation of the model using alias"""
|
||||
return pprint.pformat(self.model_dump(by_alias=True))
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Returns the JSON representation of the model using alias"""
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> Optional[Self]:
|
||||
"""Create an instance of EntityInput from a JSON string"""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Return the dictionary representation of the model using alias."""
|
||||
excluded_fields: Set[str] = set([])
|
||||
|
||||
_dict = self.model_dump(
|
||||
by_alias=True,
|
||||
exclude=excluded_fields,
|
||||
exclude_none=True,
|
||||
)
|
||||
# set to None if type (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.type is None and "type" in self.model_fields_set:
|
||||
_dict["type"] = None
|
||||
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
||||
"""Create an instance of EntityInput from a dict"""
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({"text": obj.get("text"), "type": obj.get("type")})
|
||||
return _obj
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 0.1.0
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
@@ -18,11 +18,10 @@ import re # noqa: F401
|
||||
import json
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
||||
from pydantic import BaseModel, ConfigDict, StrictStr
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
from hindsight_client_api.models.entity_input import EntityInput
|
||||
|
||||
class MemoryItem(BaseModel):
|
||||
"""
|
||||
@@ -33,11 +32,7 @@ class MemoryItem(BaseModel):
|
||||
context: Optional[StrictStr] = None
|
||||
metadata: Optional[Dict[str, StrictStr]] = None
|
||||
document_id: Optional[StrictStr] = None
|
||||
entities: Optional[List[EntityInput]] = Field(
|
||||
default=None,
|
||||
description="Optional entities to combine with auto-extracted entities."
|
||||
)
|
||||
__properties: ClassVar[List[str]] = ["content", "timestamp", "context", "metadata", "document_id", "entities"]
|
||||
__properties: ClassVar[List[str]] = ["content", "timestamp", "context", "metadata", "document_id"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@@ -98,11 +93,6 @@ class MemoryItem(BaseModel):
|
||||
if self.document_id is None and "document_id" in self.model_fields_set:
|
||||
_dict['document_id'] = None
|
||||
|
||||
# set to None if entities (nullable) is None
|
||||
# and model_fields_set contains the field
|
||||
if self.entities is None and "entities" in self.model_fields_set:
|
||||
_dict['entities'] = None
|
||||
|
||||
return _dict
|
||||
|
||||
@classmethod
|
||||
@@ -119,8 +109,7 @@ class MemoryItem(BaseModel):
|
||||
"timestamp": obj.get("timestamp"),
|
||||
"context": obj.get("context"),
|
||||
"metadata": obj.get("metadata"),
|
||||
"document_id": obj.get("document_id"),
|
||||
"entities": [EntityInput.from_dict(_item) for _item in obj["entities"]] if obj.get("entities") is not None else None
|
||||
"document_id": obj.get("document_id")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user