diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a1d41872..8f8d1d1a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ name: CI on: pull_request: branches: [ main ] + workflow_dispatch: concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} @@ -199,7 +200,6 @@ jobs: HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -421,7 +421,6 @@ jobs: HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Prefer CPU-only PyTorch in CI (but keep PyPI for everything else) - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -484,7 +483,6 @@ jobs: HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Prefer CPU-only PyTorch in CI (but keep PyPI for everything else) - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -587,7 +585,6 @@ jobs: HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Prefer CPU-only PyTorch in CI (but keep PyPI for everything else) - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -719,7 +716,6 @@ jobs: HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Prefer CPU-only PyTorch in CI (but keep PyPI for everything else) - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -826,7 +822,6 @@ jobs: HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Prefer CPU-only PyTorch in CI (but keep PyPI for everything else) - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -931,7 +926,6 @@ jobs: HINDSIGHT_API_URL: http://localhost:8888 HINDSIGHT_EMBED_PACKAGE_PATH: ${{ github.workspace }}/hindsight-embed GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -1038,7 +1032,6 @@ jobs: HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -1282,7 +1275,6 @@ jobs: HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite # Prefer CPU-only PyTorch in CI - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -1340,7 +1332,6 @@ jobs: HINDSIGHT_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json HINDSIGHT_LLM_MODEL: google/gemini-2.5-flash-lite # Prefer CPU-only PyTorch in CI - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -1396,7 +1387,6 @@ jobs: HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite HINDSIGHT_API_URL: http://localhost:8888 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -1530,7 +1520,6 @@ jobs: HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu steps: - uses: actions/checkout@v6 @@ -1603,9 +1592,6 @@ jobs: verify-generated-files: runs-on: ubuntu-latest - env: - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu - steps: - uses: actions/checkout@v6 @@ -1675,9 +1661,6 @@ jobs: check-openapi-compatibility: runs-on: ubuntu-latest - env: - UV_INDEX: pytorch=https://download.pytorch.org/whl/cpu - steps: - uses: actions/checkout@v6 with: diff --git a/hindsight-api-slim/hindsight_api/alembic/versions/a3b4c5d6e7f8_add_consolidation_failed_at_to_memory_units.py b/hindsight-api-slim/hindsight_api/alembic/versions/a3b4c5d6e7f8_add_consolidation_failed_at_to_memory_units.py new file mode 100644 index 000000000..06e5f0889 --- /dev/null +++ b/hindsight-api-slim/hindsight_api/alembic/versions/a3b4c5d6e7f8_add_consolidation_failed_at_to_memory_units.py @@ -0,0 +1,52 @@ +"""Add consolidation_failed_at column to memory_units for tracking persistent LLM failures. + +When all LLM retries are exhausted on a single-memory batch, the memory is marked +with consolidation_failed_at instead of consolidated_at, so it is not silently lost +and can be retried later via the API. + +Revision ID: a3b4c5d6e7f8 +Revises: g7h8i9j0k1l2 +Create Date: 2026-03-17 +""" + +from collections.abc import Sequence + +from alembic import context, op + +revision: str = "a3b4c5d6e7f8" +down_revision: str | Sequence[str] | None = "g7h8i9j0k1l2" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def _get_schema_prefix() -> str: + """Get schema prefix for table names (required for multi-tenant support).""" + schema = context.config.get_main_option("target_schema") + return f'"{schema}".' if schema else "" + + +def upgrade() -> None: + schema = _get_schema_prefix() + + op.execute( + f""" + ALTER TABLE {schema}memory_units + ADD COLUMN IF NOT EXISTS consolidation_failed_at TIMESTAMPTZ DEFAULT NULL + """ + ) + + # Index to efficiently query memories that failed consolidation for a given bank + op.execute( + f""" + CREATE INDEX IF NOT EXISTS idx_memory_units_consolidation_failed + ON {schema}memory_units (bank_id, consolidation_failed_at) + WHERE consolidation_failed_at IS NOT NULL AND fact_type IN ('experience', 'world') + """ + ) + + +def downgrade() -> None: + schema = _get_schema_prefix() + + op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_consolidation_failed") + op.execute(f"ALTER TABLE {schema}memory_units DROP COLUMN IF EXISTS consolidation_failed_at") diff --git a/hindsight-api-slim/hindsight_api/api/http.py b/hindsight-api-slim/hindsight_api/api/http.py index 5d5206bae..a32dcad25 100644 --- a/hindsight-api-slim/hindsight_api/api/http.py +++ b/hindsight-api-slim/hindsight_api/api/http.py @@ -10,7 +10,7 @@ import logging import uuid from contextlib import asynccontextmanager -from datetime import datetime +from datetime import datetime, timezone from typing import Any, Literal from fastapi import Depends, FastAPI, File, Form, Header, HTTPException, Query, UploadFile @@ -1328,6 +1328,14 @@ class ClearMemoryObservationsResponse(BaseModel): deleted_count: int +class RecoverConsolidationResponse(BaseModel): + """Response model for recovering failed consolidation.""" + + model_config = ConfigDict(json_schema_extra={"example": {"retried_count": 42}}) + + retried_count: int + + class BankStatsResponse(BaseModel): """Response model for bank statistics endpoint.""" @@ -3902,6 +3910,34 @@ async def api_clear_observations(bank_id: str, request_context: RequestContext = logger.error(f"Error in DELETE /v1/default/banks/{bank_id}/observations: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) + @app.post( + "/v1/default/banks/{bank_id}/consolidation/recover", + response_model=RecoverConsolidationResponse, + summary="Recover failed consolidation", + description=( + "Reset all memories that were permanently marked as failed during consolidation " + "(after exhausting all LLM retries and adaptive batch splitting) so they are " + "picked up again on the next consolidation run. Does not delete any observations." + ), + operation_id="recover_consolidation", + tags=["Banks"], + ) + async def api_recover_consolidation(bank_id: str, request_context: RequestContext = Depends(get_request_context)): + """Reset consolidation-failed memories for recovery.""" + try: + result = await app.state.memory.retry_failed_consolidation(bank_id, request_context=request_context) + return RecoverConsolidationResponse(retried_count=result["retried_count"]) + except OperationValidationError as e: + raise HTTPException(status_code=e.status_code, detail=e.reason) + except (AuthenticationError, HTTPException): + raise + except Exception as e: + import traceback + + error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" + logger.error(f"Error in POST /v1/default/banks/{bank_id}/consolidation/recover: {error_detail}") + raise HTTPException(status_code=500, detail=str(e)) + @app.delete( "/v1/default/banks/{bank_id}/memories/{memory_id}/observations", response_model=ClearMemoryObservationsResponse, @@ -4135,7 +4171,7 @@ async def api_create_webhook( await bank_utils.get_bank_profile(pool, bank_id) webhook_id = uuid.uuid4() - now = datetime.utcnow().isoformat() + "Z" + now = datetime.now(timezone.utc).isoformat() row = await pool.fetchrow( f""" INSERT INTO {fq_table("webhooks")} diff --git a/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py b/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py index 578172d2b..50bfe0943 100644 --- a/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py +++ b/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py @@ -80,6 +80,7 @@ class _BatchLLMResult: deletes: list[_DeleteAction] = field(default_factory=list) obs_count: int = 0 prompt_chars: int = 0 + failed: bool = False @dataclass @@ -219,6 +220,7 @@ async def run_consolidation_job( FROM {fq_table("memory_units")} WHERE bank_id = $1 AND consolidated_at IS NULL + AND consolidation_failed_at IS NULL AND fact_type IN ('experience', 'world') """, bank_id, @@ -240,6 +242,7 @@ async def run_consolidation_job( "observations_deleted": 0, "actions_executed": 0, "skipped": 0, + "memories_failed": 0, } # Track all unique tags from consolidated memories for mental model refresh filtering @@ -257,6 +260,7 @@ async def run_consolidation_job( FROM {fq_table("memory_units")} WHERE bank_id = $1 AND consolidated_at IS NULL + AND consolidation_failed_at IS NULL AND fact_type IN ('experience', 'world') ORDER BY created_at ASC LIMIT $2 @@ -298,94 +302,141 @@ async def run_consolidation_job( if memory_tags: consolidated_tags.update(memory_tags) - async with pool.acquire() as conn: - # Determine observation_scopes for this batch. All memories in a batch share - # the same tags (enforced by tag_groups), so we only check the first memory. - # asyncpg returns JSONB columns as raw JSON strings, so parse if needed. - _obs_raw = llm_batch[0].get("observation_scopes") if llm_batch else None - _obs_parsed = json.loads(_obs_raw) if isinstance(_obs_raw, str) else _obs_raw - - # Resolve the scope spec into a concrete list[list[str]] (or None for combined). - if _obs_parsed == "per_tag": - _memory_tags = llm_batch[0].get("tags") or [] - obs_tags_list = [[tag] for tag in _memory_tags] if _memory_tags else None - elif _obs_parsed == "all_combinations": - _memory_tags = llm_batch[0].get("tags") or [] - obs_tags_list = ( - [ - list(combo) - for r in range(1, len(_memory_tags) + 1) - for combo in combinations(_memory_tags, r) - ] - if _memory_tags - else None - ) - elif _obs_parsed == "combined" or _obs_parsed is None: - obs_tags_list = None # single combined pass (default behaviour) - else: - # explicit list[list[str]] - obs_tags_list = _obs_parsed - - batch_deleted: int = 0 - if obs_tags_list: - # Multi-pass: run one observation consolidation pass per tag set - results = [] - for obs_tags in obs_tags_list: - pass_results, pass_deleted = await _process_memory_batch( + # Process llm_batch with adaptive splitting: on LLM failure, halve the sub-batch + # and retry, down to batch_size=1. Only if a single-memory batch still fails is + # the memory marked with consolidation_failed_at and excluded from future runs + # until explicitly retried via the API. + all_results: list[dict[str, Any]] = [] + all_deleted = 0 + succeeded_ids: list[Any] = [] + failed_ids: list[Any] = [] + + pending: list[list[dict[str, Any]]] = [llm_batch] + while pending: + sub_batch = pending.pop(0) + + async with pool.acquire() as conn: + # Determine observation_scopes for this sub-batch. All memories share + # the same tags (enforced by tag_groups), so we only check the first memory. + # asyncpg returns JSONB columns as raw JSON strings, so parse if needed. + _obs_raw = sub_batch[0].get("observation_scopes") if sub_batch else None + _obs_parsed = json.loads(_obs_raw) if isinstance(_obs_raw, str) else _obs_raw + + # Resolve the scope spec into a concrete list[list[str]] (or None for combined). + if _obs_parsed == "per_tag": + _memory_tags = sub_batch[0].get("tags") or [] + obs_tags_list = [[tag] for tag in _memory_tags] if _memory_tags else None + elif _obs_parsed == "all_combinations": + _memory_tags = sub_batch[0].get("tags") or [] + obs_tags_list = ( + [ + list(combo) + for r in range(1, len(_memory_tags) + 1) + for combo in combinations(_memory_tags, r) + ] + if _memory_tags + else None + ) + elif _obs_parsed == "combined" or _obs_parsed is None: + obs_tags_list = None # single combined pass (default behaviour) + else: + # explicit list[list[str]] + obs_tags_list = _obs_parsed + + sub_deleted: int = 0 + sub_llm_failed = False + if obs_tags_list: + # Multi-pass: run one observation consolidation pass per tag set + sub_results: list[dict[str, Any]] = [] + for obs_tags in obs_tags_list: + pass_results, pass_deleted, pass_failed = await _process_memory_batch( + conn=conn, + memory_engine=memory_engine, + llm_config=llm_config, + bank_id=bank_id, + memories=sub_batch, + request_context=request_context, + perf=perf, + config=config, + obs_tags_override=obs_tags, + ) + sub_deleted += pass_deleted + sub_llm_failed = sub_llm_failed or pass_failed + # Merge results: prefer non-skipped actions + if not sub_results: + sub_results = pass_results + else: + for i, (existing, new) in enumerate(zip(sub_results, pass_results)): + if existing.get("action") == "skipped" and new.get("action") != "skipped": + sub_results[i] = new + elif existing.get("action") != "skipped" and new.get("action") != "skipped": + # Both did something — combine into "multiple" + existing_created = existing.get( + "created", 1 if existing.get("action") == "created" else 0 + ) + existing_updated = existing.get( + "updated", 1 if existing.get("action") == "updated" else 0 + ) + new_created = new.get("created", 1 if new.get("action") == "created" else 0) + new_updated = new.get("updated", 1 if new.get("action") == "updated" else 0) + total = existing_created + existing_updated + new_created + new_updated + sub_results[i] = { + "action": "multiple", + "created": existing_created + new_created, + "updated": existing_updated + new_updated, + "merged": 0, + "total_actions": total, + } + else: + # Normal single pass using the memory's own tags + sub_results, sub_deleted, sub_llm_failed = await _process_memory_batch( conn=conn, memory_engine=memory_engine, llm_config=llm_config, bank_id=bank_id, - memories=llm_batch, + memories=sub_batch, request_context=request_context, perf=perf, config=config, - obs_tags_override=obs_tags, ) - batch_deleted += pass_deleted - # Merge results: prefer non-skipped actions - if not results: - results = pass_results - else: - for i, (existing, new) in enumerate(zip(results, pass_results)): - if existing.get("action") == "skipped" and new.get("action") != "skipped": - results[i] = new - elif existing.get("action") != "skipped" and new.get("action") != "skipped": - # Both did something — combine into "multiple" - existing_created = existing.get( - "created", 1 if existing.get("action") == "created" else 0 - ) - existing_updated = existing.get( - "updated", 1 if existing.get("action") == "updated" else 0 - ) - new_created = new.get("created", 1 if new.get("action") == "created" else 0) - new_updated = new.get("updated", 1 if new.get("action") == "updated" else 0) - total = existing_created + existing_updated + new_created + new_updated - results[i] = { - "action": "multiple", - "created": existing_created + new_created, - "updated": existing_updated + new_updated, - "merged": 0, - "total_actions": total, - } + + all_deleted += sub_deleted + + if sub_llm_failed and len(sub_batch) > 1: + # Split and retry with smaller batches + mid = len(sub_batch) // 2 + logger.warning( + f"[CONSOLIDATION] bank={bank_id} LLM failed for sub-batch of {len(sub_batch)}," + f" splitting into {mid}/{len(sub_batch) - mid}" + ) + pending[0:0] = [sub_batch[:mid], sub_batch[mid:]] + elif sub_llm_failed: + # batch_size=1 and still failing — mark as permanently failed for now + failed_ids.append(sub_batch[0]["id"]) + all_results.append({"action": "failed"}) + logger.warning( + f"[CONSOLIDATION] bank={bank_id} LLM failed for single memory" + f" {sub_batch[0]['id']}, marking consolidation_failed_at" + ) else: - # Normal single pass using the memory's own tags - results, batch_deleted = await _process_memory_batch( - conn=conn, - memory_engine=memory_engine, - llm_config=llm_config, - bank_id=bank_id, - memories=llm_batch, - request_context=request_context, - perf=perf, - config=config, + succeeded_ids.extend(m["id"] for m in sub_batch) + all_results.extend(sub_results) + + # Commit consolidated_at / consolidation_failed_at in a single DB round-trip + async with pool.acquire() as conn: + if succeeded_ids: + await conn.executemany( + f"UPDATE {fq_table('memory_units')} SET consolidated_at = NOW() WHERE id = $1", + [(mem_id,) for mem_id in succeeded_ids], + ) + if failed_ids: + await conn.executemany( + f"UPDATE {fq_table('memory_units')} SET consolidation_failed_at = NOW() WHERE id = $1", + [(mem_id,) for mem_id in failed_ids], ) - stats["observations_deleted"] += batch_deleted - await conn.executemany( - f"UPDATE {fq_table('memory_units')} SET consolidated_at = NOW() WHERE id = $1", - [(m["id"],) for m in llm_batch], - ) + stats["observations_deleted"] += all_deleted + results = all_results # Checkpoint: abort if the operation (and thus the bank) was deleted mid-run. if operation_id and not await memory_engine._check_op_alive(operation_id): @@ -413,6 +464,8 @@ async def run_consolidation_job( stats["actions_executed"] += result.get("total_actions", 0) elif action == "skipped": stats["skipped"] += 1 + elif action == "failed": + stats["memories_failed"] += 1 # Per-LLM-batch log llm_batch_time = time.time() - llm_batch_start @@ -425,6 +478,7 @@ async def run_consolidation_job( batch_created = stats["observations_created"] - snap_stats["observations_created"] batch_updated = stats["observations_updated"] - snap_stats["observations_updated"] batch_skipped = stats["skipped"] - snap_stats["skipped"] + batch_failed = stats["memories_failed"] - snap_stats["memories_failed"] llm_calls_made = perf.llm_calls - snap_llm_calls logger.info( f"[CONSOLIDATION] bank={bank_id} llm_batch #{llm_batch_num}" @@ -432,7 +486,8 @@ async def run_consolidation_job( f" | {stats['memories_processed']}/{total_count} processed" f" | {', '.join(timing_parts)}" f" | created={batch_created} updated={batch_updated} skipped={batch_skipped}" - f" | input_tokens=~{input_tokens}" + + (f" failed={batch_failed}" if batch_failed else "") + + f" | input_tokens=~{input_tokens}" f" | avg={llm_batch_time / len(llm_batch):.3f}s/memory" ) @@ -584,7 +639,7 @@ async def _process_memory_batch( perf: ConsolidationPerfLog | None = None, config: Any = None, obs_tags_override: list[str] | None = None, -) -> tuple[list[dict[str, Any]], int]: +) -> tuple[list[dict[str, Any]], int, bool]: """ Process a batch of memories in a single LLM call. @@ -747,7 +802,7 @@ async def _process_memory_batch( else: results.append({"action": "skipped", "reason": "no_durable_knowledge"}) - return results, deleted_count + return results, deleted_count, llm_result.failed def _min_date(dates: "Any") -> "datetime | None": @@ -1081,7 +1136,7 @@ def _fact_line(m: dict[str, Any]) -> str: logger.error( f"[CONSOLIDATION] LLM batch call failed after {max_attempts} attempts, skipping batch. Last error: {last_exc}" ) - return _BatchLLMResult(obs_count=len(union_observations), prompt_chars=len(prompt)) + return _BatchLLMResult(obs_count=len(union_observations), prompt_chars=len(prompt), failed=True) async def _create_observation_directly( diff --git a/hindsight-api-slim/hindsight_api/engine/llm_wrapper.py b/hindsight-api-slim/hindsight_api/engine/llm_wrapper.py index df63d5e20..4fd4c2e1b 100644 --- a/hindsight-api-slim/hindsight_api/engine/llm_wrapper.py +++ b/hindsight-api-slim/hindsight_api/engine/llm_wrapper.py @@ -633,7 +633,7 @@ def _verify_claude_code_available(self) -> None: # Reduce Claude Agent SDK logging verbosity import logging as sdk_logging - from claude_agent_sdk import query # noqa: F401 + from claude_agent_sdk import query # noqa: F401 # type: ignore[unresolved-import] sdk_logging.getLogger("claude_agent_sdk").setLevel(sdk_logging.WARNING) sdk_logging.getLogger("claude_agent_sdk._internal").setLevel(sdk_logging.WARNING) diff --git a/hindsight-api-slim/hindsight_api/engine/memory_engine.py b/hindsight-api-slim/hindsight_api/engine/memory_engine.py index fb0816688..eeb71e5a1 100644 --- a/hindsight-api-slim/hindsight_api/engine/memory_engine.py +++ b/hindsight-api-slim/hindsight_api/engine/memory_engine.py @@ -3878,6 +3878,58 @@ async def clear_observations( return {"deleted_count": count or 0} + async def retry_failed_consolidation( + self, + bank_id: str, + *, + request_context: "RequestContext", + ) -> dict[str, int]: + """ + Reset memories that previously failed consolidation so they are retried on the next + consolidation run. + + Clears consolidation_failed_at (and consolidated_at) for all memories in the bank + that were marked as permanently failed after exhausting all LLM retries and adaptive + batch splitting. Does not delete any observations. + + Args: + bank_id: Bank ID + request_context: Request context for authentication. + + Returns: + Dictionary with count of memories queued for retry. + """ + await self._authenticate_tenant(request_context) + if self._operation_validator: + from hindsight_api.extensions import BankWriteContext + + ctx = BankWriteContext( + bank_id=bank_id, operation="retry_failed_consolidation", request_context=request_context + ) + await self._validate_operation(self._operation_validator.validate_bank_write(ctx)) + pool = await self._get_pool() + async with acquire_with_retry(pool) as conn: + count = await conn.fetchval( + f""" + SELECT COUNT(*) FROM {fq_table("memory_units")} + WHERE bank_id = $1 + AND consolidation_failed_at IS NOT NULL + AND fact_type IN ('experience', 'world') + """, + bank_id, + ) + await conn.execute( + f""" + UPDATE {fq_table("memory_units")} + SET consolidation_failed_at = NULL, consolidated_at = NULL + WHERE bank_id = $1 + AND consolidation_failed_at IS NOT NULL + AND fact_type IN ('experience', 'world') + """, + bank_id, + ) + return {"retried_count": count or 0} + async def clear_observations_for_memory( self, bank_id: str, diff --git a/hindsight-api-slim/hindsight_api/engine/providers/claude_code_llm.py b/hindsight-api-slim/hindsight_api/engine/providers/claude_code_llm.py index 2eaa8f2b2..c91d2947e 100644 --- a/hindsight-api-slim/hindsight_api/engine/providers/claude_code_llm.py +++ b/hindsight-api-slim/hindsight_api/engine/providers/claude_code_llm.py @@ -68,7 +68,7 @@ def _verify_claude_code_available(self) -> None: # Reduce Claude Agent SDK logging verbosity import logging as sdk_logging - from claude_agent_sdk import query # noqa: F401 + from claude_agent_sdk import query # noqa: F401 # type: ignore[unresolved-import] sdk_logging.getLogger("claude_agent_sdk").setLevel(sdk_logging.WARNING) sdk_logging.getLogger("claude_agent_sdk._internal").setLevel(sdk_logging.WARNING) @@ -141,7 +141,12 @@ async def call( OutputTooLongError: If output exceeds token limits (not supported by Claude Agent SDK). Exception: Re-raises API errors after retries exhausted. """ - from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query + from claude_agent_sdk import ( # type: ignore[unresolved-import] + AssistantMessage, + ClaudeAgentOptions, + TextBlock, + query, + ) start_time = time.time() @@ -331,7 +336,7 @@ async def call_with_tools( Returns: LLMToolCallResult with content and/or tool_calls. """ - from claude_agent_sdk import ( + from claude_agent_sdk import ( # type: ignore[unresolved-import] AssistantMessage, ClaudeAgentOptions, ClaudeSDKClient, diff --git a/hindsight-api-slim/pyproject.toml b/hindsight-api-slim/pyproject.toml index 8a6f3e081..cb7b988c9 100644 --- a/hindsight-api-slim/pyproject.toml +++ b/hindsight-api-slim/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ "filelock>=3.20.1", # TOCTOU race condition fix "authlib>=1.6.6", # Account takeover vulnerability fix "aiohttp>=3.13.3", # Multiple DoS vulnerabilities - "claude-agent-sdk>=0.1.27", + "claude-agent-sdk>=0.1.27; sys_platform == 'darwin'", ] [project.optional-dependencies] @@ -168,9 +168,16 @@ quote-style = "double" indent-style = "space" [tool.uv] -# Allow uv to search all configured indexes for packages, not just the first one -# This prevents dependency resolution failures when using pytorch index + PyPI -index-strategy = "unsafe-best-match" +# Use explicit index for PyTorch to prevent the pytorch index from serving +# non-pytorch packages (e.g. markupsafe) with incompatible wheels +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[tool.uv.sources] +# Route torch to the CPU-only PyTorch index; everything else uses PyPI +torch = { index = "pytorch-cpu" } [tool.ty] # Type checking configuration diff --git a/hindsight-api-slim/tests/test_consolidation_failure_recovery.py b/hindsight-api-slim/tests/test_consolidation_failure_recovery.py new file mode 100644 index 000000000..c737ddd4f --- /dev/null +++ b/hindsight-api-slim/tests/test_consolidation_failure_recovery.py @@ -0,0 +1,476 @@ +"""Tests for consolidation failure handling: adaptive batch splitting, consolidation_failed_at, +and the recovery API. + +These tests use a mock LLM to simulate LLM failures deterministically, without making real +API calls. All tests insert memories directly into the database to bypass retain's LLM calls +and focus exclusively on the consolidation code paths. +""" + +import uuid +from unittest.mock import MagicMock + +import pytest +import pytest_asyncio + +from hindsight_api.engine.consolidation.consolidator import run_consolidation_job +from hindsight_api.engine.memory_engine import MemoryEngine +from hindsight_api.engine.providers.mock_llm import MockLLM +from hindsight_api.engine.task_backend import SyncTaskBackend + + +@pytest_asyncio.fixture(scope="function") +async def memory_no_llm_verify(pg0_db_url, embeddings, cross_encoder, query_analyzer): + """MemoryEngine with mock LLM. + + Migrations are already applied by the session-scoped pg0_db_url fixture, so + run_migrations=False avoids advisory-lock serialization overhead per test. + """ + mem = MemoryEngine( + db_url=pg0_db_url, + memory_llm_provider="mock", + memory_llm_api_key="", + memory_llm_model="mock", + embeddings=embeddings, + cross_encoder=cross_encoder, + query_analyzer=query_analyzer, + pool_min_size=1, + pool_max_size=5, + run_migrations=False, + task_backend=SyncTaskBackend(), + skip_llm_verification=True, + ) + await mem.initialize() + yield mem + try: + if mem._pool and not mem._pool._closing: + await mem.close() + except Exception: + pass + + +@pytest.fixture(autouse=True) +def enable_observations(): + """Enable observations for all tests in this module.""" + from hindsight_api.config import _get_raw_config + + config = _get_raw_config() + original = config.enable_observations + config.enable_observations = True + yield + config.enable_observations = original + + +def _make_failing_mock_llm(*, fail_first_n: int = 999) -> MockLLM: + """Return a MockLLM that raises ValueError for the first `fail_first_n` consolidation calls.""" + mock_llm = MockLLM(provider="mock", api_key="", base_url="", model="mock-model") + call_count = 0 + + def callback(messages, scope): + nonlocal call_count + if scope == "consolidation": + call_count += 1 + if call_count <= fail_first_n: + raise ValueError(f"Simulated LLM failure (call {call_count})") + # Return empty response — no creates/updates/deletes + from hindsight_api.engine.consolidation.consolidator import _ConsolidationBatchResponse + + return _ConsolidationBatchResponse() + + mock_llm.set_response_callback(callback) + return mock_llm + + +def _make_always_success_mock_llm() -> MockLLM: + """Return a MockLLM that always succeeds with an empty consolidation response.""" + mock_llm = MockLLM(provider="mock", api_key="", base_url="", model="mock-model") + + def callback(messages, scope): + from hindsight_api.engine.consolidation.consolidator import _ConsolidationBatchResponse + + return _ConsolidationBatchResponse() + + mock_llm.set_response_callback(callback) + return mock_llm + + +def _inject_mock_llm(memory: MemoryEngine, mock_llm: MockLLM) -> None: + """Replace memory._consolidation_llm_config with a wrapper that returns mock_llm from with_config.""" + wrapper = MagicMock() + wrapper.with_config.return_value = mock_llm + memory._consolidation_llm_config = wrapper + + +async def _insert_memories(conn, bank_id: str, texts: list[str]) -> list[uuid.UUID]: + """Insert experience memories directly, bypassing LLM-based retain.""" + ids = [] + for text in texts: + mem_id = uuid.uuid4() + await conn.execute( + """ + INSERT INTO memory_units (id, bank_id, text, fact_type, created_at) + VALUES ($1, $2, $3, 'experience', now()) + """, + mem_id, + bank_id, + text, + ) + ids.append(mem_id) + return ids + + +class TestAdaptiveBatchSplitting: + """Verify that a failing batch is halved and retried until batch_size=1 succeeds.""" + + @pytest.mark.asyncio + async def test_splitting_recovers_all_memories(self, memory_no_llm_verify: MemoryEngine, request_context): + """When a batch of 2 fails, both are retried individually and succeed.""" + bank_id = f"test-split-recovery-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + mem_ids = await _insert_memories( + conn, + bank_id, + [ + "Alice runs marathons every spring.", + "Alice trained for six months for her last race.", + ], + ) + + # Exhaust all 3 retries for batch=2 (calls 1-3 fail), then each batch=1 succeeds (calls 4-5) + mock_llm = _make_failing_mock_llm(fail_first_n=3) + _inject_mock_llm(memory_no_llm_verify, mock_llm) + + result = await run_consolidation_job( + memory_engine=memory_no_llm_verify, + bank_id=bank_id, + request_context=request_context, + ) + + assert result["status"] == "completed" + assert result["memories_processed"] == 2 + assert result["memories_failed"] == 0 + + # Both memories must have consolidated_at set and consolidation_failed_at NULL + async with memory_no_llm_verify._pool.acquire() as conn: + rows = await conn.fetch( + """ + SELECT id, consolidated_at, consolidation_failed_at + FROM memory_units + WHERE bank_id = $1 AND fact_type = 'experience' + """, + bank_id, + ) + assert len(rows) == 2 + for row in rows: + assert row["consolidated_at"] is not None, f"Memory {row['id']} should have consolidated_at set" + assert row["consolidation_failed_at"] is None, ( + f"Memory {row['id']} should NOT have consolidation_failed_at set" + ) + + # LLM called 5 times: 3 retries failed (batch=2) + 1 succeeded (batch=1) + 1 succeeded (batch=1) + consolidation_calls = [c for c in mock_llm.get_mock_calls() if c["scope"] == "consolidation"] + assert len(consolidation_calls) == 5 + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + @pytest.mark.asyncio + async def test_splitting_with_larger_batch(self, memory_no_llm_verify: MemoryEngine, request_context): + """A batch of 4 that always fails at size>1 resolves to 4 individual calls.""" + bank_id = f"test-split-large-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + await _insert_memories( + conn, + bank_id, + [ + "Bob plays chess competitively.", + "Bob won a regional chess tournament.", + "Bob practices tactics every morning.", + "Bob coaches youth chess on weekends.", + ], + ) + + # Exhaust all 3 retries for batch=4 (calls 1-3 fail), then both batch=2 halves succeed + # (calls 4-5). This verifies that halving once is sufficient when batch=2 works. + mock_llm = _make_failing_mock_llm(fail_first_n=3) + _inject_mock_llm(memory_no_llm_verify, mock_llm) + + result = await run_consolidation_job( + memory_engine=memory_no_llm_verify, + bank_id=bank_id, + request_context=request_context, + ) + + assert result["memories_processed"] == 4 + assert result["memories_failed"] == 0 + + async with memory_no_llm_verify._pool.acquire() as conn: + rows = await conn.fetch( + "SELECT consolidated_at, consolidation_failed_at FROM memory_units " + "WHERE bank_id = $1 AND fact_type = 'experience'", + bank_id, + ) + assert all(r["consolidated_at"] is not None for r in rows) + assert all(r["consolidation_failed_at"] is None for r in rows) + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + +class TestConsolidationFailedAt: + """Verify that consolidation_failed_at is set — and consolidated_at is NOT — when all retries fail.""" + + @pytest.mark.asyncio + async def test_single_memory_permanent_failure(self, memory_no_llm_verify: MemoryEngine, request_context): + """A single memory that exhausts all LLM retries gets consolidation_failed_at, not consolidated_at.""" + bank_id = f"test-perm-fail-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + (mem_id,) = await _insert_memories(conn, bank_id, ["Carol enjoys painting watercolors."]) + + # Always fail + mock_llm = _make_failing_mock_llm(fail_first_n=999) + _inject_mock_llm(memory_no_llm_verify, mock_llm) + + result = await run_consolidation_job( + memory_engine=memory_no_llm_verify, + bank_id=bank_id, + request_context=request_context, + ) + + assert result["memories_failed"] == 1 + assert result["memories_processed"] == 1 + + async with memory_no_llm_verify._pool.acquire() as conn: + row = await conn.fetchrow( + "SELECT consolidated_at, consolidation_failed_at FROM memory_units WHERE id = $1", + mem_id, + ) + + assert row["consolidated_at"] is None, "consolidated_at must NOT be set for a permanently failed memory" + assert row["consolidation_failed_at"] is not None, "consolidation_failed_at must be set" + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + @pytest.mark.asyncio + async def test_failed_memory_excluded_from_next_run(self, memory_no_llm_verify: MemoryEngine, request_context): + """A memory marked consolidation_failed_at is not re-processed on the next consolidation run.""" + bank_id = f"test-excluded-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + (mem_id,) = await _insert_memories(conn, bank_id, ["Dave collects vinyl records."]) + # Manually stamp consolidation_failed_at to simulate a prior failed run + await conn.execute( + "UPDATE memory_units SET consolidation_failed_at = NOW() WHERE id = $1", + mem_id, + ) + + # Even with a healthy LLM, the memory should be skipped + mock_llm = _make_always_success_mock_llm() + _inject_mock_llm(memory_no_llm_verify, mock_llm) + + result = await run_consolidation_job( + memory_engine=memory_no_llm_verify, + bank_id=bank_id, + request_context=request_context, + ) + + # No unconsolidated memories to pick up (consolidation_failed_at ≠ NULL, consolidated_at = NULL + # but the SELECT filters on consolidated_at IS NULL AND fact_type IN ('experience','world')) + assert result["status"] in ("no_new_memories", "completed") + if result["status"] == "completed": + assert result["memories_processed"] == 0 + + # Memory still has consolidation_failed_at set and consolidated_at NULL + async with memory_no_llm_verify._pool.acquire() as conn: + row = await conn.fetchrow( + "SELECT consolidated_at, consolidation_failed_at FROM memory_units WHERE id = $1", + mem_id, + ) + assert row["consolidated_at"] is None + assert row["consolidation_failed_at"] is not None + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + @pytest.mark.asyncio + async def test_partial_batch_failure(self, memory_no_llm_verify: MemoryEngine, request_context): + """In a batch of 2, if only the first individual retry fails, the second still succeeds.""" + bank_id = f"test-partial-fail-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + mem_ids = await _insert_memories( + conn, + bank_id, + [ + "Eve speaks three languages fluently.", + "Eve learned Japanese in two years.", + ], + ) + + # Exhaust 3 retries for batch=2 (calls 1-3), exhaust 3 retries for first batch=1 (calls 4-6), + # second batch=1 succeeds (call 7) + mock_llm = _make_failing_mock_llm(fail_first_n=6) + _inject_mock_llm(memory_no_llm_verify, mock_llm) + + result = await run_consolidation_job( + memory_engine=memory_no_llm_verify, + bank_id=bank_id, + request_context=request_context, + ) + + assert result["memories_processed"] == 2 + assert result["memories_failed"] == 1 + + async with memory_no_llm_verify._pool.acquire() as conn: + rows = { + str(r["id"]): r + for r in await conn.fetch( + "SELECT id, consolidated_at, consolidation_failed_at FROM memory_units " + "WHERE bank_id = $1 AND fact_type = 'experience'", + bank_id, + ) + } + + # One should have failed, one should have succeeded + failed = [r for r in rows.values() if r["consolidation_failed_at"] is not None] + succeeded = [r for r in rows.values() if r["consolidated_at"] is not None] + assert len(failed) == 1 + assert len(succeeded) == 1 + # They must be different memories + assert str(failed[0]["id"]) != str(succeeded[0]["id"]) + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + +class TestRecoverConsolidation: + """Verify the retry_failed_consolidation() method and the /consolidation/recover endpoint.""" + + @pytest.mark.asyncio + async def test_recover_resets_failed_memories(self, memory_no_llm_verify: MemoryEngine, request_context): + """retry_failed_consolidation resets consolidation_failed_at and consolidated_at.""" + bank_id = f"test-recover-reset-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + ids = await _insert_memories( + conn, + bank_id, + [ + "Frank is a competitive cyclist.", + "Frank completed the Tour de France route.", + ], + ) + # Mark both as failed + for mem_id in ids: + await conn.execute( + "UPDATE memory_units SET consolidation_failed_at = NOW() WHERE id = $1", + mem_id, + ) + + result = await memory_no_llm_verify.retry_failed_consolidation( + bank_id, request_context=request_context + ) + + assert result["retried_count"] == 2 + + async with memory_no_llm_verify._pool.acquire() as conn: + rows = await conn.fetch( + "SELECT consolidated_at, consolidation_failed_at FROM memory_units " + "WHERE bank_id = $1 AND fact_type = 'experience'", + bank_id, + ) + assert all(r["consolidation_failed_at"] is None for r in rows), "consolidation_failed_at must be cleared" + assert all(r["consolidated_at"] is None for r in rows), "consolidated_at must also be cleared" + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + @pytest.mark.asyncio + async def test_recover_returns_zero_when_none_failed(self, memory_no_llm_verify: MemoryEngine, request_context): + """retry_failed_consolidation returns 0 when no memories have failed.""" + bank_id = f"test-recover-zero-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + result = await memory_no_llm_verify.retry_failed_consolidation( + bank_id, request_context=request_context + ) + + assert result["retried_count"] == 0 + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + @pytest.mark.asyncio + async def test_recover_then_consolidate_succeeds(self, memory_no_llm_verify: MemoryEngine, request_context): + """After recovery, the memory is picked up by the next consolidation run.""" + bank_id = f"test-recover-consolidate-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + (mem_id,) = await _insert_memories(conn, bank_id, ["Grace is an expert rock climber."]) + await conn.execute( + "UPDATE memory_units SET consolidation_failed_at = NOW() WHERE id = $1", mem_id + ) + + # Recover + recover_result = await memory_no_llm_verify.retry_failed_consolidation( + bank_id, request_context=request_context + ) + assert recover_result["retried_count"] == 1 + + # Now consolidate with a healthy LLM + mock_llm = _make_always_success_mock_llm() + _inject_mock_llm(memory_no_llm_verify, mock_llm) + + run_result = await run_consolidation_job( + memory_engine=memory_no_llm_verify, + bank_id=bank_id, + request_context=request_context, + ) + + assert run_result["memories_processed"] == 1 + assert run_result["memories_failed"] == 0 + + async with memory_no_llm_verify._pool.acquire() as conn: + row = await conn.fetchrow( + "SELECT consolidated_at, consolidation_failed_at FROM memory_units WHERE id = $1", + mem_id, + ) + assert row["consolidated_at"] is not None, "Memory should be consolidated after recovery" + assert row["consolidation_failed_at"] is None + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) + + @pytest.mark.asyncio + async def test_recover_endpoint_via_http(self, memory_no_llm_verify: MemoryEngine, request_context): + """The POST /consolidation/recover endpoint returns the correct retried_count.""" + import httpx + + from hindsight_api.api.http import create_app + + bank_id = f"test-recover-http-{uuid.uuid4().hex[:8]}" + await memory_no_llm_verify.get_bank_profile(bank_id=bank_id, request_context=request_context) + + async with memory_no_llm_verify._pool.acquire() as conn: + ids = await _insert_memories( + conn, + bank_id, + ["Henry is a professional chef.", "Henry trained at Le Cordon Bleu."], + ) + for mem_id in ids: + await conn.execute( + "UPDATE memory_units SET consolidation_failed_at = NOW() WHERE id = $1", mem_id + ) + + app = create_app(memory_no_llm_verify, initialize_memory=False) + transport = httpx.ASGITransport(app=app) + async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: + response = await client.post(f"/v1/default/banks/{bank_id}/consolidation/recover") + + assert response.status_code == 200 + body = response.json() + assert body["retried_count"] == 2 + + await memory_no_llm_verify.delete_bank(bank_id, request_context=request_context) diff --git a/hindsight-api-slim/tests/test_load_large_batch.py b/hindsight-api-slim/tests/test_load_large_batch.py index 4f499aa11..70c212d78 100644 --- a/hindsight-api-slim/tests/test_load_large_batch.py +++ b/hindsight-api-slim/tests/test_load_large_batch.py @@ -149,6 +149,16 @@ async def test_large_batch_500k_chars_20_items(self, memory_with_mock_llm, reque call_tracker = {"count": 0, "facts": 0} async def mock_llm_call(*args, **kwargs): + from hindsight_api.engine.consolidation.consolidator import _ConsolidationBatchResponse + + # Consolidation calls expect a _ConsolidationBatchResponse (not a raw dict), + # because consolidation does NOT use skip_validation=True. + if kwargs.get("scope") == "consolidation": + return_usage = kwargs.get("return_usage", False) + if return_usage: + return _ConsolidationBatchResponse(), TokenUsage(input_tokens=0, output_tokens=0) + return _ConsolidationBatchResponse() + call_tracker["count"] += 1 # Extract the content from the user message to generate proportional facts @@ -157,7 +167,7 @@ async def mock_llm_call(*args, **kwargs): mock_facts = create_mock_facts_from_content(user_msg, ratio=1.5) call_tracker["facts"] += len(mock_facts) - # Return a dict (parsed JSON) since skip_validation=True but the code expects a dict + # Return a dict (parsed JSON) — fact extraction uses skip_validation=True response_dict = {"facts": mock_facts} return_usage = kwargs.get("return_usage", False) @@ -236,6 +246,14 @@ async def test_batch_chunking_behavior(self, memory_with_mock_llm, request_conte logger.info(f"Created {num_items} items with {actual_total_chars:,} chars (should trigger chunking)") async def mock_llm_call(*args, **kwargs): + from hindsight_api.engine.consolidation.consolidator import _ConsolidationBatchResponse + + if kwargs.get("scope") == "consolidation": + return_usage = kwargs.get("return_usage", False) + if return_usage: + return _ConsolidationBatchResponse(), TokenUsage(input_tokens=0, output_tokens=0) + return _ConsolidationBatchResponse() + messages = kwargs.get("messages", args[0] if args else []) user_msg = messages[-1]["content"] if messages else "" mock_facts = create_mock_facts_from_content(user_msg, ratio=1.0) diff --git a/hindsight-clients/go/api/openapi.yaml b/hindsight-clients/go/api/openapi.yaml index 85871a740..ba097ab08 100644 --- a/hindsight-clients/go/api/openapi.yaml +++ b/hindsight-clients/go/api/openapi.yaml @@ -2106,6 +2106,46 @@ paths: summary: Clear all observations tags: - Banks + /v1/default/banks/{bank_id}/consolidation/recover: + post: + description: Reset all memories that were permanently marked as failed during + consolidation (after exhausting all LLM retries and adaptive batch splitting) + so they are picked up again on the next consolidation run. Does not delete + any observations. + operationId: recover_consolidation + parameters: + - explode: false + in: path + name: bank_id + required: true + schema: + title: Bank Id + type: string + style: simple + - explode: false + in: header + name: authorization + required: false + schema: + nullable: true + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/RecoverConsolidationResponse' + description: Successful Response + "422": + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + description: Validation Error + summary: Recover failed consolidation + tags: + - Banks /v1/default/banks/{bank_id}/memories/{memory_id}/observations: delete: description: Delete all observations derived from a specific memory and reset @@ -4449,6 +4489,17 @@ components: - id - text title: RecallResult + RecoverConsolidationResponse: + description: Response model for recovering failed consolidation. + example: + retried_count: 42 + properties: + retried_count: + title: Retried Count + type: integer + required: + - retried_count + title: RecoverConsolidationResponse ReflectBasedOn: description: "Evidence the response is based on: memories, mental models, and\ \ directives." diff --git a/hindsight-clients/go/api_banks.go b/hindsight-clients/go/api_banks.go index 83a1ad4a2..01073bf72 100644 --- a/hindsight-clients/go/api_banks.go +++ b/hindsight-clients/go/api_banks.go @@ -1023,6 +1023,128 @@ func (a *BanksAPIService) ListBanksExecute(r ApiListBanksRequest) (*BankListResp return localVarReturnValue, localVarHTTPResponse, nil } +type ApiRecoverConsolidationRequest struct { + ctx context.Context + ApiService *BanksAPIService + bankId string + authorization *string +} + +func (r ApiRecoverConsolidationRequest) Authorization(authorization string) ApiRecoverConsolidationRequest { + r.authorization = &authorization + return r +} + +func (r ApiRecoverConsolidationRequest) Execute() (*RecoverConsolidationResponse, *http.Response, error) { + return r.ApiService.RecoverConsolidationExecute(r) +} + +/* +RecoverConsolidation Recover failed consolidation + +Reset all memories that were permanently marked as failed during consolidation (after exhausting all LLM retries and adaptive batch splitting) so they are picked up again on the next consolidation run. Does not delete any observations. + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @param bankId + @return ApiRecoverConsolidationRequest +*/ +func (a *BanksAPIService) RecoverConsolidation(ctx context.Context, bankId string) ApiRecoverConsolidationRequest { + return ApiRecoverConsolidationRequest{ + ApiService: a, + ctx: ctx, + bankId: bankId, + } +} + +// Execute executes the request +// @return RecoverConsolidationResponse +func (a *BanksAPIService) RecoverConsolidationExecute(r ApiRecoverConsolidationRequest) (*RecoverConsolidationResponse, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPost + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *RecoverConsolidationResponse + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "BanksAPIService.RecoverConsolidation") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/v1/default/banks/{bank_id}/consolidation/recover" + localVarPath = strings.Replace(localVarPath, "{"+"bank_id"+"}", url.PathEscape(parameterValueToString(r.bankId, "bankId")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.authorization != nil { + parameterAddToHeaderOrQuery(localVarHeaderParams, "authorization", r.authorization, "simple", "") + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 422 { + var v HTTPValidationError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type ApiResetBankConfigRequest struct { ctx context.Context ApiService *BanksAPIService diff --git a/hindsight-clients/go/model_recover_consolidation_response.go b/hindsight-clients/go/model_recover_consolidation_response.go new file mode 100644 index 000000000..eeeb703fb --- /dev/null +++ b/hindsight-clients/go/model_recover_consolidation_response.go @@ -0,0 +1,158 @@ +/* +Hindsight HTTP API + +HTTP API for Hindsight + +API version: 0.4.18 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package hindsight + +import ( + "encoding/json" + "bytes" + "fmt" +) + +// checks if the RecoverConsolidationResponse type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &RecoverConsolidationResponse{} + +// RecoverConsolidationResponse Response model for recovering failed consolidation. +type RecoverConsolidationResponse struct { + RetriedCount int32 `json:"retried_count"` +} + +type _RecoverConsolidationResponse RecoverConsolidationResponse + +// NewRecoverConsolidationResponse instantiates a new RecoverConsolidationResponse object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewRecoverConsolidationResponse(retriedCount int32) *RecoverConsolidationResponse { + this := RecoverConsolidationResponse{} + this.RetriedCount = retriedCount + return &this +} + +// NewRecoverConsolidationResponseWithDefaults instantiates a new RecoverConsolidationResponse object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewRecoverConsolidationResponseWithDefaults() *RecoverConsolidationResponse { + this := RecoverConsolidationResponse{} + return &this +} + +// GetRetriedCount returns the RetriedCount field value +func (o *RecoverConsolidationResponse) GetRetriedCount() int32 { + if o == nil { + var ret int32 + return ret + } + + return o.RetriedCount +} + +// GetRetriedCountOk returns a tuple with the RetriedCount field value +// and a boolean to check if the value has been set. +func (o *RecoverConsolidationResponse) GetRetriedCountOk() (*int32, bool) { + if o == nil { + return nil, false + } + return &o.RetriedCount, true +} + +// SetRetriedCount sets field value +func (o *RecoverConsolidationResponse) SetRetriedCount(v int32) { + o.RetriedCount = v +} + +func (o RecoverConsolidationResponse) MarshalJSON() ([]byte, error) { + toSerialize,err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o RecoverConsolidationResponse) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["retried_count"] = o.RetriedCount + return toSerialize, nil +} + +func (o *RecoverConsolidationResponse) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "retried_count", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err; + } + + for _, requiredProperty := range(requiredProperties) { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varRecoverConsolidationResponse := _RecoverConsolidationResponse{} + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + err = decoder.Decode(&varRecoverConsolidationResponse) + + if err != nil { + return err + } + + *o = RecoverConsolidationResponse(varRecoverConsolidationResponse) + + return err +} + +type NullableRecoverConsolidationResponse struct { + value *RecoverConsolidationResponse + isSet bool +} + +func (v NullableRecoverConsolidationResponse) Get() *RecoverConsolidationResponse { + return v.value +} + +func (v *NullableRecoverConsolidationResponse) Set(val *RecoverConsolidationResponse) { + v.value = val + v.isSet = true +} + +func (v NullableRecoverConsolidationResponse) IsSet() bool { + return v.isSet +} + +func (v *NullableRecoverConsolidationResponse) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableRecoverConsolidationResponse(val *RecoverConsolidationResponse) *NullableRecoverConsolidationResponse { + return &NullableRecoverConsolidationResponse{value: val, isSet: true} +} + +func (v NullableRecoverConsolidationResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableRecoverConsolidationResponse) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + + diff --git a/hindsight-clients/python/.openapi-generator/FILES b/hindsight-clients/python/.openapi-generator/FILES index 2223ebc7a..cbd2d1bbc 100644 --- a/hindsight-clients/python/.openapi-generator/FILES +++ b/hindsight-clients/python/.openapi-generator/FILES @@ -71,6 +71,7 @@ hindsight_client_api/models/recall_request.py hindsight_client_api/models/recall_request_tag_groups_inner.py hindsight_client_api/models/recall_response.py hindsight_client_api/models/recall_result.py +hindsight_client_api/models/recover_consolidation_response.py hindsight_client_api/models/reflect_based_on.py hindsight_client_api/models/reflect_directive.py hindsight_client_api/models/reflect_fact.py diff --git a/hindsight-clients/python/hindsight_client_api/__init__.py b/hindsight-clients/python/hindsight_client_api/__init__.py index 42fb42cd9..59fa9f2bc 100644 --- a/hindsight-clients/python/hindsight_client_api/__init__.py +++ b/hindsight-clients/python/hindsight_client_api/__init__.py @@ -96,6 +96,7 @@ from hindsight_client_api.models.recall_request_tag_groups_inner import RecallRequestTagGroupsInner from hindsight_client_api.models.recall_response import RecallResponse from hindsight_client_api.models.recall_result import RecallResult +from hindsight_client_api.models.recover_consolidation_response import RecoverConsolidationResponse from hindsight_client_api.models.reflect_based_on import ReflectBasedOn from hindsight_client_api.models.reflect_directive import ReflectDirective from hindsight_client_api.models.reflect_fact import ReflectFact diff --git a/hindsight-clients/python/hindsight_client_api/api/banks_api.py b/hindsight-clients/python/hindsight_client_api/api/banks_api.py index 10271033c..dcdbf7468 100644 --- a/hindsight-clients/python/hindsight_client_api/api/banks_api.py +++ b/hindsight-clients/python/hindsight_client_api/api/banks_api.py @@ -28,6 +28,7 @@ from hindsight_client_api.models.consolidation_response import ConsolidationResponse from hindsight_client_api.models.create_bank_request import CreateBankRequest from hindsight_client_api.models.delete_response import DeleteResponse +from hindsight_client_api.models.recover_consolidation_response import RecoverConsolidationResponse from hindsight_client_api.models.update_disposition_request import UpdateDispositionRequest from hindsight_client_api.api_client import ApiClient, RequestSerialized @@ -2319,6 +2320,284 @@ def _list_banks_serialize( + @validate_call + async def recover_consolidation( + 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, + ) -> RecoverConsolidationResponse: + """Recover failed consolidation + + Reset all memories that were permanently marked as failed during consolidation (after exhausting all LLM retries and adaptive batch splitting) so they are picked up again on the next consolidation run. Does not delete any observations. + + :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._recover_consolidation_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': "RecoverConsolidationResponse", + '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 recover_consolidation_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[RecoverConsolidationResponse]: + """Recover failed consolidation + + Reset all memories that were permanently marked as failed during consolidation (after exhausting all LLM retries and adaptive batch splitting) so they are picked up again on the next consolidation run. Does not delete any observations. + + :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._recover_consolidation_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': "RecoverConsolidationResponse", + '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 recover_consolidation_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: + """Recover failed consolidation + + Reset all memories that were permanently marked as failed during consolidation (after exhausting all LLM retries and adaptive batch splitting) so they are picked up again on the next consolidation run. Does not delete any observations. + + :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._recover_consolidation_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': "RecoverConsolidationResponse", + '422': "HTTPValidationError", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _recover_consolidation_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='POST', + resource_path='/v1/default/banks/{bank_id}/consolidation/recover', + 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 reset_bank_config( self, diff --git a/hindsight-clients/python/hindsight_client_api/models/__init__.py b/hindsight-clients/python/hindsight_client_api/models/__init__.py index 3678ad404..5ee4d2202 100644 --- a/hindsight-clients/python/hindsight_client_api/models/__init__.py +++ b/hindsight-clients/python/hindsight_client_api/models/__init__.py @@ -70,6 +70,7 @@ from hindsight_client_api.models.recall_request_tag_groups_inner import RecallRequestTagGroupsInner from hindsight_client_api.models.recall_response import RecallResponse from hindsight_client_api.models.recall_result import RecallResult +from hindsight_client_api.models.recover_consolidation_response import RecoverConsolidationResponse from hindsight_client_api.models.reflect_based_on import ReflectBasedOn from hindsight_client_api.models.reflect_directive import ReflectDirective from hindsight_client_api.models.reflect_fact import ReflectFact diff --git a/hindsight-clients/python/hindsight_client_api/models/recover_consolidation_response.py b/hindsight-clients/python/hindsight_client_api/models/recover_consolidation_response.py new file mode 100644 index 000000000..4c3c43eb9 --- /dev/null +++ b/hindsight-clients/python/hindsight_client_api/models/recover_consolidation_response.py @@ -0,0 +1,87 @@ +# coding: utf-8 + +""" + Hindsight HTTP API + + HTTP API for Hindsight + + The version of the OpenAPI document: 0.4.18 + 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 +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self + +class RecoverConsolidationResponse(BaseModel): + """ + Response model for recovering failed consolidation. + """ # noqa: E501 + retried_count: StrictInt + __properties: ClassVar[List[str]] = ["retried_count"] + + 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 RecoverConsolidationResponse 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 RecoverConsolidationResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "retried_count": obj.get("retried_count") + }) + return _obj + + diff --git a/hindsight-clients/typescript/generated/sdk.gen.ts b/hindsight-clients/typescript/generated/sdk.gen.ts index 483793ed4..ead1fa773 100644 --- a/hindsight-clients/typescript/generated/sdk.gen.ts +++ b/hindsight-clients/typescript/generated/sdk.gen.ts @@ -131,6 +131,9 @@ import type { RecallMemoriesData, RecallMemoriesErrors, RecallMemoriesResponses, + RecoverConsolidationData, + RecoverConsolidationErrors, + RecoverConsolidationResponses, ReflectData, ReflectErrors, ReflectResponses, @@ -935,6 +938,20 @@ export const clearObservations = ( ThrowOnError >({ url: "/v1/default/banks/{bank_id}/observations", ...options }); +/** + * Recover failed consolidation + * + * Reset all memories that were permanently marked as failed during consolidation (after exhausting all LLM retries and adaptive batch splitting) so they are picked up again on the next consolidation run. Does not delete any observations. + */ +export const recoverConsolidation = ( + options: Options, +) => + (options.client ?? client).post< + RecoverConsolidationResponses, + RecoverConsolidationErrors, + ThrowOnError + >({ url: "/v1/default/banks/{bank_id}/consolidation/recover", ...options }); + /** * Clear observations for a memory * diff --git a/hindsight-clients/typescript/generated/types.gen.ts b/hindsight-clients/typescript/generated/types.gen.ts index ff163810c..7ecbd4e93 100644 --- a/hindsight-clients/typescript/generated/types.gen.ts +++ b/hindsight-clients/typescript/generated/types.gen.ts @@ -1608,6 +1608,18 @@ export type RecallResult = { source_fact_ids?: Array | null; }; +/** + * RecoverConsolidationResponse + * + * Response model for recovering failed consolidation. + */ +export type RecoverConsolidationResponse = { + /** + * Retried Count + */ + retried_count: number; +}; + /** * ReflectBasedOn * @@ -4245,6 +4257,44 @@ export type ClearObservationsResponses = { export type ClearObservationsResponse = ClearObservationsResponses[keyof ClearObservationsResponses]; +export type RecoverConsolidationData = { + body?: never; + headers?: { + /** + * Authorization + */ + authorization?: string | null; + }; + path: { + /** + * Bank Id + */ + bank_id: string; + }; + query?: never; + url: "/v1/default/banks/{bank_id}/consolidation/recover"; +}; + +export type RecoverConsolidationErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type RecoverConsolidationError = + RecoverConsolidationErrors[keyof RecoverConsolidationErrors]; + +export type RecoverConsolidationResponses = { + /** + * Successful Response + */ + 200: RecoverConsolidationResponse; +}; + +export type RecoverConsolidationResponse2 = + RecoverConsolidationResponses[keyof RecoverConsolidationResponses]; + export type ClearMemoryObservationsData = { body?: never; headers?: { diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/consolidation-recover/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/consolidation-recover/route.ts new file mode 100644 index 000000000..42eabf04a --- /dev/null +++ b/hindsight-control-plane/src/app/api/banks/[bankId]/consolidation-recover/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { sdk, lowLevelClient } from "@/lib/hindsight-client"; + +export async function POST(request: Request, { params }: { params: Promise<{ bankId: string }> }) { + try { + const { bankId } = await params; + + if (!bankId) { + return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); + } + + const response = await sdk.recoverConsolidation({ + client: lowLevelClient, + path: { bank_id: bankId }, + }); + + if (response.error) { + console.error("API error recovering consolidation:", response.error); + return NextResponse.json({ error: "Failed to recover consolidation" }, { status: 500 }); + } + + return NextResponse.json(response.data, { status: 200 }); + } catch (error) { + console.error("Error recovering consolidation:", error); + return NextResponse.json({ error: "Failed to recover consolidation" }, { status: 500 }); + } +} diff --git a/hindsight-control-plane/src/app/banks/[bankId]/page.tsx b/hindsight-control-plane/src/app/banks/[bankId]/page.tsx index e12e2da5e..a4a213db4 100644 --- a/hindsight-control-plane/src/app/banks/[bankId]/page.tsx +++ b/hindsight-control-plane/src/app/banks/[bankId]/page.tsx @@ -62,6 +62,7 @@ export default function BankPage() { const [showClearObservationsDialog, setShowClearObservationsDialog] = useState(false); const [isClearingObservations, setIsClearingObservations] = useState(false); const [isConsolidating, setIsConsolidating] = useState(false); + const [isRecoveringConsolidation, setIsRecoveringConsolidation] = useState(false); const [showResetConfigDialog, setShowResetConfigDialog] = useState(false); const [isResettingConfig, setIsResettingConfig] = useState(false); @@ -137,6 +138,22 @@ export default function BankPage() { } }; + const handleRecoverConsolidation = async () => { + if (!bankId) return; + + setIsRecoveringConsolidation(true); + try { + const result = await client.recoverConsolidation(bankId); + toast.success( + `Recovered ${result.retried_count} failed ${result.retried_count === 1 ? "memory" : "memories"} for re-consolidation` + ); + } catch (error) { + // Error toast is shown automatically by the API client interceptor + } finally { + setIsRecoveringConsolidation(false); + } + }; + return (
@@ -181,6 +198,23 @@ export default function BankPage() { Off )} + + {isRecoveringConsolidation ? ( + + ) : ( + + )} + {isRecoveringConsolidation ? "Recovering..." : "Recover Consolidation"} + {!observationsEnabled && ( + Off + )} + setShowClearObservationsDialog(true)} disabled={!observationsEnabled} diff --git a/hindsight-control-plane/src/lib/api.ts b/hindsight-control-plane/src/lib/api.ts index 9178ecf14..521713b04 100644 --- a/hindsight-control-plane/src/lib/api.ts +++ b/hindsight-control-plane/src/lib/api.ts @@ -417,6 +417,17 @@ export class ControlPlaneClient { }); } + /** + * Recover failed consolidation for a bank (reset memories marked consolidation_failed_at) + */ + async recoverConsolidation(bankId: string) { + return this.fetchApi<{ + retried_count: number; + }>(`/api/banks/${bankId}/consolidation-recover`, { + method: "POST", + }); + } + /** * Get chunk */ diff --git a/hindsight-docs/static/openapi.json b/hindsight-docs/static/openapi.json index a41f2c3a3..78777d64f 100644 --- a/hindsight-docs/static/openapi.json +++ b/hindsight-docs/static/openapi.json @@ -3125,6 +3125,65 @@ } } }, + "/v1/default/banks/{bank_id}/consolidation/recover": { + "post": { + "tags": [ + "Banks" + ], + "summary": "Recover failed consolidation", + "description": "Reset all memories that were permanently marked as failed during consolidation (after exhausting all LLM retries and adaptive batch splitting) so they are picked up again on the next consolidation run. Does not delete any observations.", + "operationId": "recover_consolidation", + "parameters": [ + { + "name": "bank_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bank Id" + } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecoverConsolidationResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/v1/default/banks/{bank_id}/memories/{memory_id}/observations": { "delete": { "tags": [ @@ -6901,6 +6960,23 @@ "type": "world" } }, + "RecoverConsolidationResponse": { + "properties": { + "retried_count": { + "type": "integer", + "title": "Retried Count" + } + }, + "type": "object", + "required": [ + "retried_count" + ], + "title": "RecoverConsolidationResponse", + "description": "Response model for recovering failed consolidation.", + "example": { + "retried_count": 42 + } + }, "ReflectBasedOn": { "properties": { "memories": { diff --git a/uv.lock b/uv.lock index ce995723f..38632e305 100644 --- a/uv.lock +++ b/uv.lock @@ -4,12 +4,16 @@ requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform != 'win32'", - "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'darwin'", ] [manifest] @@ -603,15 +607,12 @@ name = "claude-agent-sdk" version = "0.1.31" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "mcp" }, + { name = "anyio", marker = "sys_platform != 'win32'" }, + { name = "mcp", marker = "sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/df/071dce5803c4db8cd53708bcda3b6022c1c4b68fc00e9007593309515286/claude_agent_sdk-0.1.31.tar.gz", hash = "sha256:b68c681083d7cc985dd3e48f73aabf459f056c1a7e1c5b9c47033c6af94da1a1", size = 61191 } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/7c/e249a3b4215e28a9722b3d9ab6057bceeeaa2b948530f022065ef2154555/claude_agent_sdk-0.1.31-py3-none-macosx_11_0_arm64.whl", hash = "sha256:801bacfe4192782a7cc7b61b0d23a57f061c069993dd3dfa8109aa2e7050a530", size = 54284257 }, - { url = "https://files.pythonhosted.org/packages/d6/a8/1a8288736aeafcc48e3dcb3326ec7f487dbf89ebba77d526e9464786a299/claude_agent_sdk-0.1.31-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:0b608e0cbfcedcb827427e6d16a73fe573d58e7f93e15f95435066feacbe6511", size = 68462461 }, - { url = "https://files.pythonhosted.org/packages/26/7a/7dcd0b77263ed55b17554fa3a67a6772b788e7048a524fd06c9baa970564/claude_agent_sdk-0.1.31-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:d0cb30e026a22246e84d9237d23bb4df20be5146913a04d2802ddd37d4f8b8c9", size = 70173234 }, - { url = "https://files.pythonhosted.org/packages/37/a5/4a8de7a9738f454b54aa97557f0fba9c74b0901ea418597008c668243fea/claude_agent_sdk-0.1.31-py3-none-win_amd64.whl", hash = "sha256:8ceca675c2770ad739bd1208362059a830e91c74efcf128045b5a7af14d36f2b", size = 72366975 }, ] [[package]] @@ -1504,7 +1505,7 @@ dependencies = [ { name = "anthropic" }, { name = "asyncpg" }, { name = "authlib" }, - { name = "claude-agent-sdk" }, + { name = "claude-agent-sdk", marker = "sys_platform == 'darwin'" }, { name = "cohere" }, { name = "cryptography" }, { name = "dateparser" }, @@ -1556,7 +1557,8 @@ all = [ { name = "pg0-embedded" }, { name = "safetensors" }, { name = "sentence-transformers" }, - { name = "torch" }, + { name = "torch", version = "2.10.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.10.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, { name = "transformers" }, ] embedded-db = [ @@ -1569,7 +1571,8 @@ local-ml = [ { name = "mlx-lm" }, { name = "safetensors" }, { name = "sentence-transformers" }, - { name = "torch" }, + { name = "torch", version = "2.10.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.10.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, { name = "transformers" }, ] test = [ @@ -1601,7 +1604,7 @@ requires-dist = [ { name = "anthropic", specifier = ">=0.40.0" }, { name = "asyncpg", specifier = ">=0.29.0" }, { name = "authlib", specifier = ">=1.6.6" }, - { name = "claude-agent-sdk", specifier = ">=0.1.27" }, + { name = "claude-agent-sdk", marker = "sys_platform == 'darwin'", specifier = ">=0.1.27" }, { name = "cohere", specifier = ">=5.0.0" }, { name = "cryptography", specifier = ">=46.0.5" }, { name = "dateparser", specifier = ">=1.2.2" }, @@ -1651,7 +1654,7 @@ requires-dist = [ { name = "sqlalchemy", specifier = ">=2.0.44" }, { name = "testcontainers", marker = "extra == 'test'", specifier = ">=4.0.0" }, { name = "tiktoken", specifier = ">=0.12.0" }, - { name = "torch", marker = "extra == 'local-ml'", specifier = ">=2.6.0" }, + { name = "torch", marker = "extra == 'local-ml'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cpu" }, { name = "transformers", marker = "extra == 'local-ml'", specifier = ">=4.53.0" }, { name = "typer", specifier = ">=0.9.0" }, { name = "urllib3", specifier = ">=2.6.3" }, @@ -2906,140 +2909,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130 }, ] -[[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921 }, -] - -[[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621 }, -] - -[[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029 }, -] - -[[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765 }, -] - -[[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467 }, -] - -[[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695 }, -] - -[[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834 }, -] - -[[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976 }, -] - -[[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform != 'win32'" }, - { name = "nvidia-cusparse-cu12", marker = "sys_platform != 'win32'" }, - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905 }, -] - -[[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466 }, -] - -[[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691 }, -] - -[[package]] -name = "nvidia-nccl-cu12" -version = "2.27.5" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229 }, -] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836 }, -] - -[[package]] -name = "nvidia-nvshmem-cu12" -version = "3.3.20" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145 }, -] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954 }, -] - [[package]] name = "oauthlib" version = "3.3.1" @@ -4945,8 +4814,8 @@ name = "secretstorage" version = "3.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "sys_platform != 'win32'" }, - { name = "jeepney", marker = "sys_platform != 'win32'" }, + { name = "cryptography", marker = "sys_platform != 'darwin' and sys_platform != 'win32'" }, + { name = "jeepney", marker = "sys_platform != 'darwin' and sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/32/8a/ed6747b1cc723c81f526d4c12c1b1d43d07190e1e8258dbf934392fc850e/secretstorage-3.4.1.tar.gz", hash = "sha256:a799acf5be9fb93db609ebaa4ab6e8f1f3ed5ae640e0fa732bfea59e9c3b50e8", size = 19871 } wheels = [ @@ -4961,7 +4830,8 @@ dependencies = [ { name = "huggingface-hub" }, { name = "scikit-learn" }, { name = "scipy" }, - { name = "torch" }, + { name = "torch", version = "2.10.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.10.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, { name = "tqdm" }, { name = "transformers" }, { name = "typing-extensions" }, @@ -5331,58 +5201,95 @@ wheels = [ [[package]] name = "torch" -version = "2.9.1" -source = { registry = "https://pypi.org/simple" } +version = "2.10.0" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'darwin'", +] dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, - { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "typing-extensions" }, + { name = "filelock", marker = "sys_platform == 'darwin'" }, + { name = "fsspec", marker = "sys_platform == 'darwin'" }, + { name = "jinja2", marker = "sys_platform == 'darwin'" }, + { name = "networkx", marker = "sys_platform == 'darwin'" }, + { name = "setuptools", marker = "python_full_version >= '3.12' and sys_platform == 'darwin'" }, + { name = "sympy", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/15/db/c064112ac0089af3d2f7a2b5bfbabf4aa407a78b74f87889e524b91c5402/torch-2.9.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:62b3fd888277946918cba4478cf849303da5359f0fb4e3bfb86b0533ba2eaf8d", size = 104220430 }, - { url = "https://files.pythonhosted.org/packages/56/be/76eaa36c9cd032d3b01b001e2c5a05943df75f26211f68fae79e62f87734/torch-2.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d033ff0ac3f5400df862a51bdde9bad83561f3739ea0046e68f5401ebfa67c1b", size = 899821446 }, - { url = "https://files.pythonhosted.org/packages/47/cc/7a2949e38dfe3244c4df21f0e1c27bce8aedd6c604a587dd44fc21017cb4/torch-2.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d06b30a9207b7c3516a9e0102114024755a07045f0c1d2f2a56b1819ac06bcb", size = 110973074 }, - { url = "https://files.pythonhosted.org/packages/1e/ce/7d251155a783fb2c1bb6837b2b7023c622a2070a0a72726ca1df47e7ea34/torch-2.9.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:52347912d868653e1528b47cafaf79b285b98be3f4f35d5955389b1b95224475", size = 74463887 }, - { url = "https://files.pythonhosted.org/packages/0f/27/07c645c7673e73e53ded71705045d6cb5bae94c4b021b03aa8d03eee90ab/torch-2.9.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:da5f6f4d7f4940a173e5572791af238cb0b9e21b1aab592bd8b26da4c99f1cd6", size = 104126592 }, - { url = "https://files.pythonhosted.org/packages/19/17/e377a460603132b00760511299fceba4102bd95db1a0ee788da21298ccff/torch-2.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:27331cd902fb4322252657f3902adf1c4f6acad9dcad81d8df3ae14c7c4f07c4", size = 899742281 }, - { url = "https://files.pythonhosted.org/packages/b1/1a/64f5769025db846a82567fa5b7d21dba4558a7234ee631712ee4771c436c/torch-2.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:81a285002d7b8cfd3fdf1b98aa8df138d41f1a8334fd9ea37511517cedf43083", size = 110940568 }, - { url = "https://files.pythonhosted.org/packages/6e/ab/07739fd776618e5882661d04c43f5b5586323e2f6a2d7d84aac20d8f20bd/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:c0d25d1d8e531b8343bea0ed811d5d528958f1dcbd37e7245bc686273177ad7e", size = 74479191 }, - { url = "https://files.pythonhosted.org/packages/20/60/8fc5e828d050bddfab469b3fe78e5ab9a7e53dda9c3bdc6a43d17ce99e63/torch-2.9.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c29455d2b910b98738131990394da3e50eea8291dfeb4b12de71ecf1fdeb21cb", size = 104135743 }, - { url = "https://files.pythonhosted.org/packages/f2/b7/6d3f80e6918213babddb2a37b46dbb14c15b14c5f473e347869a51f40e1f/torch-2.9.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:524de44cd13931208ba2c4bde9ec7741fd4ae6bfd06409a604fc32f6520c2bc9", size = 899749493 }, - { url = "https://files.pythonhosted.org/packages/a6/47/c7843d69d6de8938c1cbb1eba426b1d48ddf375f101473d3e31a5fc52b74/torch-2.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:545844cc16b3f91e08ce3b40e9c2d77012dd33a48d505aed34b7740ed627a1b2", size = 110944162 }, - { url = "https://files.pythonhosted.org/packages/28/0e/2a37247957e72c12151b33a01e4df651d9d155dd74d8cfcbfad15a79b44a/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5be4bf7496f1e3ffb1dd44b672adb1ac3f081f204c5ca81eba6442f5f634df8e", size = 74830751 }, - { url = "https://files.pythonhosted.org/packages/4b/f7/7a18745edcd7b9ca2381aa03353647bca8aace91683c4975f19ac233809d/torch-2.9.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:30a3e170a84894f3652434b56d59a64a2c11366b0ed5776fab33c2439396bf9a", size = 104142929 }, - { url = "https://files.pythonhosted.org/packages/f4/dd/f1c0d879f2863ef209e18823a988dc7a1bf40470750e3ebe927efdb9407f/torch-2.9.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8301a7b431e51764629208d0edaa4f9e4c33e6df0f2f90b90e261d623df6a4e2", size = 899748978 }, - { url = "https://files.pythonhosted.org/packages/1f/9f/6986b83a53b4d043e36f3f898b798ab51f7f20fdf1a9b01a2720f445043d/torch-2.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2e1c42c0ae92bf803a4b2409fdfed85e30f9027a66887f5e7dcdbc014c7531db", size = 111176995 }, - { url = "https://files.pythonhosted.org/packages/40/60/71c698b466dd01e65d0e9514b5405faae200c52a76901baf6906856f17e4/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:2c14b3da5df416cf9cb5efab83aa3056f5b8cd8620b8fde81b4987ecab730587", size = 74480347 }, - { url = "https://files.pythonhosted.org/packages/48/50/c4b5112546d0d13cc9eaa1c732b823d676a9f49ae8b6f97772f795874a03/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1edee27a7c9897f4e0b7c14cfc2f3008c571921134522d5b9b5ec4ebbc69041a", size = 74433245 }, - { url = "https://files.pythonhosted.org/packages/81/c9/2628f408f0518b3bae49c95f5af3728b6ab498c8624ab1e03a43dd53d650/torch-2.9.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:19d144d6b3e29921f1fc70503e9f2fc572cde6a5115c0c0de2f7ca8b1483e8b6", size = 104134804 }, - { url = "https://files.pythonhosted.org/packages/28/fc/5bc91d6d831ae41bf6e9e6da6468f25330522e92347c9156eb3f1cb95956/torch-2.9.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c432d04376f6d9767a9852ea0def7b47a7bbc8e7af3b16ac9cf9ce02b12851c9", size = 899747132 }, - { url = "https://files.pythonhosted.org/packages/63/5d/e8d4e009e52b6b2cf1684bde2a6be157b96fb873732542fb2a9a99e85a83/torch-2.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:d187566a2cdc726fc80138c3cdb260970fab1c27e99f85452721f7759bbd554d", size = 110934845 }, - { url = "https://files.pythonhosted.org/packages/bd/b2/2d15a52516b2ea3f414643b8de68fa4cb220d3877ac8b1028c83dc8ca1c4/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cb10896a1f7fedaddbccc2017ce6ca9ecaaf990f0973bdfcf405439750118d2c", size = 74823558 }, - { url = "https://files.pythonhosted.org/packages/86/5c/5b2e5d84f5b9850cd1e71af07524d8cbb74cba19379800f1f9f7c997fc70/torch-2.9.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0a2bd769944991c74acf0c4ef23603b9c777fdf7637f115605a4b2d8023110c7", size = 104145788 }, - { url = "https://files.pythonhosted.org/packages/a9/8c/3da60787bcf70add986c4ad485993026ac0ca74f2fc21410bc4eb1bb7695/torch-2.9.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:07c8a9660bc9414c39cac530ac83b1fb1b679d7155824144a40a54f4a47bfa73", size = 899735500 }, - { url = "https://files.pythonhosted.org/packages/db/2b/f7818f6ec88758dfd21da46b6cd46af9d1b3433e53ddbb19ad1e0da17f9b/torch-2.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c88d3299ddeb2b35dcc31753305612db485ab6f1823e37fb29451c8b2732b87e", size = 111163659 }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:0826ac8e409551e12b2360ac18b4161a838cbd111933e694752f351191331d09" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:7fbbf409143a4fe0812a40c0b46a436030a7e1d14fe8c5234dfbe44df47f617e" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:b39cafff7229699f9d6e172cac74d85fd71b568268e439e08d9c540e54732a3e" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:90821a3194b8806d9fa9fdaa9308c1bc73df0c26808274b14129a97c99f35794" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:358bd7125cbec6e692d60618a5eec7f55a51b29e3652a849fd42af021d818023" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:470de4176007c2700735e003a830828a88d27129032a3add07291da07e2a94e8" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:4584ab167995c0479f6821e3dceaf199c8166c811d3adbba5d8eedbbfa6764fd" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:45a1c5057629444aeb1c452c18298fa7f30f2f7aeadd4dc41f9d340980294407" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:339e05502b6c839db40e88720cb700f5a3b50cda332284873e851772d41b2c1e" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:840351da59cedb7bcbc51981880050813c19ef6b898a7fecf73a3afc71aff3fe" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:c88b1129fd4e14f0f882963c6728315caae35d2f47374d17edeed1edc7697497" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f4bea7dc451267c028593751612ad559299589304e68df54ae7672427893ff2c" }, +] + +[[package]] +name = "torch" +version = "2.10.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'win32'", +] +dependencies = [ + { name = "filelock", marker = "sys_platform != 'darwin'" }, + { name = "fsspec", marker = "sys_platform != 'darwin'" }, + { name = "jinja2", marker = "sys_platform != 'darwin'" }, + { name = "networkx", marker = "sys_platform != 'darwin'" }, + { name = "setuptools", marker = "python_full_version >= '3.12' and sys_platform != 'darwin'" }, + { name = "sympy", marker = "sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp311-cp311-linux_aarch64.whl", hash = "sha256:ce5c113d1f55f8c1f5af05047a24e50d11d293e0cbbb5bf7a75c6c761edd6eaa" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp311-cp311-linux_s390x.whl", hash = "sha256:0e286fcf6ce0cc7b204396c9b4ea0d375f1f0c3e752f68ce3d3aeb265511db8c" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1cfcb9b1558c6e52dffd0d4effce83b13c5ae5d97338164c372048c21f9cfccb" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:b7cb1ec66cefb90fd7b676eac72cfda3b8d4e4d0cacd7a531963bc2e0a9710ab" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:17a09465bab2aab8f0f273410297133d8d8fb6dd84dccbd252ca4a4f3a111847" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp311-cp311-win_arm64.whl", hash = "sha256:c35c0de592941d4944698dbfa87271ab85d3370eca3b694943a2ab307ac34b3f" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-linux_aarch64.whl", hash = "sha256:8de5a36371b775e2d4881ed12cc7f2de400b1ad3d728aa74a281f649f87c9b8c" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:9accc30b56cb6756d4a9d04fcb8ebc0bb68c7d55c1ed31a8657397d316d31596" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:179451716487f8cb09b56459667fa1f5c4c0946c1e75fbeae77cfc40a5768d87" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ee40b8a4b4b2cf0670c6fd4f35a7ef23871af956fecb238fbf5da15a72650b1d" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:21cb5436978ef47c823b7a813ff0f8c2892e266cfe0f1d944879b5fba81bf4e1" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:3eaa727e6a73affa61564d86b9d03191df45c8650d0666bd3d57c8597ef61e78" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-linux_aarch64.whl", hash = "sha256:fd215f3d0f681905c5b56b0630a3d666900a37fcc3ca5b937f95275c66f9fd9c" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:170a0623108055be5199370335cf9b41ba6875b3cb6f086db4aee583331a4899" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e51994492cdb76edce29da88de3672a3022f9ef0ffd90345436948d4992be2c7" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8d316e5bf121f1eab1147e49ad0511a9d92e4c45cc357d1ab0bee440da71a095" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:b719da5af01b59126ac13eefd6ba3dd12d002dc0e8e79b8b365e55267a8189d3" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:b67d91326e4ed9eccbd6b7d84ed7ffa43f93103aa3f0b24145f3001f3b11b714" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-linux_aarch64.whl", hash = "sha256:5af75e5f49de21b0bdf7672bc27139bd285f9e8dbcabe2d617a2eb656514ac36" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-linux_s390x.whl", hash = "sha256:ba51ef01a510baf8fff576174f702c47e1aa54389a9f1fba323bb1a5003ff0bf" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0fedcb1a77e8f2aaf7bfd21591bf6d1e0b207473268c9be16b17cb7783253969" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:106dd1930cb30a4a337366ba3f9b25318ebf940f51fd46f789281dd9e736bdc4" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:eb1bde1ce198f05c8770017de27e001d404499cf552aaaa014569eff56ca25c0" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-linux_aarch64.whl", hash = "sha256:ea2bcc9d1fca66974a71d4bf9a502539283f35d61fcab5a799b4e120846f1e02" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:f8294fd2fc6dd8f4435a891a0122307a043b14b21f0dac1bca63c85bfb59e586" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a28fdbcfa2fbacffec81300f24dd1bed2b0ccfdbed107a823cff12bc1db070f6" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:aada8afc068add586464b2a55adb7cc9091eec55caf5320447204741cb6a0604" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:2adc71fe471e98a608723bfc837f7e1929885ebb912c693597711e139c1cda41" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-linux_aarch64.whl", hash = "sha256:9412bd37b70f5ebd1205242c4ba4cabae35a605947f2b30806d5c9b467936db9" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:e71c476517c33e7db69825a9ff46c7f47a723ec4dac5b2481cff4246d1c632be" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:23882f8d882460aca809882fc42f5e343bf07585274f929ced00177d1be1eb67" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4fcd8b4cc2ae20f2b7749fb275349c55432393868778c2d50a08e81d5ee5591e" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.10.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:ffc8da9a1341092d6a90cb5b1c1a33cd61abf0fb43f0cd88443c27fa372c26ae" }, ] [[package]] @@ -5436,19 +5343,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/88/ae8320064e32679a5429a2c9ebbc05c2bf32cefb6e076f9b07f6d685a9b4/transformers-5.3.0-py3-none-any.whl", hash = "sha256:50ac8c89c3c7033444fb3f9f53138096b997ebb70d4b5e50a2e810bf12d3d29a", size = 10661827 }, ] -[[package]] -name = "triton" -version = "3.5.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/72/ec90c3519eaf168f22cb1757ad412f3a2add4782ad3a92861c9ad135d886/triton-3.5.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61413522a48add32302353fdbaaf92daaaab06f6b5e3229940d21b5207f47579", size = 170425802 }, - { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207 }, - { url = "https://files.pythonhosted.org/packages/27/46/8c3bbb5b0a19313f50edcaa363b599e5a1a5ac9683ead82b9b80fe497c8d/triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4346b6ebbd4fad18773f5ba839114f4826037c9f2f34e0148894cd5dd3dba", size = 170470410 }, - { url = "https://files.pythonhosted.org/packages/37/92/e97fcc6b2c27cdb87ce5ee063d77f8f26f19f06916aa680464c8104ef0f6/triton-3.5.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d2c70127fca6a23e247f9348b8adde979d2e7a20391bfbabaac6aebc7e6a8", size = 170579924 }, - { url = "https://files.pythonhosted.org/packages/a4/e6/c595c35e5c50c4bc56a7bac96493dad321e9e29b953b526bbbe20f9911d0/triton-3.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0637b1efb1db599a8e9dc960d53ab6e4637db7d4ab6630a0974705d77b14b60", size = 170480488 }, - { url = "https://files.pythonhosted.org/packages/16/b5/b0d3d8b901b6a04ca38df5e24c27e53afb15b93624d7fd7d658c7cd9352a/triton-3.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bac7f7d959ad0f48c0e97d6643a1cc0fd5786fe61cb1f83b537c6b2d54776478", size = 170582192 }, -] - [[package]] name = "ty" version = "0.0.8"