* feat: entity labels * feat: entity labels — optional, free_values, multi_value, UI polish Completes the entity labels system: **Schema & extraction** - Dynamic Pydantic Labels model per fact: each group becomes a typed field (Literal | None, list[Literal], str | None, or list[str]) - `optional: bool` flag per group — non-optional enum fields appear in JSON schema required array so structured-output providers enforce them - `free_values: bool` flag per group — accepts any LLM-generated string instead of a predefined enum; example values shown as hints in prompt - New `is_label_entity()` helper for labels-only mode filtering that handles both enum lookup and free_values key-prefix matching - Sentinel rejection: "None"/"null"/"n/a" strings dropped in post-processing **BM25 / dense retrieval** - `text_signals` column on memory_units: entity names + date tokens for enriched BM25 indexing without polluting stored fact text - Dense embedding includes occurred_end when it differs from occurred_start - Alembic migration z1u2v3w4x5y6 (merge revision fixing two heads) **UI (bank-config-view)** - Shadcn Switch replaces custom Toggle for both entity-labels and observations - Shadcn Checkbox for multi/optional/free_values per group - Input heights bumped to h-8 throughout the editor - "Label Groups" → "Entity Labels", "Free-form entities" → "Entities" - Free-text groups show "Example hints" banner in values section **Tests (45 unit + 3 LLM integration)** - build_labels_model: single, multi, mixed, free_values optional/required/multi - is_label_entity: enum match, free_values prefix match, no false positives - Post-processing: null/absent/string-None/free_values/sentinels/multi-value - Schema: labels in required, structured object, no labels when unconfigured - LLM integration: single-value enum, multi-value enum, free_values retain **Docs** - retain.md: new Entity Labels section covering groups, flags, examples - configuration.md: retain_free_form_entities env var + entity_labels note * fix(tests): update hierarchical fields count for entity_labels additions entity_labels and retain_free_form_entities are hierarchical fields, bumping the expected count from 11 to 13. * fix(migration): rename text_signals revision to avoid collision with main Main branch claimed z1u2v3w4x5y6 for observation_scopes. Rename our text_signals migration to a2b3c4d5e6f7, chaining after z1u2v3w4x5y6. * refactor(entity-labels): simplify free_values — always str|None, no multi - free_values groups always produce str | None (multi_value and optional flags are ignored for free text groups — always optional, never multi) - Prompt section for free_values groups shows only key + description, no values list (users put examples in the description instead) - UI: section title "Entities", toggle "Free Form Entities", replace per-group checkboxes with a type dropdown (Enum / Free text); only show multi checkbox and values list when type is Enum - Update tests to reflect new behaviour * refactor(entity-labels): replace free_values/multi_value booleans with type field - LabelGroup now uses type: "value" | "multi-values" | "text" instead of free_values/multi_value boolean pair - Backward-compat migration converts legacy dicts automatically - Rename retain_free_form_entities → entities_allow_free_form throughout - Update UI dropdown to show Single value / Multi-values / Free text - Remove separate multi checkbox (captured by type selection) - Update docs examples and configuration.md - Update all tests to use new field names * fix(migration): backfill observation_scopes column for DBs with swapped z1u2v3w4x5y6 Local DBs that had z1u2v3w4x5y6 applied when it referred to the old text_signals migration (before it was renamed to a2b3c4d5e6f7) won't have observation_scopes in their memory_units table. This migration adds the column with IF NOT EXISTS so it's a no-op on clean installs. * feat(entity-labels): add tag field to auto-populate memory unit tags from labels When a LabelGroup has tag=True, extracted key:value entities for that group are automatically written to the memory unit's tags array. This lets entity labels double as tags, enabling immediate filtering via the existing tags/tags_match API params with no extra infrastructure. - Add tag: bool = False to LabelGroup - _inject_label_tags() helper called in both sync and batch extraction paths - UI: add Tag checkbox per label group row - Docs: document the new tag field - Tests: 4 new unit tests covering all tag injection paths * style: ruff format migration file * fix(migration): fix multiple alembic heads after rebase — point text_signals after nullable_event_date * fix(clients): update timestamp field to use Timestamp wrapper type after timestamp=unset feature * style: ruff format agent.py * fix(docs): update Go quickstart example to use NullableTimestamp for timestamp field
130 lines
3.8 KiB
Bash
Executable file
130 lines
3.8 KiB
Bash
Executable file
#!/bin/bash
|
|
set -e
|
|
|
|
# Service flags (default to true if not set)
|
|
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|')
|
|
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
|
|
PIDS=()
|
|
|
|
# Start API if enabled
|
|
if [ "$ENABLE_API" = "true" ]; then
|
|
cd /app/api
|
|
# Run API directly - Python's PYTHONUNBUFFERED=1 handles output buffering
|
|
hindsight-api &
|
|
API_PID=$!
|
|
PIDS+=($API_PID)
|
|
|
|
# Wait for API to be ready
|
|
for i in {1..60}; do
|
|
if curl -sf http://localhost:8888/health &>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
else
|
|
echo "API disabled (HINDSIGHT_ENABLE_API=false)"
|
|
fi
|
|
|
|
# Start Control Plane if enabled
|
|
if [ "$ENABLE_CP" = "true" ]; then
|
|
echo "🎛️ Starting Control Plane..."
|
|
cd /app/control-plane
|
|
PORT="${HINDSIGHT_CP_PORT:-9999}" node server.js &
|
|
CP_PID=$!
|
|
PIDS+=($CP_PID)
|
|
else
|
|
echo "Control Plane disabled (HINDSIGHT_ENABLE_CP=false)"
|
|
fi
|
|
|
|
# Print status
|
|
echo ""
|
|
echo "✅ Hindsight is running!"
|
|
echo ""
|
|
echo "📍 Access:"
|
|
if [ "$ENABLE_CP" = "true" ]; then
|
|
echo " Control Plane: http://localhost:${HINDSIGHT_CP_PORT:-9999}"
|
|
fi
|
|
if [ "$ENABLE_API" = "true" ]; then
|
|
echo " API: http://localhost:8888"
|
|
fi
|
|
echo ""
|
|
|
|
# Check if any services are running
|
|
if [ ${#PIDS[@]} -eq 0 ]; then
|
|
echo "❌ No services enabled! Set HINDSIGHT_ENABLE_API=true or HINDSIGHT_ENABLE_CP=true"
|
|
exit 1
|
|
fi
|
|
|
|
# Wait for any process to exit
|
|
wait -n
|
|
|
|
# Exit with status of first exited process
|
|
exit $?
|