From c070b375a7ea3bebd96427a00624fa5c30633cc4 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:17:53 -0400 Subject: [PATCH] Add Pre-commit Hooks (#165) * chore (pre-commit): Setup Pre-commit Hooks and Add Path filter to unittests * chore (docs): Add pre-commit hook docs * chore (docs): Code Rabbit nitpicks --- .env.template | 2 +- .github/PULL_REQUEST_TEMPLATE/release.md | 1 - .github/workflows/docker-build.yml | 2 +- .github/workflows/unittest.yml | 67 ++ .markdownlint.json | 15 + .pre-commit-config.yaml | 130 ++++ CONTRIBUTING.md | 3 + Dockerfile | 1 - README.md | 52 ++ config.toml.example | 2 +- docs/README.md | 10 +- docs/changelog/compatibility-guide.mdx | 4 +- docs/changelog/introduction.mdx | 8 +- docs/logo/honcho-dark.svg | 2 +- docs/logo/honcho-light.svg | 2 +- docs/v1/README.md | 2 +- .../endpoint/apps/create-app.mdx | 2 +- .../endpoint/apps/get-all-apps.mdx | 2 +- .../endpoint/apps/get-app-by-name.mdx | 2 +- .../endpoint/apps/get-or-create-app.mdx | 2 +- .../endpoint/apps/update-app.mdx | 2 +- .../collections/create-collection.mdx | 2 +- .../collections/delete-collection.mdx | 2 +- .../collections/get-collection-by-name.mdx | 2 +- .../endpoint/collections/get-collection.mdx | 2 +- .../endpoint/collections/get-collections.mdx | 2 +- .../collections/update-collection.mdx | 2 +- .../endpoint/documents/create-document.mdx | 2 +- .../endpoint/documents/delete-document.mdx | 2 +- .../endpoint/documents/get-document.mdx | 2 +- .../endpoint/documents/get-documents.mdx | 2 +- .../endpoint/documents/update-document.mdx | 2 +- .../endpoint/keys/create-key.mdx | 2 +- .../create-batch-messages-for-session.mdx | 2 +- .../messages/create-message-for-session.mdx | 2 +- .../endpoint/messages/get-message.mdx | 2 +- .../endpoint/messages/get-messages.mdx | 2 +- .../endpoint/messages/update-message.mdx | 2 +- .../api-reference/endpoint/sessions/chat.mdx | 2 +- .../endpoint/sessions/clone-session.mdx | 2 +- .../endpoint/sessions/create-session.mdx | 2 +- .../endpoint/sessions/delete-session.mdx | 2 +- .../endpoint/sessions/get-chat-stream.mdx | 2 +- .../endpoint/sessions/get-sessions.mdx | 2 +- .../endpoint/sessions/update-session.mdx | 2 +- .../endpoint/users/create-user.mdx | 2 +- .../endpoint/users/get-or-create-user.mdx | 2 +- .../endpoint/users/get-user-by-name.mdx | 2 +- .../endpoint/users/get-users.mdx | 2 +- .../endpoint/users/update-user.mdx | 2 +- docs/v1/api-reference/introduction.mdx | 4 +- docs/v1/contributing/error-handling.mdx | 36 +- docs/v1/contributing/guidelines.mdx | 4 +- docs/v1/contributing/license.mdx | 4 +- docs/v1/getting-started/architecture.mdx | 58 +- docs/v1/getting-started/development.mdx | 2 +- docs/v1/guides/dialectic-endpoint.mdx | 13 +- docs/v1/guides/discord.mdx | 11 +- docs/v1/guides/honcho-mcp.mdx | 6 +- docs/v1/guides/overview.mdx | 2 +- docs/v1/guides/simple-memory.mdx | 18 +- docs/v1/guides/streaming-response.mdx | 24 +- docs/v1/openapi.json | 2 +- docs/v2/README.md | 2 +- .../endpoint/keys/create-key.mdx | 2 +- .../messages/create-messages-for-session.mdx | 2 +- .../endpoint/messages/get-message.mdx | 2 +- .../endpoint/messages/get-messages.mdx | 2 +- .../endpoint/messages/update-message.mdx | 2 +- .../endpoint/messages/upload-file.mdx | 2 +- docs/v2/api-reference/endpoint/peers/chat.mdx | 2 +- .../endpoint/peers/get-or-create-peer.mdx | 2 +- .../endpoint/peers/get-peers.mdx | 2 +- .../endpoint/peers/get-sessions-for-peer.mdx | 2 +- .../peers/get-working-representation.mdx | 2 +- .../endpoint/peers/search-peer.mdx | 2 +- .../endpoint/peers/update-peer.mdx | 2 +- .../sessions/add-peers-to-session.mdx | 2 +- .../endpoint/sessions/clone-session.mdx | 2 +- .../endpoint/sessions/delete-session.mdx | 2 +- .../sessions/get-or-create-session.mdx | 2 +- .../endpoint/sessions/get-peer-config.mdx | 2 +- .../endpoint/sessions/get-session-context.mdx | 2 +- .../endpoint/sessions/get-session-peers.mdx | 2 +- .../endpoint/sessions/get-sessions.mdx | 2 +- .../sessions/remove-peers-from-session.mdx | 2 +- .../endpoint/sessions/search-session.mdx | 2 +- .../endpoint/sessions/set-peer-config.mdx | 2 +- .../endpoint/sessions/set-session-peers.mdx | 2 +- .../endpoint/sessions/update-session.mdx | 2 +- .../workspaces/get-all-workspaces.mdx | 2 +- .../workspaces/get-deriver-status.mdx | 2 +- .../workspaces/get-or-create-workspace.mdx | 2 +- .../endpoint/workspaces/search-workspace.mdx | 2 +- .../endpoint/workspaces/update-workspace.mdx | 2 +- docs/v2/contributing/guidelines.mdx | 4 +- docs/v2/contributing/license.mdx | 4 +- .../core-concepts/architecture.mdx | 22 +- .../documentation/core-concepts/features.mdx | 14 +- .../documentation/core-concepts/glossary.mdx | 2 +- .../documentation/introduction/overview.mdx | 2 +- .../documentation/introduction/quickstart.mdx | 17 +- .../documentation/introduction/vibecoding.mdx | 12 +- .../reference/guided-tutorial.mdx | 62 +- docs/v2/documentation/reference/platform.mdx | 10 +- docs/v2/documentation/reference/sdk.mdx | 44 +- docs/v2/guides/ai-assisted-setup.mdx | 14 +- docs/v2/guides/dialectic-endpoint.mdx | 9 +- docs/v2/guides/discord.mdx | 6 +- docs/v2/guides/file-uploads.mdx | 18 +- docs/v2/guides/get-context.mdx | 50 +- docs/v2/guides/mcp.mdx | 4 +- docs/v2/guides/overview.mdx | 18 +- docs/v2/guides/search.mdx | 28 +- docs/v2/guides/streaming-response.mdx | 50 +- docs/v2/guides/telegram.mdx | 2 +- docs/v2/guides/using-filters.mdx | 2 +- docs/v2/guides/working-rep.mdx | 5 +- docs/v2/openapi.documented.yml | 4 +- migrations/README | 2 +- ...3cf2cf77_add_indexes_to_documents_table.py | 2 +- .../d429de0e5338_adopt_peer_paradigm.py | 60 +- pyproject.toml | 26 +- scripts/update_version.py | 614 ++++++++++-------- sdks/python/src/honcho/py.typed | 1 - sdks/python/uv.lock | 2 +- sdks/typescript/.gitignore | 2 +- sdks/typescript/README.md | 1 - sdks/typescript/__tests__/README.md | 19 +- .../__tests__/__mocks__/@honcho-ai/core.ts | 2 +- sdks/typescript/__tests__/client.test.ts | 2 +- sdks/typescript/__tests__/integration.test.ts | 2 +- sdks/typescript/__tests__/pagination.test.ts | 118 ++-- sdks/typescript/__tests__/peer.test.ts | 2 +- sdks/typescript/__tests__/session.test.ts | 2 +- .../__tests__/session_context.test.ts | 70 +- sdks/typescript/__tests__/setup.ts | 2 +- sdks/typescript/examples/get_context.ts | 4 +- .../typescript/examples/get_representation.ts | 4 +- sdks/typescript/examples/search.ts | 4 +- sdks/typescript/jest.config.js | 2 +- sdks/typescript/package.json | 1 + sdks/typescript/tsconfig.json | 2 +- src/deriver/prompts.py | 14 +- src/deriver/queue_payload.py | 4 +- src/dialectic/prompts.py | 8 +- src/utils/summarizer.py | 2 +- tests/sdk/test_client.py | 6 +- tests/test_advanced_filters.py | 36 +- uv.lock | 107 +++ 150 files changed, 1283 insertions(+), 862 deletions(-) create mode 100644 .markdownlint.json create mode 100644 .pre-commit-config.yaml diff --git a/.env.template b/.env.template index b2274be2..08052584 100644 --- a/.env.template +++ b/.env.template @@ -1,6 +1,6 @@ # Honcho Environment Variables Template # Copy this file to .env and fill in the appropriate values -# +# # Required variables are marked with (REQUIRED) # Optional variables have default values and can be left commented out diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md index 722015cb..4072d41b 100644 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -45,4 +45,3 @@ Log of changes introduced in this release in the style fo https://keepachangelog ### **Fixed** ### **Security** - diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index fd67fa24..23217a7f 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -54,5 +54,5 @@ jobs: uses: actions/attest-build-provenance@v1.1.2 with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - subject-digest: ${{ steps.push.outputs.digest }} + subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index dbb61218..42e1bfe5 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -3,14 +3,60 @@ name: FastAPI Tests with PostgreSQL and uv on: push: branches: [main] + paths: + - '**.py' + - '**.ts' + - '**.js' + - '**.tsx' + - '**.jsx' + - 'pyproject.toml' + - 'uv.lock' + - 'sdks/typescript/package.json' + - 'sdks/typescript/bun.lock' + - '.github/workflows/unittest.yml' pull_request: branches: [main] + paths: + - '**.py' + - '**.ts' + - '**.js' + - '**.tsx' + - '**.jsx' + - 'pyproject.toml' + - 'uv.lock' + - 'sdks/typescript/package.json' + - 'sdks/typescript/bun.lock' + - '.github/workflows/unittest.yml' permissions: contents: read jobs: + # Determine which tests to run based on changed files + changes: + runs-on: ubuntu-latest + outputs: + python: ${{ steps.filter.outputs.python }} + typescript: ${{ steps.filter.outputs.typescript }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + python: + - '**.py' + - 'pyproject.toml' + - 'uv.lock' + - 'migrations/**' + - '.github/workflows/unittest.yml' + typescript: + - 'sdks/typescript/**' + - '.github/workflows/unittest.yml' + test-python: + needs: changes + if: ${{ needs.changes.outputs.python == 'true' }} runs-on: ubuntu-latest services: @@ -64,6 +110,8 @@ jobs: SUMMARY_MODEL: test test-typescript: + needs: changes + if: ${{ needs.changes.outputs.typescript == 'true' }} runs-on: ubuntu-latest steps: @@ -86,3 +134,22 @@ jobs: env: HONCHO_API_KEY: test-key HONCHO_BASE_URL: http://localhost:8000 + + # Status check for branch protection rules + # This job always runs and reports success only if all required jobs pass + test-status: + runs-on: ubuntu-latest + needs: [changes, test-python, test-typescript] + if: always() + steps: + - name: Check test results + run: | + if [[ "${{ needs.changes.outputs.python }}" == "true" && "${{ needs.test-python.result }}" != "success" && "${{ needs.test-python.result }}" != "skipped" ]]; then + echo "Python tests failed or were cancelled" + exit 1 + fi + if [[ "${{ needs.changes.outputs.typescript }}" == "true" && "${{ needs.test-typescript.result }}" != "success" && "${{ needs.test-typescript.result }}" != "skipped" ]]; then + echo "TypeScript tests failed or were cancelled" + exit 1 + fi + echo "All required tests passed!" diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..7970bd37 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,15 @@ +{ + "default": true, + "MD013": false, + "MD024": false, + "MD025": false, + "MD029": false, + "MD040": false, + "MD041": false, + "line-length": false, + "no-duplicate-heading": false, + "single-h1": false, + "ol-prefix": false, + "fenced-code-language": false, + "first-line-h1": false +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..49f577ce --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,130 @@ +# .pre-commit-config.yaml +repos: + # Basic file checks (run on all files) + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-added-large-files + args: ['--maxkb=1000'] + - id: check-merge-conflict + - id: debug-statements + files: \.(py|js|ts)$ + - id: mixed-line-ending + args: ['--fix=lf'] + # Additional checks from suggestions + - id: check-docstring-first + files: \.py$ + - id: check-executables-have-shebangs + - id: check-case-conflict + + # Python code formatting and linting with ruff + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + # Linter - only on Python directories + - id: ruff + args: [--fix] + files: ^(src/|tests/|scripts/|migrations/|sdks/python/).*\.py$ + # Formatter - only on Python directories + - id: ruff-format + files: ^(src/|tests/|scripts/|migrations/|sdks/python/).*\.py$ + + # Security checks - only on main src code (not tests/scripts) + - repo: https://github.com/PyCQA/bandit + rev: 1.7.10 + hooks: + - id: bandit + args: ['-r'] + files: ^(src/|sdks/python/src/).*\.py$ + + # Local hooks using your uv environment + - repo: local + hooks: + # TypeScript linting with biome + - id: biome-check + name: biome check and format + entry: bash -c 'cd sdks/typescript && bun run lint:fix' + language: system + files: ^sdks/typescript/.*\.(js|ts|jsx|tsx|json|jsonc)$ + pass_filenames: false + + # Type checking with basedpyright - only on main Python code + - id: basedpyright + name: basedpyright + entry: uv run basedpyright + language: system + files: ^(src/|tests/|sdks/python/|scripts/).*\.py$ + require_serial: true + pass_filenames: false + + # Run main application tests + - id: pytest-main + name: pytest (main app) + entry: uv run pytest tests/ + language: system + files: ^(src/|tests/).*\.py$ + stages: [pre-push] + pass_filenames: false + + # Run Python SDK tests (if they exist) + - id: pytest-python-sdk + name: pytest (Python SDK) + entry: bash -c 'if [ -d "sdks/python/tests" ]; then cd sdks/python && uv run pytest; fi' + language: system + files: ^sdks/python/.*\.py$ + stages: [pre-push] + pass_filenames: false + + # TypeScript build/test with bun + - id: typescript-check + name: TypeScript build and test + entry: bash -c 'if [ -f "sdks/typescript/package.json" ]; then cd sdks/typescript && bun run build && bun run test; fi' + language: system + files: ^sdks/typescript/.*\.(js|ts|jsx|tsx|json)$ + stages: [pre-push] + pass_filenames: false + + # TypeScript type checking with bun + - id: typescript-typecheck + name: TypeScript type check + entry: bash -c 'if [ -f "sdks/typescript/package.json" ]; then cd sdks/typescript && bun run typecheck; fi' + language: system + files: ^sdks/typescript/.*\.(ts|tsx)$ + pass_filenames: false + + # Optional: Coverage check for main app only + - id: coverage-main + name: coverage check (main app) + entry: bash -c 'uv run coverage run -m pytest tests/ && uv run coverage report --fail-under=80' + language: system + files: ^(src/|tests/).*\.py$ + stages: [pre-push] + pass_filenames: false + + # License header check for Python files + - id: check-license-header + name: check license headers + entry: bash -c 'for f in "$@"; do if [[ "$f" =~ \.(py)$ ]] && ! grep -q "Copyright" "$f"; then echo "Missing license header in $f"; exit 1; fi; done' -- + language: system + files: ^(src/|sdks/python/src/).*\.py$ + pass_filenames: true + + # Documentation linting + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.37.0 + hooks: + - id: markdownlint + args: ['--fix'] + files: \.(md|mdx)$ + + # Commit message linting + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.13.0 + hooks: + - id: commitizen + stages: [commit-msg] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4315678..9e405212 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,11 +18,14 @@ Before you start contributing, please: 1. Fork the repository on GitHub 2. Clone your fork locally: + ```bash git clone https://github.com/YOUR_USERNAME/honcho.git cd honcho ``` + 3. Add the upstream repository as a remote: + ```bash git remote add upstream https://github.com/plastic-labs/honcho.git ``` diff --git a/Dockerfile b/Dockerfile index 0e72129a..02340993 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,4 +44,3 @@ EXPOSE 8000 # https://stackoverflow.com/questions/29663459/python-app-does-not-print-anything-when-running-detached-in-docker CMD ["fastapi", "run", "--host", "0.0.0.0", "src/main.py"] - diff --git a/README.md b/README.md index e4d44187..b5ec684b 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,58 @@ This is a development server that will reload whenever code is changed. When first launching the API with a connection to the database it will provision the necessary tables for Honcho to operate. +### Pre-commit Hooks + +Honcho uses pre-commit hooks to ensure code quality and consistency across the project. These hooks automatically run checks on your code before each commit, including linting, formatting, type checking, and security scans. + +#### Installation + +To set up pre-commit hooks in your development environment: + +1. **Install pre-commit using uv** + +```bash +uv add --dev pre-commit +``` + +2. **Install the pre-commit hooks** + +```bash +uv run pre-commit install \ + --hook-type pre-commit \ + --hook-type commit-msg \ + --hook-type pre-push +``` + +This will install hooks for `pre-commit`, `commit-msg`, and `pre-push` stages. + +#### What the hooks do + +The pre-commit configuration includes: + +- **Code Quality**: Python linting and formatting (ruff), TypeScript linting (biome) +- **Type Checking**: Static type analysis with basedpyright +- **Security**: Vulnerability scanning with bandit +- **Documentation**: Markdown linting and license header checks +- **Testing**: Automated test runs for Python and TypeScript code +- **File Hygiene**: Trailing whitespace, line endings, file size checks +- **Commit Standards**: Conventional commit message validation + +#### Manual execution + +You can run the hooks manually on all files without making a commit: + +```bash +uv run pre-commit run --all-files +``` + +Or run specific hooks: + +```bash +uv run pre-commit run ruff --all-files +uv run pre-commit run basedpyright --all-files +``` + ### Docker As mentioned earlier a `docker-compose` template is included for running Honcho. diff --git a/config.toml.example b/config.toml.example index 4548de06..811bb75c 100644 --- a/config.toml.example +++ b/config.toml.example @@ -45,7 +45,7 @@ PROFILES_SAMPLE_RATE = 0.1 [llm] DEFAULT_MAX_TOKENS = 2500 -# API Keys for LLM providers +# API Keys for LLM providers # ANTHROPIC_API_KEY = "your-api-key" # OPENAI_API_KEY = "your-api-key" # OPENAI_COMPATIBLE_API_KEY = "your-api-key" diff --git a/docs/README.md b/docs/README.md index 70609f46..7ad6b192 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,18 +5,21 @@ These docs are built using Next.js via mintlify. ## Setting Up Honcho's Docs Locally 1. Clone the repository: + ``` git clone git@github.com:plastic-labs/honcho.git ``` 2. Navigate into the `docs` folder: + ``` cd honcho/docs/ ``` + The docs folder contains the markdown files that make up the documentation. The majority of the files are in the pages directory. Some notable files in this folder include: - 3. Verify that you have Node.js and npm installed in your system. You can check by running: + ``` node --version npm --version @@ -25,18 +28,21 @@ npm --version 4. If not installed, download Node.js and npm from the respective official websites. 5. Once you have Node.js and npm running, proceed to install `pnpm` - another package manager that helps to manage project dependencies: + ``` npm install -g pnpm ``` 6. Install the project dependencies using pnpm: + ``` pnpm i ``` 7. After the successful installation of the project dependencies, start the local server: + ``` pnpm dev ``` -Now, you should be able to view the docs on your local environment by visiting `http://localhost:3000`. You can explore the different markdown files and make changes as you see fit. \ No newline at end of file +Now, you should be able to view the docs on your local environment by visiting `http://localhost:3000`. You can explore the different markdown files and make changes as you see fit. diff --git a/docs/changelog/compatibility-guide.mdx b/docs/changelog/compatibility-guide.mdx index 1dab943a..44f044b6 100644 --- a/docs/changelog/compatibility-guide.mdx +++ b/docs/changelog/compatibility-guide.mdx @@ -13,13 +13,13 @@ This guide helps you understand which versions of Honcho's API are compatible wi **Compatible Version:** v1.2.1 - + Install with: ```bash npm install @honcho-ai/sdk@1.2.1 ``` - + **Compatible Version:** v1.2.2 diff --git a/docs/changelog/introduction.mdx b/docs/changelog/introduction.mdx index bc721157..2a248166 100644 --- a/docs/changelog/introduction.mdx +++ b/docs/changelog/introduction.mdx @@ -64,7 +64,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t - Migration/provision scripts did not have correct database connection arguments, causing timeouts - + ### Fixed @@ -201,7 +201,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t - Get/poll deriver queue status endpoints added to workspace - Added endpoint to upload files as messages - + ### Removed - Removed peer messages in accordance with Honcho 2.1.0 @@ -222,7 +222,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t [TypeScript SDK](https://www.npmjs.com/package/@honcho-ai/sdk) ### Added - + - linting via Biome - Adding filter parameter to various endpoints @@ -258,4 +258,4 @@ Welcome to the Honcho changelog! This section documents all notable changes to t If you encounter issues using the Honcho API or its SDKs: 1. Open an issue on [GitHub](https://github.com/plastic-labs/honcho/issues) -2. Join our [Discord community](http://discord.gg/plasticlabs) for support +2. Join our [Discord community](http://discord.gg/plasticlabs) for support diff --git a/docs/logo/honcho-dark.svg b/docs/logo/honcho-dark.svg index 5e5244dd..6e4f7af4 100644 --- a/docs/logo/honcho-dark.svg +++ b/docs/logo/honcho-dark.svg @@ -34,4 +34,4 @@ - \ No newline at end of file + diff --git a/docs/logo/honcho-light.svg b/docs/logo/honcho-light.svg index db7d3d4a..54344fee 100644 --- a/docs/logo/honcho-light.svg +++ b/docs/logo/honcho-light.svg @@ -27,4 +27,4 @@ - \ No newline at end of file + diff --git a/docs/v1/README.md b/docs/v1/README.md index 59d1aa49..5a017ff8 100644 --- a/docs/v1/README.md +++ b/docs/v1/README.md @@ -1 +1 @@ -This subdirectory contains the app/user/session paradigm documentation for Honcho (Honcho v1.1.0). \ No newline at end of file +This subdirectory contains the app/user/session paradigm documentation for Honcho (Honcho v1.1.0). diff --git a/docs/v1/api-reference/endpoint/apps/create-app.mdx b/docs/v1/api-reference/endpoint/apps/create-app.mdx index f557617b..5eeb2a38 100644 --- a/docs/v1/api-reference/endpoint/apps/create-app.mdx +++ b/docs/v1/api-reference/endpoint/apps/create-app.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/apps/get-all-apps.mdx b/docs/v1/api-reference/endpoint/apps/get-all-apps.mdx index 52b4472f..3b033fa6 100644 --- a/docs/v1/api-reference/endpoint/apps/get-all-apps.mdx +++ b/docs/v1/api-reference/endpoint/apps/get-all-apps.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/list ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/apps/get-app-by-name.mdx b/docs/v1/api-reference/endpoint/apps/get-app-by-name.mdx index 97cdd25e..919b9f95 100644 --- a/docs/v1/api-reference/endpoint/apps/get-app-by-name.mdx +++ b/docs/v1/api-reference/endpoint/apps/get-app-by-name.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/name/{name} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/apps/get-or-create-app.mdx b/docs/v1/api-reference/endpoint/apps/get-or-create-app.mdx index ff43616e..86c17f0a 100644 --- a/docs/v1/api-reference/endpoint/apps/get-or-create-app.mdx +++ b/docs/v1/api-reference/endpoint/apps/get-or-create-app.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/get_or_create/{name} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/apps/update-app.mdx b/docs/v1/api-reference/endpoint/apps/update-app.mdx index b2e718c4..9b2b9cb8 100644 --- a/docs/v1/api-reference/endpoint/apps/update-app.mdx +++ b/docs/v1/api-reference/endpoint/apps/update-app.mdx @@ -1,3 +1,3 @@ --- openapi: put /v1/apps/{app_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/collections/create-collection.mdx b/docs/v1/api-reference/endpoint/collections/create-collection.mdx index ebb14c42..97619f5b 100644 --- a/docs/v1/api-reference/endpoint/collections/create-collection.mdx +++ b/docs/v1/api-reference/endpoint/collections/create-collection.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/collections ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/collections/delete-collection.mdx b/docs/v1/api-reference/endpoint/collections/delete-collection.mdx index 7a059489..a94c3f16 100644 --- a/docs/v1/api-reference/endpoint/collections/delete-collection.mdx +++ b/docs/v1/api-reference/endpoint/collections/delete-collection.mdx @@ -1,3 +1,3 @@ --- openapi: delete /v1/apps/{app_id}/users/{user_id}/collections/{collection_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/collections/get-collection-by-name.mdx b/docs/v1/api-reference/endpoint/collections/get-collection-by-name.mdx index 54dc1f92..fc9445e7 100644 --- a/docs/v1/api-reference/endpoint/collections/get-collection-by-name.mdx +++ b/docs/v1/api-reference/endpoint/collections/get-collection-by-name.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/{user_id}/collections/name/{name} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/collections/get-collection.mdx b/docs/v1/api-reference/endpoint/collections/get-collection.mdx index 014ecae9..4383b307 100644 --- a/docs/v1/api-reference/endpoint/collections/get-collection.mdx +++ b/docs/v1/api-reference/endpoint/collections/get-collection.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/{user_id}/collections ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/collections/get-collections.mdx b/docs/v1/api-reference/endpoint/collections/get-collections.mdx index 9b6e96cf..6928b518 100644 --- a/docs/v1/api-reference/endpoint/collections/get-collections.mdx +++ b/docs/v1/api-reference/endpoint/collections/get-collections.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/collections/list ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/collections/update-collection.mdx b/docs/v1/api-reference/endpoint/collections/update-collection.mdx index 56560e7e..3b7978cd 100644 --- a/docs/v1/api-reference/endpoint/collections/update-collection.mdx +++ b/docs/v1/api-reference/endpoint/collections/update-collection.mdx @@ -1,3 +1,3 @@ --- openapi: put /v1/apps/{app_id}/users/{user_id}/collections/{collection_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/documents/create-document.mdx b/docs/v1/api-reference/endpoint/documents/create-document.mdx index 55fa7cc5..069225de 100644 --- a/docs/v1/api-reference/endpoint/documents/create-document.mdx +++ b/docs/v1/api-reference/endpoint/documents/create-document.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/documents/delete-document.mdx b/docs/v1/api-reference/endpoint/documents/delete-document.mdx index c5b3bbba..cd311c94 100644 --- a/docs/v1/api-reference/endpoint/documents/delete-document.mdx +++ b/docs/v1/api-reference/endpoint/documents/delete-document.mdx @@ -1,3 +1,3 @@ --- openapi: delete /v1/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/documents/get-document.mdx b/docs/v1/api-reference/endpoint/documents/get-document.mdx index a073650b..09c6c4a7 100644 --- a/docs/v1/api-reference/endpoint/documents/get-document.mdx +++ b/docs/v1/api-reference/endpoint/documents/get-document.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/documents/get-documents.mdx b/docs/v1/api-reference/endpoint/documents/get-documents.mdx index 4ae4ef73..bbaa3992 100644 --- a/docs/v1/api-reference/endpoint/documents/get-documents.mdx +++ b/docs/v1/api-reference/endpoint/documents/get-documents.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/list ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/documents/update-document.mdx b/docs/v1/api-reference/endpoint/documents/update-document.mdx index 9e2fb1fa..9e82a822 100644 --- a/docs/v1/api-reference/endpoint/documents/update-document.mdx +++ b/docs/v1/api-reference/endpoint/documents/update-document.mdx @@ -1,3 +1,3 @@ --- openapi: put /v1/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents/{document_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/keys/create-key.mdx b/docs/v1/api-reference/endpoint/keys/create-key.mdx index 6a522ffb..3f0b4753 100644 --- a/docs/v1/api-reference/endpoint/keys/create-key.mdx +++ b/docs/v1/api-reference/endpoint/keys/create-key.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/keys ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/messages/create-batch-messages-for-session.mdx b/docs/v1/api-reference/endpoint/messages/create-batch-messages-for-session.mdx index b747040f..56f1ac68 100644 --- a/docs/v1/api-reference/endpoint/messages/create-batch-messages-for-session.mdx +++ b/docs/v1/api-reference/endpoint/messages/create-batch-messages-for-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages/batch ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/messages/create-message-for-session.mdx b/docs/v1/api-reference/endpoint/messages/create-message-for-session.mdx index 29581f9a..a3e82371 100644 --- a/docs/v1/api-reference/endpoint/messages/create-message-for-session.mdx +++ b/docs/v1/api-reference/endpoint/messages/create-message-for-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/messages/get-message.mdx b/docs/v1/api-reference/endpoint/messages/get-message.mdx index 519fbecc..59c2f386 100644 --- a/docs/v1/api-reference/endpoint/messages/get-message.mdx +++ b/docs/v1/api-reference/endpoint/messages/get-message.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages/{message_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/messages/get-messages.mdx b/docs/v1/api-reference/endpoint/messages/get-messages.mdx index 1f6001f4..ffbda8e8 100644 --- a/docs/v1/api-reference/endpoint/messages/get-messages.mdx +++ b/docs/v1/api-reference/endpoint/messages/get-messages.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages/list ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/messages/update-message.mdx b/docs/v1/api-reference/endpoint/messages/update-message.mdx index ed44d5e0..227d3413 100644 --- a/docs/v1/api-reference/endpoint/messages/update-message.mdx +++ b/docs/v1/api-reference/endpoint/messages/update-message.mdx @@ -1,3 +1,3 @@ --- openapi: put /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages/{message_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/chat.mdx b/docs/v1/api-reference/endpoint/sessions/chat.mdx index da1520af..d3ac1044 100644 --- a/docs/v1/api-reference/endpoint/sessions/chat.mdx +++ b/docs/v1/api-reference/endpoint/sessions/chat.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/chat ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/clone-session.mdx b/docs/v1/api-reference/endpoint/sessions/clone-session.mdx index 871d9951..2c1caa71 100644 --- a/docs/v1/api-reference/endpoint/sessions/clone-session.mdx +++ b/docs/v1/api-reference/endpoint/sessions/clone-session.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/clone ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/create-session.mdx b/docs/v1/api-reference/endpoint/sessions/create-session.mdx index 455f8e6b..33efd338 100644 --- a/docs/v1/api-reference/endpoint/sessions/create-session.mdx +++ b/docs/v1/api-reference/endpoint/sessions/create-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/delete-session.mdx b/docs/v1/api-reference/endpoint/sessions/delete-session.mdx index f9c3480a..05904688 100644 --- a/docs/v1/api-reference/endpoint/sessions/delete-session.mdx +++ b/docs/v1/api-reference/endpoint/sessions/delete-session.mdx @@ -1,3 +1,3 @@ --- openapi: delete /v1/apps/{app_id}/users/{user_id}/sessions/{session_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/get-chat-stream.mdx b/docs/v1/api-reference/endpoint/sessions/get-chat-stream.mdx index 4963b362..6b4cd59e 100644 --- a/docs/v1/api-reference/endpoint/sessions/get-chat-stream.mdx +++ b/docs/v1/api-reference/endpoint/sessions/get-chat-stream.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions/{session_id}/chat/stream ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/get-sessions.mdx b/docs/v1/api-reference/endpoint/sessions/get-sessions.mdx index e5ca7307..db500a7d 100644 --- a/docs/v1/api-reference/endpoint/sessions/get-sessions.mdx +++ b/docs/v1/api-reference/endpoint/sessions/get-sessions.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/{user_id}/sessions/list ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/sessions/update-session.mdx b/docs/v1/api-reference/endpoint/sessions/update-session.mdx index ca747d47..b88adf84 100644 --- a/docs/v1/api-reference/endpoint/sessions/update-session.mdx +++ b/docs/v1/api-reference/endpoint/sessions/update-session.mdx @@ -1,3 +1,3 @@ --- openapi: put /v1/apps/{app_id}/users/{user_id}/sessions/{session_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/users/create-user.mdx b/docs/v1/api-reference/endpoint/users/create-user.mdx index e98510ac..4d8ea33b 100644 --- a/docs/v1/api-reference/endpoint/users/create-user.mdx +++ b/docs/v1/api-reference/endpoint/users/create-user.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/users/get-or-create-user.mdx b/docs/v1/api-reference/endpoint/users/get-or-create-user.mdx index 0dd11ce4..d7820d88 100644 --- a/docs/v1/api-reference/endpoint/users/get-or-create-user.mdx +++ b/docs/v1/api-reference/endpoint/users/get-or-create-user.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/get_or_create/{name} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/users/get-user-by-name.mdx b/docs/v1/api-reference/endpoint/users/get-user-by-name.mdx index aa9ad808..72a525ec 100644 --- a/docs/v1/api-reference/endpoint/users/get-user-by-name.mdx +++ b/docs/v1/api-reference/endpoint/users/get-user-by-name.mdx @@ -1,3 +1,3 @@ --- openapi: get /v1/apps/{app_id}/users/name/{name} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/users/get-users.mdx b/docs/v1/api-reference/endpoint/users/get-users.mdx index e75216ad..e8a18671 100644 --- a/docs/v1/api-reference/endpoint/users/get-users.mdx +++ b/docs/v1/api-reference/endpoint/users/get-users.mdx @@ -1,3 +1,3 @@ --- openapi: post /v1/apps/{app_id}/users/list ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/endpoint/users/update-user.mdx b/docs/v1/api-reference/endpoint/users/update-user.mdx index 720a6137..872f63d3 100644 --- a/docs/v1/api-reference/endpoint/users/update-user.mdx +++ b/docs/v1/api-reference/endpoint/users/update-user.mdx @@ -1,3 +1,3 @@ --- openapi: put /v1/apps/{app_id}/users/{user_id} ---- \ No newline at end of file +--- diff --git a/docs/v1/api-reference/introduction.mdx b/docs/v1/api-reference/introduction.mdx index 2ded7a6e..582b79a7 100644 --- a/docs/v1/api-reference/introduction.mdx +++ b/docs/v1/api-reference/introduction.mdx @@ -2,12 +2,12 @@ title: 'Introduction' --- -This section of the documentation goes over all of the different API endpoints available in the Honcho +This section of the documentation covers the different API endpoints available in the Honcho Server. They largely map to CRUD operations for each of the core primitives. For information about the core primitives consult [Architecture](/v1/getting-started/architecture) - This part of the documentation is autogenerated for the most up to date and accurate API spec view the + This part of the documentation is autogenerated. For the most up-to-date and accurate API spec view the [Redoc](https://demo.honcho.dev/redoc) or [Swagger](https://demo.honcho.dev/docs) directly. diff --git a/docs/v1/contributing/error-handling.mdx b/docs/v1/contributing/error-handling.mdx index 8ac2e55d..c289a05e 100644 --- a/docs/v1/contributing/error-handling.mdx +++ b/docs/v1/contributing/error-handling.mdx @@ -18,23 +18,23 @@ Honcho defines custom exception types in `src/exceptions.py`: class HonchoException(Exception): status_code = 500 # Default status code detail = "An unexpected error occurred" # Default message - + class ResourceNotFoundException(HonchoException): status_code = 404 detail = "Resource not found" - + class ValidationException(HonchoException): status_code = 422 detail = "Validation error" - + class ConflictException(HonchoException): - status_code = 409 + status_code = 409 detail = "Resource conflict" - + class AuthenticationException(HonchoException): status_code = 401 detail = "Authentication failed" - + class AuthorizationException(HonchoException): status_code = 403 detail = "Not authorized to access this resource" @@ -107,7 +107,7 @@ try: logger.info(f"Successfully processed message {message_id}") except Exception as e: logger.error( - f"Error processing message {message_id}: {str(e)}", + f"Error processing message {message_id}: {str(e)}", exc_info=True ) if os.getenv("SENTRY_ENABLED", "False").lower() == "true": @@ -124,7 +124,7 @@ except Exception as e: 2. **Include context in logs**: ```python logger.error( - f"Failed to process message for app {app_id}, user {user_id}", + f"Failed to process message for app {app_id}, user {user_id}", exc_info=True ) ``` @@ -155,16 +155,16 @@ if os.getenv("SENTRY_ENABLED", "False").lower() == "true": async def update_document(db, collection_id, document_id, document): """ Update a document. - + Args: db: Database session collection_id: ID of the collection document_id: ID of the document document: Document update schema - + Returns: The updated document - + Raises: ResourceNotFoundException: If the document or collection does not exist ValidationException: If the document data is invalid @@ -172,13 +172,13 @@ async def update_document(db, collection_id, document_id, document): try: # Get document (raises ResourceNotFoundException if not found) honcho_document = await get_document(db, collection_id, document_id) - + # Update document data if document.content is not None: honcho_document.content = document.content if document.metadata is not None: honcho_document.h_metadata = document.metadata - + await db.commit() logger.info(f"Document {document_id} updated successfully") return honcho_document @@ -194,9 +194,9 @@ async def update_document(db, collection_id, document_id, document): @router.get("/{document_id}") async def get_document( app_id: str, - user_id: str, - collection_id: str, - document_id: str, + user_id: str, + collection_id: str, + document_id: str, db: AsyncSession = db ): """Get a document by ID""" @@ -216,7 +216,7 @@ async def process_item(db, payload): if field not in payload: logger.error(f"Missing required field in payload: {field}") raise ValidationException(f"Missing field: {field}") - + # Process the item await do_processing(db, payload) logger.info(f"Processed message {payload['message_id']}") @@ -225,4 +225,4 @@ async def process_item(db, payload): if os.getenv("SENTRY_ENABLED", "False").lower() == "true": sentry_sdk.capture_exception(e) raise -``` \ No newline at end of file +``` diff --git a/docs/v1/contributing/guidelines.mdx b/docs/v1/contributing/guidelines.mdx index 5d8b1b7b..70bff6e5 100644 --- a/docs/v1/contributing/guidelines.mdx +++ b/docs/v1/contributing/guidelines.mdx @@ -7,13 +7,13 @@ icon: 'handshake-angle' This project is completely open source and welcomes any and all open source contributions. The workflow for contributing is to make a fork of the repository. You can claim an issue in the issues tab or start a new thread to -indicate a feature or bug fix you are working on. +indicate a feature or bug fix you are working on. Once you have finished your contribution make a PR , and it will be reviewed by a project manager. Feel free to join us in our [discord](http://discord.gg/plasticlabs) to discuss your changes or get help. Your changes will undergo a period of testing and discussion before finally -being entered into the `main` branch and being staged for release. For more +being entered into the `main` branch and being staged for release. For more details, check out our [contributing](https://github.com/plastic-labs/honcho/blob/main/CONTRIBUTING.md) document. diff --git a/docs/v1/contributing/license.mdx b/docs/v1/contributing/license.mdx index a53e2d7a..6855347e 100644 --- a/docs/v1/contributing/license.mdx +++ b/docs/v1/contributing/license.mdx @@ -3,7 +3,7 @@ title: 'License' icon: 'scroll' --- -Honcho is licensed under the AGPL-3.0 License. This is copied below for convenience and also present in the +Honcho is licensed under the AGPL-3.0 License. This is copied below for convenience and also present in the [GitHub Repository](https://github.com/plastic-labs/honcho) ``` @@ -668,4 +668,4 @@ specific requirements. if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . -``` \ No newline at end of file +``` diff --git a/docs/v1/getting-started/architecture.mdx b/docs/v1/getting-started/architecture.mdx index dc2169a4..fe3e7676 100644 --- a/docs/v1/getting-started/architecture.mdx +++ b/docs/v1/getting-started/architecture.mdx @@ -4,26 +4,26 @@ description: 'Learn the core primitives and the structure of Honcho' icon: 'building' --- -Honcho is a user context management system for AI powered applications. -The storage concepts are inspired by, but not a 1:1 mapping of, the OpenAI -Assistants API. The insights concepts are inspired by cognitive science, +Honcho is a user context management system for AI-powered applications. +The storage concepts are inspired by, but not a 1:1 mapping of, the OpenAI +Assistants API. The insights concepts are inspired by cognitive science, philosophy, and machine learning. Honcho is open source. We believe trust and transparency are vital for developing AI technology. We're also focused on using and supporting existing tools rather than developing from scratch. -We focus on flexible, user-centric storage primitives to promote community -exploration of novel memory frameworks and the usage of the -[Dialectic API](https://blog.plasticlabs.ai/blog/Introducing-Honcho's-Dialectic-API) -to support them. Language models are highly capable of modeling human psychology. -By building a data management framework that is user-centric, we aim to address -not only practical application development issues (like scaling, statefulness, -etc.) but also kickstart exploration of the design space of what's possible -given access to rich user models. You can read more about Honcho's origin, +We focus on flexible, user-centric storage primitives to promote community +exploration of novel memory frameworks and the usage of the +[Dialectic API](https://blog.plasticlabs.ai/blog/Introducing-Honcho's-Dialectic-API) +to support them. Language models are highly capable of modeling human psychology. +By building a data management framework that is user-centric, we aim to address +not only practical application development issues (like scaling, statefulness, +etc.) but also kickstart exploration of the design space of what's possible +given access to rich user models. You can read more about Honcho's origin, inspiration and philosophy on our [blog](https://blog.plasticlabs.ai). -## Core Primitives +## Core Primitives Using Honcho has the following flow: 1. Initialize your `Honcho` instance and `App` @@ -53,7 +53,7 @@ erDiagram datetime created_at jsonb h_metadata "metadata" } - + User { BigInteger id PK string public_id @@ -62,7 +62,7 @@ erDiagram datetime created_at string app_id FK } - + Session { BigInteger id PK string public_id @@ -71,7 +71,7 @@ erDiagram datetime created_at string user_id FK } - + Message { BigInteger id PK string public_id @@ -81,7 +81,7 @@ erDiagram jsonb h_metadata "metadata" datetime created_at } - + Metamessage { BigInteger id PK string public_id @@ -93,7 +93,7 @@ erDiagram datetime created_at jsonb h_metadata "metadata" } - + Collection { BigInteger id PK string public_id @@ -102,7 +102,7 @@ erDiagram jsonb h_metadata "metadata" string user_id FK } - + Document { BigInteger id PK string public_id @@ -121,30 +121,30 @@ An `App` is the highest-level primitive in Honcho. It is the scope that all of y ### Users The `User` object is the main interface for managing a User's context. With it -you can interface with the `User`'s `Session`s and `Collections`s directly. +you can interface with the `User`'s `Session`s and `Collections`s directly. -### Sessions +### Sessions The `Session` object is useful for organizing your interactions with `User`s. Different `User`s can have different sessions enabling you to neatly segment user context. It also accepts a `location_id` parameter which can specifically -denote *where* users' sessions are taking place. +denote *where* users' sessions are taking place. -### Messages +### Messages Sessions are made up of `Message` objects. You can append them to sessions. This is pretty straightforward. ### Metamessages -Success in LLM applications is dependent on elegant context management, so we -provide a `Metamessage` object for flexible context storage and construction. Each -`Metamessage` is tied to a `User` object via the required `user_id` argument. Keeping -this separate from the core user-assistant message history ensures the +Success in LLM applications is dependent on elegant context management, so we +provide a `Metamessage` object for flexible context storage and construction. Each +`Metamessage` is tied to a `User` object via the required `user_id` argument. Keeping +this separate from the core user-assistant message history ensures the insights service running ambiently is doing so on authentic ground truth -We've found this particularly useful for storing intermediate inferences, -constructing very specific chat histories, and more. Metamessages can optionally be -attached to sessions and/or messages, so constructing historical context for inference is +We've found this particularly useful for storing intermediate inferences, +constructing very specific chat histories, and more. Metamessages can optionally be +attached to sessions and/or messages, so constructing historical context for inference is as easy as possible. ### Collections diff --git a/docs/v1/getting-started/development.mdx b/docs/v1/getting-started/development.mdx index b54faf5b..59d3f63a 100644 --- a/docs/v1/getting-started/development.mdx +++ b/docs/v1/getting-started/development.mdx @@ -95,4 +95,4 @@ Repeat step 3. -Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/introduction) \ No newline at end of file +Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/introduction) diff --git a/docs/v1/guides/dialectic-endpoint.mdx b/docs/v1/guides/dialectic-endpoint.mdx index 5d86d6d5..39dd7982 100644 --- a/docs/v1/guides/dialectic-endpoint.mdx +++ b/docs/v1/guides/dialectic-endpoint.mdx @@ -4,17 +4,17 @@ description: "An endpoint for reasoning about your users" icon: "comments" --- -Honcho by default runs ambient inference on top of the `message` objects you store. Those messages serve as the ground truth upon which facts about the user are derived and stored. The **Dialectic Endpoint** is the natural language interface through which insights are synthesized from those facts. We believe [intellectual respect](https://blog.plasticlabs.ai/extrusions/Extrusion-02.24) for LLMs is paramount in building effective AI agents/apps. It follows that the LLM should know better than any human what would aid them in their generation task. Thus, the Dialectic endpoint exists for flexible agent-to-agent communication. +Honcho by default runs ambient inference on top of the `message` objects you store. Those messages serve as the ground truth upon which facts about the user are derived and stored. The **Dialectic Endpoint** is the natural language interface through which insights are synthesized from those facts. We believe [intellectual respect](https://blog.plasticlabs.ai/extrusions/Extrusion-02.24) for LLMs is paramount in building effective AI agents/apps. It follows that the LLM should know better than any human what would aid them in their generation task. Thus, the Dialectic endpoint exists for flexible agent-to-agent communication. ## Automatic Fact Derivation -On every message written to a session, an automatic callback is run that will reason about the conversation and store facts in a `collection` named `honcho`. This is a reserved `collection` specifically for the backend Honcho agent to interact with. +On every message written to a session, an automatic callback is run that will reason about the conversation and store facts in a `collection` named `honcho`. This is a reserved `collection` specifically for the backend Honcho agent to interact with. ## Dialectic Endpoint -You can query the automatically derived facts in the `honcho` collection directly, or you can offload this task to our agent and use the Dialectic endpoint. This endpoint allows you to define logic enabling your agent to talk to our agent that automatically retrieves and synthesizes facts from the collection. +You can query the automatically derived facts in the `honcho` collection directly, or you can offload this task to our agent and use the Dialectic endpoint. This endpoint allows you to define logic enabling your agent to talk to our agent that automatically retrieves and synthesizes facts from the collection. -This chat interface is exposed via the `chat` endpoint. It accepts a string or a list of strings. Below is some example code on how this works. +This chat interface is exposed via the `chat` endpoint. It accepts a string or a list of strings. Below is some example code on how this works. ## Prerequisites @@ -53,7 +53,7 @@ const session = await honcho.apps.users.sessions.create(app.id, user.id, {}); // (assuming some messages have been written to Honcho for the deriver to use) ``` -## Static Dialectic Call +## Static Dialectic Call ```python Python @@ -91,6 +91,3 @@ with honcho.apps.users.sessions.with_streaming_response.stream( We've designed the Dialectic endpoint to be infinitely flexible. We wrote an incomplete list of ideas on how to use it on our blog [here](https://blog.plasticlabs.ai/blog/Introducing-Honcho's-Dialectic-API#how-it-works). - - - diff --git a/docs/v1/guides/discord.mdx b/docs/v1/guides/discord.mdx index 939f5441..f9bfdb9c 100644 --- a/docs/v1/guides/discord.mdx +++ b/docs/v1/guides/discord.mdx @@ -11,7 +11,7 @@ Any application interface that defines logic based on events and supports special commands can work easily with Honcho. Here's how to use Honcho with **Discord** as an interface. If you're not familiar with Discord bot application logic, the [py-cord](https://pycord.dev/) docs would be a good -place to start. +place to start. ## Events @@ -113,7 +113,7 @@ is_reply_to_bot = ( is_mention = bot.user.mentioned_in(message) ``` -These lines check what kind of message is being sent in Discord, which is a useful condition to check before entering the reply logic. The code inside that if-statement is commented quite well, so we'll just go over the relevant Honcho parts. +These lines check what kind of message is being sent in Discord, which is a useful condition to check before entering the reply logic. The code inside that if-statement is commented quite well, so we'll just go over the relevant Honcho parts. ```python # Get a user object for the message author @@ -121,7 +121,7 @@ user_id = f"discord_{str(message.author.id)}" user = honcho.apps.users.get_or_create(name=user_id, app_id=app.id) ``` -Here we're getting or creating a user for an app that's been defined at the top of the file. +Here we're getting or creating a user for an app that's been defined at the top of the file. ```python # Use the channel ID as the location_id (for DMs, this will be unique to the user) @@ -151,7 +151,7 @@ The first helper function we create is called `get_session`. This simplifies a l def get_session(user_id, location_id, create=False): # Get an existing session for the user and location or optionally create a new one if none exists. # Returns a tuple of (session, is_new) where is_new indicates if a new session was created. - + # Query for *active* sessions with both user_id and location_id sessions_iter = honcho.apps.users.sessions.list( app_id=app.id, user_id=user_id, reverse=True, filter={"is_active": True} @@ -246,7 +246,7 @@ async def restart(ctx): await ctx.respond(msg) ``` -This slash command restarts a conversation with a bot. In Honcho, the `delete` method marks a session's `is_active` field to `False`. +This slash command restarts a conversation with a bot. In Honcho, the `delete` method marks a session's `is_active` field to `False`. ## Recap @@ -257,4 +257,3 @@ How you use Honcho is tightly coupled with the client you're building in. Here, - how to sort and filter when calling list methods You are well on your way to becoming a context construction master! Stay tuned for more in-depth examples. If you want a challenge, try deciphering how we construct context in one of our apps, [Bloom](https://github.com/plastic-labs/tutor-gpt/blob/main/app/Chat.tsx). - diff --git a/docs/v1/guides/honcho-mcp.mdx b/docs/v1/guides/honcho-mcp.mdx index 56a45db8..7647b6cb 100644 --- a/docs/v1/guides/honcho-mcp.mdx +++ b/docs/v1/guides/honcho-mcp.mdx @@ -22,7 +22,7 @@ cd honcho-mcp 3. Sync the virtual environment. This package uses [uv](https://docs.astral.sh/uv/), [install](https://docs.astral.sh/uv/#installation) if you haven't. ``` -uv sync +uv sync ``` 4. In Claude Desktop, go to the *top left Mac Toolbar* Settings > Developer and click "Edit Config" @@ -49,7 +49,7 @@ uv sync You probably will need to put the full path to the uv executable in the command field. You can get this by running `which uv` on MacOS/Linux or `where uv` on Windows. -6. Restart the Claude Desktop app. Upon relaunch it should start Honcho and the tools should be available! +6. Restart the Claude Desktop app. Upon relaunch it should start Honcho and the tools should be available! Just note that, by default, the MCP server is set up to use the Honcho Demo server, which only persists data for 7 days. If you're using the hosted version of Honcho, copy the `.env.template` to a proper `.env` file and update the URL and API key variables accordingly. @@ -59,4 +59,4 @@ Finally, Claude needs instructions on how to use Honcho. The Desktop app doesn't Be sure to update the \ and \ variables in the instructions.txt file. -Claude should then query for insights before responding and write your messages to storage! If you come up with more creative ways to get Claude to manage its own memory with Honcho, feel free to [let us know](https://discord.gg/plasticlabs) or make a PR on this [repo](https://github.com/plastic-labs/honcho-mcp/tree/main)! \ No newline at end of file +Claude should then query for insights before responding and write your messages to storage! If you come up with more creative ways to get Claude to manage its own memory with Honcho, feel free to [let us know](https://discord.gg/plasticlabs) or make a PR on this [repo](https://github.com/plastic-labs/honcho-mcp/tree/main)! diff --git a/docs/v1/guides/overview.mdx b/docs/v1/guides/overview.mdx index 176bdef8..b3633b84 100644 --- a/docs/v1/guides/overview.mdx +++ b/docs/v1/guides/overview.mdx @@ -3,4 +3,4 @@ title: "Spellbooks and Tutorials" sidebarTitle: 'Overview' description: 'Helpful guides and design patterns for building with Honcho' icon: 'hat-wizard' ---- \ No newline at end of file +--- diff --git a/docs/v1/guides/simple-memory.mdx b/docs/v1/guides/simple-memory.mdx index c34ae7fb..23597a06 100644 --- a/docs/v1/guides/simple-memory.mdx +++ b/docs/v1/guides/simple-memory.mdx @@ -5,7 +5,7 @@ description: "A simple example of how to store and derive facts about individual --- This guide shows how to implement a simple user memory system that derives and stores facts about users that -are then referenced later on. +are then referenced later on. A fully working example can be found on [GitHub](https://github.com/plastic-labs/honcho-python/tree/main/examples/discord/fact-memory). It's setup as a discord bot so view our [Discord guide](./discord) @@ -50,7 +50,7 @@ For this part we will be leveraging LangChain and GPT-4 to derivce facts on the ```python def derive_facts(user_input): # Derive facts about the user - + fact_derivation = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate(prompt=prompt) ]) @@ -91,7 +91,7 @@ advantage of one of LangChain's built in output parsers for this. This is where Honcho comes into play. With Honcho we can initialize `Collections` for each user and can store facts as vector embeddings. You can use multiple collections if you want to segment different types of facts or -data, but for now we just need one. +data, but for now we just need one. ```python def store_facts(app_id, user_id, facts): @@ -102,7 +102,7 @@ def store_facts(app_id, user_id, facts): except NotFoundError as e: collection = honcho.apps.users.collections.create(app_id=app.id, user_id=user.id, name="discord") collection: Collection - + for fact in facts: # store each fact in the collection honcho.apps.users.collections.documents.create( app_id=app_id, user_id=user_id, collection_id=collection.id, content=fact @@ -120,7 +120,7 @@ The trick we use below is to have the LLM determine the query and then use it in ```python def introspect(chat_history, input): - + introspection_prompt = ChatPromptTemplate.from_messages([ system_introspection ]) @@ -166,11 +166,11 @@ _type: prompt input_variables: ["chat_history", "user_input"] template: > - Given the conversation history and user input, use your theory of mind skills to list out questions you'd like to know about the user in order to best respond to them. - + Given the conversation history and user input, use your theory of mind skills to list out questions you'd like to know about the user in order to best respond to them. + Chat history: ```{chat_history}``` User input: ```{user_input}``` - + Output the questions as a numbered list. ``` @@ -189,5 +189,5 @@ template: > ``` --- -This is a very simple method of using Honcho to hold user context. For further reading on the limits read about +This is a very simple method of using Honcho to hold user context. For further reading on the limits read about [violation of expectation](https://arxiv.org/abs/2310.06983). diff --git a/docs/v1/guides/streaming-response.mdx b/docs/v1/guides/streaming-response.mdx index 5ea9b38a..4193fea5 100644 --- a/docs/v1/guides/streaming-response.mdx +++ b/docs/v1/guides/streaming-response.mdx @@ -124,7 +124,7 @@ async def restaurant_recommendation_chat(): app = await honcho.apps.get_or_create(name="food-app") user = await honcho.apps.users.get_or_create(app_id=app.id, name="food-lover") session = await honcho.apps.users.sessions.create(app_id=app.id, user_id=user.id) - + # Store multiple user messages about food preferences user_messages = [ "I absolutely love spicy Thai food, especially curries with coconut milk.", @@ -132,7 +132,7 @@ async def restaurant_recommendation_chat(): "I try to eat vegetarian most of the time, but occasionally enjoy seafood.", "I can't handle overly sweet desserts, but love something with dark chocolate." ] - + # Store the user's messages in the session for message in user_messages: await honcho.apps.users.sessions.messages.create( @@ -143,12 +143,12 @@ async def restaurant_recommendation_chat(): is_user=True ) print(f"User: {message}") - + # Ask for restaurant recommendations based on preferences print("\nRequesting restaurant recommendations...") print("Assistant: ", end="", flush=True) full_response = "" - + # Stream the response with honcho.apps.users.sessions.with_streaming_response.stream( app_id=app.id, @@ -160,7 +160,7 @@ async def restaurant_recommendation_chat(): print(chunk, end="", flush=True) full_response += chunk await asyncio.sleep(0.01) - + # Store the assistant's complete response await honcho.apps.users.sessions.messages.create( app_id=app.id, @@ -183,7 +183,7 @@ async function restaurantRecommendationChat() { const app = await honcho.apps.getOrCreate('food-app'); const user = await honcho.apps.users.getOrCreate(app.id, 'food-lover'); const session = await honcho.apps.users.sessions.create(app.id, user.id, {}); - + // Store multiple user messages about food preferences const userMessages = [ "I absolutely love spicy Thai food, especially curries with coconut milk.", @@ -191,7 +191,7 @@ async function restaurantRecommendationChat() { "I try to eat vegetarian most of the time, but occasionally enjoy seafood.", "I can't handle overly sweet desserts, but love something with dark chocolate." ]; - + // Store the user's messages in the session for (const message of userMessages) { await honcho.apps.users.sessions.messages.create(app.id, user.id, session.id, { @@ -200,23 +200,23 @@ async function restaurantRecommendationChat() { }); console.log(`User: ${message}`); } - + // Ask for restaurant recommendations based on preferences console.log("\nRequesting restaurant recommendations..."); process.stdout.write("Assistant: "); let fullResponse = ""; - + // Stream the response const stream = await honcho.apps.users.sessions.chat(app.id, user.id, session.id, { queries: "Based on this user's food preferences, recommend 3 restaurants they might enjoy in the Lower East Side.", stream: true }); - + for await (const chunk of stream) { process.stdout.write(chunk); fullResponse += chunk; } - + // Store the assistant's complete response await honcho.apps.users.sessions.messages.create(app.id, user.id, session.id, { content: fullResponse, @@ -237,4 +237,4 @@ When implementing streaming: - Be mindful of memory usage when accumulating large responses - Use appropriate error handling for network interruptions -Streaming responses provide a more interactive and engaging user experience. By implementing streaming in your Honcho applications, you can create more responsive AI-powered features that feel natural and immediate to your users. \ No newline at end of file +Streaming responses provide a more interactive and engaging user experience. By implementing streaming in your Honcho applications, you can create more responsive AI-powered features that feel natural and immediate to your users. diff --git a/docs/v1/openapi.json b/docs/v1/openapi.json index 643f0151..bc67bae5 100644 --- a/docs/v1/openapi.json +++ b/docs/v1/openapi.json @@ -5169,4 +5169,4 @@ } } } -} \ No newline at end of file +} diff --git a/docs/v2/README.md b/docs/v2/README.md index 4bfc72a7..8939faa1 100644 --- a/docs/v2/README.md +++ b/docs/v2/README.md @@ -1 +1 @@ -This subdirectory contains the peer-paradigm documentation for Honcho (Honcho v2.0.0 onwards). \ No newline at end of file +This subdirectory contains the peer-paradigm documentation for Honcho (Honcho v2.0.0 onwards). diff --git a/docs/v2/api-reference/endpoint/keys/create-key.mdx b/docs/v2/api-reference/endpoint/keys/create-key.mdx index 2d685aed..41b759f7 100644 --- a/docs/v2/api-reference/endpoint/keys/create-key.mdx +++ b/docs/v2/api-reference/endpoint/keys/create-key.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/keys ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/messages/create-messages-for-session.mdx b/docs/v2/api-reference/endpoint/messages/create-messages-for-session.mdx index d651c335..19e9c9cb 100644 --- a/docs/v2/api-reference/endpoint/messages/create-messages-for-session.mdx +++ b/docs/v2/api-reference/endpoint/messages/create-messages-for-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/{session_id}/messages/ ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/messages/get-message.mdx b/docs/v2/api-reference/endpoint/messages/get-message.mdx index 53f88506..53765daa 100644 --- a/docs/v2/api-reference/endpoint/messages/get-message.mdx +++ b/docs/v2/api-reference/endpoint/messages/get-message.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/workspaces/{workspace_id}/sessions/{session_id}/messages/{message_id} ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/messages/get-messages.mdx b/docs/v2/api-reference/endpoint/messages/get-messages.mdx index 28150f33..612c7aab 100644 --- a/docs/v2/api-reference/endpoint/messages/get-messages.mdx +++ b/docs/v2/api-reference/endpoint/messages/get-messages.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/{session_id}/messages/list ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/messages/update-message.mdx b/docs/v2/api-reference/endpoint/messages/update-message.mdx index f019a410..77a296b2 100644 --- a/docs/v2/api-reference/endpoint/messages/update-message.mdx +++ b/docs/v2/api-reference/endpoint/messages/update-message.mdx @@ -1,3 +1,3 @@ --- openapi: put /v2/workspaces/{workspace_id}/sessions/{session_id}/messages/{message_id} ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/messages/upload-file.mdx b/docs/v2/api-reference/endpoint/messages/upload-file.mdx index bef4ed66..55ca55a8 100644 --- a/docs/v2/api-reference/endpoint/messages/upload-file.mdx +++ b/docs/v2/api-reference/endpoint/messages/upload-file.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/{session_id}/messages/upload ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/chat.mdx b/docs/v2/api-reference/endpoint/peers/chat.mdx index eac09dcc..8fe3cd18 100644 --- a/docs/v2/api-reference/endpoint/peers/chat.mdx +++ b/docs/v2/api-reference/endpoint/peers/chat.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/peers/{peer_id}/chat ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/get-or-create-peer.mdx b/docs/v2/api-reference/endpoint/peers/get-or-create-peer.mdx index 968cf4e9..724d8100 100644 --- a/docs/v2/api-reference/endpoint/peers/get-or-create-peer.mdx +++ b/docs/v2/api-reference/endpoint/peers/get-or-create-peer.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/peers ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/get-peers.mdx b/docs/v2/api-reference/endpoint/peers/get-peers.mdx index 9ff50e45..954e89b2 100644 --- a/docs/v2/api-reference/endpoint/peers/get-peers.mdx +++ b/docs/v2/api-reference/endpoint/peers/get-peers.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/peers/list ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/get-sessions-for-peer.mdx b/docs/v2/api-reference/endpoint/peers/get-sessions-for-peer.mdx index a1d02d45..8b33a832 100644 --- a/docs/v2/api-reference/endpoint/peers/get-sessions-for-peer.mdx +++ b/docs/v2/api-reference/endpoint/peers/get-sessions-for-peer.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/peers/{peer_id}/sessions ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/get-working-representation.mdx b/docs/v2/api-reference/endpoint/peers/get-working-representation.mdx index ff061263..b712a032 100644 --- a/docs/v2/api-reference/endpoint/peers/get-working-representation.mdx +++ b/docs/v2/api-reference/endpoint/peers/get-working-representation.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/peers/{peer_id}/representation ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/search-peer.mdx b/docs/v2/api-reference/endpoint/peers/search-peer.mdx index b61ef014..c8c25451 100644 --- a/docs/v2/api-reference/endpoint/peers/search-peer.mdx +++ b/docs/v2/api-reference/endpoint/peers/search-peer.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/peers/{peer_id}/search ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/peers/update-peer.mdx b/docs/v2/api-reference/endpoint/peers/update-peer.mdx index 8177101d..91b6da61 100644 --- a/docs/v2/api-reference/endpoint/peers/update-peer.mdx +++ b/docs/v2/api-reference/endpoint/peers/update-peer.mdx @@ -1,3 +1,3 @@ --- openapi: put /v2/workspaces/{workspace_id}/peers/{peer_id} ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/add-peers-to-session.mdx b/docs/v2/api-reference/endpoint/sessions/add-peers-to-session.mdx index cb548248..e73ece96 100644 --- a/docs/v2/api-reference/endpoint/sessions/add-peers-to-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/add-peers-to-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/{session_id}/peers ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/clone-session.mdx b/docs/v2/api-reference/endpoint/sessions/clone-session.mdx index 62eae331..e06f19a1 100644 --- a/docs/v2/api-reference/endpoint/sessions/clone-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/clone-session.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/workspaces/{workspace_id}/sessions/{session_id}/clone ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/delete-session.mdx b/docs/v2/api-reference/endpoint/sessions/delete-session.mdx index c49a2db7..2d1252fb 100644 --- a/docs/v2/api-reference/endpoint/sessions/delete-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/delete-session.mdx @@ -1,3 +1,3 @@ --- openapi: delete /v2/workspaces/{workspace_id}/sessions/{session_id} ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/get-or-create-session.mdx b/docs/v2/api-reference/endpoint/sessions/get-or-create-session.mdx index 793659e0..c013803e 100644 --- a/docs/v2/api-reference/endpoint/sessions/get-or-create-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/get-or-create-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/get-peer-config.mdx b/docs/v2/api-reference/endpoint/sessions/get-peer-config.mdx index 4b12e2f5..e84f5cea 100644 --- a/docs/v2/api-reference/endpoint/sessions/get-peer-config.mdx +++ b/docs/v2/api-reference/endpoint/sessions/get-peer-config.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/workspaces/{workspace_id}/sessions/{session_id}/peers/{peer_id}/config ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/get-session-context.mdx b/docs/v2/api-reference/endpoint/sessions/get-session-context.mdx index 08adfaa2..c8c1bdd1 100644 --- a/docs/v2/api-reference/endpoint/sessions/get-session-context.mdx +++ b/docs/v2/api-reference/endpoint/sessions/get-session-context.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/workspaces/{workspace_id}/sessions/{session_id}/context ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/get-session-peers.mdx b/docs/v2/api-reference/endpoint/sessions/get-session-peers.mdx index a363988a..18beb221 100644 --- a/docs/v2/api-reference/endpoint/sessions/get-session-peers.mdx +++ b/docs/v2/api-reference/endpoint/sessions/get-session-peers.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/workspaces/{workspace_id}/sessions/{session_id}/peers ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/get-sessions.mdx b/docs/v2/api-reference/endpoint/sessions/get-sessions.mdx index 4efaf7d0..75dc7eba 100644 --- a/docs/v2/api-reference/endpoint/sessions/get-sessions.mdx +++ b/docs/v2/api-reference/endpoint/sessions/get-sessions.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/list ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/remove-peers-from-session.mdx b/docs/v2/api-reference/endpoint/sessions/remove-peers-from-session.mdx index 472555ff..31acf6bf 100644 --- a/docs/v2/api-reference/endpoint/sessions/remove-peers-from-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/remove-peers-from-session.mdx @@ -1,3 +1,3 @@ --- openapi: delete /v2/workspaces/{workspace_id}/sessions/{session_id}/peers ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/search-session.mdx b/docs/v2/api-reference/endpoint/sessions/search-session.mdx index d1335b49..308138d5 100644 --- a/docs/v2/api-reference/endpoint/sessions/search-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/search-session.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/{session_id}/search ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/set-peer-config.mdx b/docs/v2/api-reference/endpoint/sessions/set-peer-config.mdx index 74abb1d8..6e6ee462 100644 --- a/docs/v2/api-reference/endpoint/sessions/set-peer-config.mdx +++ b/docs/v2/api-reference/endpoint/sessions/set-peer-config.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/sessions/{session_id}/peers/{peer_id}/config ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/set-session-peers.mdx b/docs/v2/api-reference/endpoint/sessions/set-session-peers.mdx index 24115e0d..475db5b9 100644 --- a/docs/v2/api-reference/endpoint/sessions/set-session-peers.mdx +++ b/docs/v2/api-reference/endpoint/sessions/set-session-peers.mdx @@ -1,3 +1,3 @@ --- openapi: put /v2/workspaces/{workspace_id}/sessions/{session_id}/peers ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/sessions/update-session.mdx b/docs/v2/api-reference/endpoint/sessions/update-session.mdx index 9706880a..94d14d1b 100644 --- a/docs/v2/api-reference/endpoint/sessions/update-session.mdx +++ b/docs/v2/api-reference/endpoint/sessions/update-session.mdx @@ -1,3 +1,3 @@ --- openapi: put /v2/workspaces/{workspace_id}/sessions/{session_id} ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/workspaces/get-all-workspaces.mdx b/docs/v2/api-reference/endpoint/workspaces/get-all-workspaces.mdx index 3d96ed9d..12e3d931 100644 --- a/docs/v2/api-reference/endpoint/workspaces/get-all-workspaces.mdx +++ b/docs/v2/api-reference/endpoint/workspaces/get-all-workspaces.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/list ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/workspaces/get-deriver-status.mdx b/docs/v2/api-reference/endpoint/workspaces/get-deriver-status.mdx index 2f78403d..0b7a32b9 100644 --- a/docs/v2/api-reference/endpoint/workspaces/get-deriver-status.mdx +++ b/docs/v2/api-reference/endpoint/workspaces/get-deriver-status.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/workspaces/{workspace_id}/deriver/status ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/workspaces/get-or-create-workspace.mdx b/docs/v2/api-reference/endpoint/workspaces/get-or-create-workspace.mdx index 9c72811b..513f4fbb 100644 --- a/docs/v2/api-reference/endpoint/workspaces/get-or-create-workspace.mdx +++ b/docs/v2/api-reference/endpoint/workspaces/get-or-create-workspace.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/workspaces/search-workspace.mdx b/docs/v2/api-reference/endpoint/workspaces/search-workspace.mdx index d06ea559..f17e4e52 100644 --- a/docs/v2/api-reference/endpoint/workspaces/search-workspace.mdx +++ b/docs/v2/api-reference/endpoint/workspaces/search-workspace.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/workspaces/{workspace_id}/search ---- \ No newline at end of file +--- diff --git a/docs/v2/api-reference/endpoint/workspaces/update-workspace.mdx b/docs/v2/api-reference/endpoint/workspaces/update-workspace.mdx index d51ed69b..07d3b75e 100644 --- a/docs/v2/api-reference/endpoint/workspaces/update-workspace.mdx +++ b/docs/v2/api-reference/endpoint/workspaces/update-workspace.mdx @@ -1,3 +1,3 @@ --- openapi: put /v2/workspaces/{workspace_id} ---- \ No newline at end of file +--- diff --git a/docs/v2/contributing/guidelines.mdx b/docs/v2/contributing/guidelines.mdx index 9f37dc02..f064e51b 100644 --- a/docs/v2/contributing/guidelines.mdx +++ b/docs/v2/contributing/guidelines.mdx @@ -42,7 +42,7 @@ git checkout -b fix/your-bug-fix-name **Branch naming conventions:** - `feature/description` - for new features -- `fix/description` - for bug fixes +- `fix/description` - for bug fixes - `docs/description` - for documentation updates - `refactor/description` - for code refactoring - `test/description` - for adding or updating tests @@ -169,4 +169,4 @@ When reporting bugs or requesting features: By contributing to Honcho, you agree that your contributions will be licensed under the same [AGPL-3.0 License](./license) that covers the project. -Thank you for helping make Honcho better! 🫡 \ No newline at end of file +Thank you for helping make Honcho better! 🫡 diff --git a/docs/v2/contributing/license.mdx b/docs/v2/contributing/license.mdx index a53e2d7a..6855347e 100644 --- a/docs/v2/contributing/license.mdx +++ b/docs/v2/contributing/license.mdx @@ -3,7 +3,7 @@ title: 'License' icon: 'scroll' --- -Honcho is licensed under the AGPL-3.0 License. This is copied below for convenience and also present in the +Honcho is licensed under the AGPL-3.0 License. This is copied below for convenience and also present in the [GitHub Repository](https://github.com/plastic-labs/honcho) ``` @@ -668,4 +668,4 @@ specific requirements. if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . -``` \ No newline at end of file +``` diff --git a/docs/v2/documentation/core-concepts/architecture.mdx b/docs/v2/documentation/core-concepts/architecture.mdx index 134914d8..032548c5 100644 --- a/docs/v2/documentation/core-concepts/architecture.mdx +++ b/docs/v2/documentation/core-concepts/architecture.mdx @@ -7,7 +7,7 @@ sidebarTitle: "Architecture" The goal of this page is to build an intuition for the primitives in Honcho and how they fit together -Honcho has 3 main components that work together to manage agent identity and context. +Honcho has 3 main components that work together to manage agent identity and context. - **The Storage API**: The Memory layer for storing interaction history for your agents - **The Deriver**: The background processing layer that builds representations of users and agents @@ -20,7 +20,7 @@ how to use them. ## Data Model -Honcho has a hierarchical data model centered around the entities below. +Honcho has a hierarchical data model centered around the entities below. ```mermaid graph TD @@ -42,15 +42,17 @@ Honcho has a hierarchical data model centered around the entities below. There are `Workspaces` at the top that contain `Peers` and `Sessions`. A `Peer` can be part of many `Sessions` and a `Session` can have many `Peers`. Both -`Sessions` and `Peers` can have `Messages` +`Sessions` and `Peers` can have `Messages` ### Workspaces -Workspaces are the top-level containers that provide complete isolation between different applications or environments; they essentially as a namespace to isolate different workloads or environments +Workspaces are the top-level containers that provide complete isolation between +different applications or environments; they essentially serve as a namespace +to isolate different workloads or environments **Key Features:** - **Isolation**: Complete data separation between workspaces -- **Multi-tenancy**: Support multiple applications or environments +- **Multi-tenancy**: Support multiple applications or environments - **Configuration**: Workspace-level settings and metadata - **Access Control**: Authentication scoped to workspace level @@ -110,7 +112,7 @@ Messages are the fundamental units of interaction within sessions. They may also be used at the peer level to ingest information of any kind that is not related to a specific interaction, but provides important context for a peer (emails, docs, files, etc.). -**Key Features:** +**Key Features:** - **Rich Content**: Support for text, metadata, and structured data - **Attribution**: Clear association with sending peer - **Ordering**: Chronological sequence within sessions @@ -136,7 +138,7 @@ differently and update different representations. Facts derived here are used in the Dialectic chat endpoint to generate context-aware responses that can correctly reference both concrete facts extracted from messages and social insights deduced from facts, tone, and -opinion. +opinion. Deriver tasks are processed in parallel, but tasks affecting the same peer representation will always be processed serially in order of message creation, so as to properly understand their cumulative effect. @@ -151,11 +153,11 @@ There are two types of tasks that the deriver currently does: Peer representations are more of an abstract concept, as they are made up of various pieces of data stored throughout Honcho. There are however -multiple types of representations that Honcho can produce. +multiple types of representations that Honcho can produce. Honcho handles both **local** and **global** representations of Peers, where **local** representations are specific to a single Peer's view of another Peer, -while Global Representations are based on any message ever produced by a Peer. +while Global Representations are based on any message ever produced by a Peer. Peer Representations @@ -164,7 +166,7 @@ representation, but she also maintains a local representation of Bob based on wh observes and similarly Bob has a global representation of himself and local representation of Alice. So in the example above, when Alice sends a message to Bob it triggers an update to both Alice's global representation of herself and Bob's local -representation of Alice. +representation of Alice. If Alice were to have another conversation with a different Peer, Nico, and sent them a message, this action would trigger an update to Alice's Global diff --git a/docs/v2/documentation/core-concepts/features.mdx b/docs/v2/documentation/core-concepts/features.mdx index 4d46ac60..831b724a 100644 --- a/docs/v2/documentation/core-concepts/features.mdx +++ b/docs/v2/documentation/core-concepts/features.mdx @@ -13,18 +13,14 @@ Peers in Honcho are abstract entities that can represent humans, agents, or NPCs - **Local Representation**: The representation that a Peer forms of other Peers, based on the messages those other Peers have sent (as observed by the Peer forming the representation). - At the Session level, you can configure which Peers are able to observe messages from other Peers in that Session. This determines which Peers form representations of others within the Session. -### Ingest Arbitrary Data -To facilitate the construction of a Peer's global representation, Honcho is able to ingest arbitrary data to a Peer by adding messages to the Peer directly, outside of the context of a Session. -- Currently, Honcho supports the ingestion of arbitrary text data. - ### Queue Status -To help developers understand when a Peer's representation is fully up to date, Honcho exposes the ability to poll the status of Peer-centric queues that construct representations. +To help developers understand when a Peer's representation is fully up to date, Honcho exposes the ability to poll the status of Peer-centric queues that construct representations. - If no Session is specified, the queue status reflects pending work for the Peer's global representation. - If a Session is specified, the queue status reflects pending work for the Peer's working representation in that Session. ### Search -Honcho supports full-text search across message content across different scopes. -- You can search within a specific Session, Peer, or across all messages in a Workspace. +Honcho supports full-text search across message content in different scopes. +- You can search within a specific Session, Peer, or across all messages in a Workspace. - Search results are ordered by relevance, making it easy to quickly retrieve important past messages. ### Scoped API Keys @@ -33,8 +29,8 @@ Builders can create scoped API keys to control access to different resources wit - **Peer-Level Keys**: Access to everything scoped to a Peer. - **Session-Level Keys**: Access to everything scoped to a Session. -### Get Context +### Get Context Honcho provides a powerful context retrieval feature that delivers formatted conversation context from sessions, making it easy to integrate with LLMs like OpenAI, Anthropic, and others. -- By default, the context includes a blend of summary and messages which covers the entire history of the session. +- By default, the context includes a blend of summary and messages which covers the entire history of the session. - Summaries are generated automatically at intervals, and recent messages are included based on your specified token budget for the context. - You can set any token limit, and if you prefer, you can disable summaries so that the context consists entirely of the most recent messages up to your chosen limit. diff --git a/docs/v2/documentation/core-concepts/glossary.mdx b/docs/v2/documentation/core-concepts/glossary.mdx index 5ef7fa33..75707f75 100644 --- a/docs/v2/documentation/core-concepts/glossary.mdx +++ b/docs/v2/documentation/core-concepts/glossary.mdx @@ -67,4 +67,4 @@ cognitive functions. continually generating & updating internal world models to anticipate sensory input, rather than passively receiving it--closely linked to Bayesian brain hypotheses, which hold that the brain interprets the world probabilistically, weighing prior knowledge against new evidence to minimize -uncertainty. \ No newline at end of file +uncertainty. diff --git a/docs/v2/documentation/introduction/overview.mdx b/docs/v2/documentation/introduction/overview.mdx index f3150c10..f5ff19e6 100644 --- a/docs/v2/documentation/introduction/overview.mdx +++ b/docs/v2/documentation/introduction/overview.mdx @@ -31,7 +31,7 @@ rich psychological understanding. # Start simple - just add messages session.add_messages([alice.message("I learn best with examples")]) -# Get powerful - query user psychology +# Get powerful - query user psychology insight = peer.chat("How should I explain this concept?") # > "This user learns best through concrete examples..." ``` diff --git a/docs/v2/documentation/introduction/quickstart.mdx b/docs/v2/documentation/introduction/quickstart.mdx index e564729b..d6e76ac8 100644 --- a/docs/v2/documentation/introduction/quickstart.mdx +++ b/docs/v2/documentation/introduction/quickstart.mdx @@ -136,21 +136,21 @@ response = alice.chat("Tell me about Bob's interests and habits") print(response) # Returns rich context like: -# "Bob is health-conscious and has been working on getting back in shape. -# He regularly goes to the gym, particularly in the evenings, and finds -# exercise helps him relax. He's encouraging about fitness and willing +# "Bob is health-conscious and has been working on getting back in shape. +# He regularly goes to the gym, particularly in the evenings, and finds +# exercise helps him relax. He's encouraging about fitness and willing # to share advice about workout routines." ``` ```typescript TypeScript -// Ask what Bob is like +// Ask what Bob is like const response = await alice.chat("Tell me about Bob's interests and habits"); console.log(response); // Returns rich context like: -// "Bob is health-conscious and has been working on getting back in shape. -// He regularly goes to the gym, particularly in the evenings, and finds -// exercise helps him relax. He's encouraging about fitness and willing +// "Bob is health-conscious and has been working on getting back in shape. +// He regularly goes to the gym, particularly in the evenings, and finds +// exercise helps him relax. He's encouraging about fitness and willing // to share advice about workout routines." ``` @@ -167,11 +167,10 @@ The response isn't just retrieving stored text - it's synthesizing insights abou ## Next Steps -This covers the core concepts: **peers**, **sessions**, **messages**, and **dialectic queries**. +This covers the core concepts: **peers**, **sessions**, **messages**, and **dialectic queries**. - For production use, [sign up for the managed platform](https://app.honcho.dev) or get an [overview here](/v2/documentation/platform/overview). - For detailed API reference, check out our [SDK documentation](/v2/documentation/platform/sdk). - For more examples, explore our [guides](/v2/guides). --- - diff --git a/docs/v2/documentation/introduction/vibecoding.mdx b/docs/v2/documentation/introduction/vibecoding.mdx index f56adf2f..105d3ae6 100644 --- a/docs/v2/documentation/introduction/vibecoding.mdx +++ b/docs/v2/documentation/introduction/vibecoding.mdx @@ -81,7 +81,7 @@ STARTER TEMPLATE: WHAT TO BUILD: - Discord bot with persistent memory using Honcho -- Learns about users through natural conversation +- Learns about users through natural conversation - Provides personalized responses based on user history - Handles multi-user conversations with context awareness - Extends the starter template with advanced memory features @@ -95,7 +95,7 @@ TECHNICAL SETUP: CORE FEATURES TO ADD: - Enhanced per-user memory and personality modeling -- Channel-specific session management +- Channel-specific session management - Theory-of-mind queries ("What does this user like?") - Advanced fact extraction from conversations - Multi-participant conversation handling @@ -127,10 +127,10 @@ Include examples of enhanced bot interactions and memory demonstrations. # Create project mkdir my-honcho-agent cd my-honcho-agent - + # Open in Cursor cursor . - + # Use Cmd+L to open AI chat # Paste one of the prompts above ``` @@ -140,10 +140,10 @@ Include examples of enhanced bot interactions and memory demonstrations. # Clone the starter template git clone https://github.com/plastic-labs/discord-python-starter.git cd discord-python-starter - + # Install dependencies uv sync - + # Configure environment cp .env.template .env # Add your Discord token and API keys diff --git a/docs/v2/documentation/reference/guided-tutorial.mdx b/docs/v2/documentation/reference/guided-tutorial.mdx index b1834b1f..cdd80139 100644 --- a/docs/v2/documentation/reference/guided-tutorial.mdx +++ b/docs/v2/documentation/reference/guided-tutorial.mdx @@ -95,10 +95,10 @@ def start_new_session(session_name=None): # Create session with descriptive ID session_id = session_name or f"chat-{int(time.time())}" session = honcho.session(session_id) - + # Add both peers to the session session.add_peers([alice, assistant]) - + print(f"Started new session: {session.id}") return session @@ -113,10 +113,10 @@ session = start_new_session("daily-checkin") def add_conversation_turn(session, user_message, assistant_response=None): """Add a conversation turn to the session""" messages_to_add = [alice.message(user_message)] - + if assistant_response: messages_to_add.append(assistant.message(assistant_response)) - + session.add_messages(messages_to_add) print(f"Added {len(messages_to_add)} messages to session") @@ -135,7 +135,7 @@ def teach_assistant_about_user(assistant_peer, user_peer, facts): fact_messages = [] for fact in facts: fact_messages.append(assistant_peer.message(f"I learned that {user_peer.id} {fact}")) - + # Add these to the assistant's global knowledge assistant_peer.add_messages(fact_messages) print(f"Taught assistant {len(facts)} facts about {user_peer.id}") @@ -145,21 +145,21 @@ def extract_facts_from_message(user_message): prompt = f""" Extract discrete facts about the user from this message: "{user_message}" - - Return only factual statements about the user, no inferences. + + Return only factual statements about the user, no inferences. Format as a simple list of facts starting with action verbs or descriptors. If no facts can be extracted, return an empty list. Example: "is working on a Python project", "likes morning coffee" """ - + response = openai.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], temperature=0.1 ) - + facts_text = response.choices[0].message.content.strip() - + # Parse facts (simple line-by-line approach) facts = [fact.strip("- ").strip() for fact in facts_text.split('\n') if fact.strip()] return [fact for fact in facts if fact and len(fact) > 5] @@ -181,21 +181,21 @@ if facts: ```python def generate_response_with_context(session, assistant_peer, user_message): """Generate AI response using Honcho's built-in context management""" - + # Get formatted conversation context - Honcho handles the complexity! context = session.get_context(tokens=2000) messages = context.to_openai(assistant=assistant_peer) - + # Add the current user message messages.append({"role": "user", "content": user_message}) - + # Call your LLM with the properly formatted context response = openai.chat.completions.create( model="gpt-3.5-turbo", messages=messages, temperature=0.7 ) - + return response.choices[0].message.content # Generate response using built-in context @@ -222,8 +222,8 @@ def get_personalized_insight(assistant_peer, user_peer, query): # Get personalized insights without manual context building insight = get_personalized_insight( - assistant, - alice, + assistant, + alice, "What programming projects has this user worked on?" ) print(f"Programming insights: {insight}") @@ -236,28 +236,28 @@ print(f"Programming insights: {insight}") ```python def chat_with_assistant(user_peer, assistant_peer, message_text, session=None): """Complete conversation flow with memory and personalization""" - + # Use existing session or create new one if not session: session = start_new_session() - + # Extract facts from user message and teach assistant facts = extract_facts_from_message(message_text) if facts: teach_assistant_about_user(assistant_peer, user_peer, facts) - + # Generate response using Honcho's built-in context management ai_response = generate_response_with_context(session, assistant_peer, message_text) - + # Add the conversation turn to session add_conversation_turn(session, message_text, ai_response) - + return ai_response # Test the complete flow response = chat_with_assistant( - alice, - assistant, + alice, + assistant, "I finished the authentication module for my Python project!" ) print(f"Assistant: {response}") @@ -287,8 +287,8 @@ def query_user_history(assistant_peer, user_peer, query): # Query across all conversations history_query = query_user_history( - assistant, - alice, + assistant, + alice, "what programming languages and technologies has this user mentioned?" ) print(f"User's programming history: {history_query}") @@ -320,11 +320,11 @@ print(f"Session summary: {session_summary}") def create_group_session(user_peers, assistant_peer): """Create a session with multiple users and an assistant""" group_session = honcho.session("group-discussion") - + # Add all peers to the session all_peers = user_peers + [assistant_peer] group_session.add_peers(all_peers) - + return group_session # Create multiple user peers @@ -362,7 +362,7 @@ print(f"Alice's language preferences: {response}") # Query session-specific knowledge session_insights = assistant.chat( - "What was the main topic of discussion in this session?", + "What was the main topic of discussion in this session?", session_id=session.id ) print(f"Session insights: {session_insights}") @@ -376,10 +376,10 @@ def use_anthropic_format(session, assistant_peer, user_message): """Example using Anthropic's message format""" context = session.get_context(tokens=1500) messages = context.to_anthropic(assistant=assistant_peer) - + # Now you can use these messages with Anthropic's API # anthropic_response = anthropic.messages.create(...) - + return messages # Get Anthropic-formatted messages @@ -422,4 +422,4 @@ Congratulations! You've built a complete personal AI assistant with Honcho that - [SDK Reference](/v2/documentation/platform/sdk) - [API Reference](/v2/api-reference/introduction) - [More Examples](/v2/guides/overview) -- [Discord Community](http://discord.gg/plasticlabs) +- [Discord Community](http://discord.gg/plasticlabs) diff --git a/docs/v2/documentation/reference/platform.mdx b/docs/v2/documentation/reference/platform.mdx index 50182080..d50c64f9 100644 --- a/docs/v2/documentation/reference/platform.mdx +++ b/docs/v2/documentation/reference/platform.mdx @@ -9,7 +9,7 @@ sidebarTitle: "Platform Overview" Start using the platform to manage Honcho instances for your workspace or app. -The quickest way to begin using Honcho in production is with the +The quickest way to begin using Honcho in production is with the [Honcho Cloud Platform](https://app.honcho.dev). Sign up, generate an API key, and start building with Honcho. @@ -32,7 +32,7 @@ Each organization has dedicated infrastructure running to isolate your workloads. Until you activate a subscription under the [Billing](https://app.honcho.dev/billing) page, the infrastructure will be deactivated. You can check the status of your instance by going to the -[Status](https://app.honcho.dev/status) page. +[Status](https://app.honcho.dev/status) page. Honcho Status Page with No Subscription @@ -66,7 +66,7 @@ see real-time responses with full request/response logging. ## **5. Create a Workspace** On the explore page, you can see and interact with the data stored in your -Honcho instance. You can create `Workspaces`, `Peers`, `Sessions`, and `Messages`. +Honcho instance. You can create `Workspaces`, `Peers`, `Sessions`, and `Messages`. Explore Honcho Page @@ -81,7 +81,7 @@ representations. Utilize your ability to search across all `Messages` in a ## **7. Peer Utilities & Chat Endpoint** Select a `Peer` to open its utilities. From here you can search that `Peer`'s -`Messages`, view participating `Sessions`, or edit its metadata. +`Messages`, view participating `Sessions`, or edit its metadata. The Chat Endpoint allows you to send a query through the Honcho API to get information about the `Peer`'s representation. If you include the `Session` ID, @@ -94,7 +94,7 @@ within that `Session`. ## **8. Session Dashboard & Get Context** -From the workspace dashboard, navigate to a `Session` to find options to add `Peers`, view `Messages`, and search across all `Messages` for a particular session. +From the workspace dashboard, navigate to a `Session` to find options to add `Peers`, view `Messages`, and search across all `Messages` for a particular session. Use the **Get Context** feature to quickly generate output that fits into your LLM’s context window. Get a subset of messages with an optional summary diff --git a/docs/v2/documentation/reference/sdk.mdx b/docs/v2/documentation/reference/sdk.mdx index 1c0e6fe4..9de0d50c 100644 --- a/docs/v2/documentation/reference/sdk.mdx +++ b/docs/v2/documentation/reference/sdk.mdx @@ -113,13 +113,13 @@ response = alice.chat("What happened in our conversation?", session_id=session.i const response = await alice.chat("What does the user know about weather?"); // Query what alice knows about the assistant (local representation) -const targetResponse = await alice.chat("What does the user know about the assistant?", { - target: assistant +const targetResponse = await alice.chat("What does the user know about the assistant?", { + target: assistant }); // Query scoped to a specific session -const sessionResponse = await alice.chat("What happened in our conversation?", { - sessionId: session.id +const sessionResponse = await alice.chat("What happened in our conversation?", { + sessionId: session.id }); ``` @@ -285,8 +285,8 @@ console.log(`Peer ID: ${alice.id}`); // Chat with peer's representations (supports streaming) const response = await alice.chat("What did I have for breakfast?"); const targetResponse = await alice.chat("What do I know about Bob?", { target: "bob" }); -const sessionResponse = await alice.chat("What happened in session-1?", { - sessionId: "session-1" +const sessionResponse = await alice.chat("What happened in session-1?", { + sessionId: "session-1" }); // Chat with streaming support @@ -308,9 +308,9 @@ const results = await alice.search("programming"); // Metadata management const metadata = await alice.getMetadata(); -await alice.setMetadata({ - ...metadata, - location: "Paris" +await alice.setMetadata({ + ...metadata, + location: "Paris" }); ``` @@ -404,9 +404,9 @@ const globalRep = await session.workingRep("alice"); const targetedRep = await session.workingRep(alice, bob); // Metadata management -await session.setMetadata({ - topic: "product planning", - status: "active" +await session.setMetadata({ + topic: "product planning", + status: "active" }); const metadata = await session.getMetadata(); ``` @@ -510,8 +510,8 @@ await groupChat.addMessages([ // Query different perspectives const userPerspective = await users[0].chat("What are people's concerns?"); -const moderatorView = await moderator.chat("What feedback am I getting?", { - sessionId: groupChat.id +const moderatorView = await moderator.chat("What feedback am I getting?", { + sessionId: groupChat.id }); ``` @@ -581,13 +581,13 @@ action_items = session.get_messages(filter={"metadata": {"action_item": True}}) ```typescript TypeScript // Add messages with metadata await session.addMessages([ - alice.message("Let's discuss the budget", { + alice.message("Let's discuss the budget", { metadata: { topic: "finance", priority: "high" } }), - assistant.message("I'll prepare the financial report", { + assistant.message("I'll prepare the financial report", { metadata: { action_item: true, due_date: "2024-01-15" @@ -596,11 +596,11 @@ await session.addMessages([ ]); // Filter messages by metadata -const financeMessages = await session.getMessages({ - filter: { metadata: { topic: "finance" } } +const financeMessages = await session.getMessages({ + filter: { metadata: { topic: "finance" } } }); -const actionItems = await session.getMessages({ - filter: { metadata: { action_item: true } } +const actionItems = await session.getMessages({ + filter: { metadata: { action_item: true } } }); ``` @@ -612,9 +612,9 @@ const actionItems = await session.getMessages({ # Iterate through all sessions for session in honcho.get_sessions(): print(f"Session: {session.id}") - + # Iterate through session messages - for message in session.get_messages(): + for message in session.get_messages(): print(f" {message.peer_id}: {message.content}") ``` diff --git a/docs/v2/guides/ai-assisted-setup.mdx b/docs/v2/guides/ai-assisted-setup.mdx index c8ce7939..f4c733e7 100644 --- a/docs/v2/guides/ai-assisted-setup.mdx +++ b/docs/v2/guides/ai-assisted-setup.mdx @@ -81,7 +81,7 @@ STARTER TEMPLATE: WHAT TO BUILD: - Discord bot with persistent memory using Honcho -- Learns about users through natural conversation +- Learns about users through natural conversation - Provides personalized responses based on user history - Handles multi-user conversations with context awareness - Extends the starter template with advanced memory features @@ -95,7 +95,7 @@ TECHNICAL SETUP: CORE FEATURES TO ADD: - Enhanced per-user memory and personality modeling -- Channel-specific session management +- Channel-specific session management - Theory-of-mind queries ("What does this user like?") - Advanced fact extraction from conversations - Multi-participant conversation handling @@ -127,10 +127,10 @@ Include examples of enhanced bot interactions and memory demonstrations. # Create project mkdir my-honcho-agent cd my-honcho-agent - + # Open in Cursor cursor . - + # Use Cmd+L to open AI chat # Paste one of the prompts above ``` @@ -140,10 +140,10 @@ Include examples of enhanced bot interactions and memory demonstrations. # Clone the starter template git clone https://github.com/plastic-labs/discord-python-starter.git cd discord-python-starter - + # Install dependencies uv sync - + # Configure environment cp .env.template .env # Add your Discord token and API keys @@ -293,4 +293,4 @@ When working with AI assistants, mention these concepts: **Pro Tip**: Be specific about your requirements and constraints when prompting AI. The more context you provide, the better the generated code will match your needs. - \ No newline at end of file + diff --git a/docs/v2/guides/dialectic-endpoint.mdx b/docs/v2/guides/dialectic-endpoint.mdx index 7d5b0a47..c9e89dc2 100644 --- a/docs/v2/guides/dialectic-endpoint.mdx +++ b/docs/v2/guides/dialectic-endpoint.mdx @@ -4,11 +4,11 @@ description: "An endpoint for reasoning about your users" icon: "comments" --- -Honcho by default runs ambient inference on top of the `message` objects you store. Those messages serve as the ground truth upon which facts about the user are derived and stored. The **Dialectic Endpoint** is the natural language interface through which insights are synthesized from those facts. We believe [intellectual respect](https://blog.plasticlabs.ai/extrusions/Extrusion-02.24) for LLMs is paramount in building effective AI agents/apps. It follows that the LLM should know better than any human what would aid them in their generation task. Thus, the Dialectic endpoint exists for flexible agent-to-agent communication. +Honcho by default runs ambient inference on top of the `message` objects you store. Those messages serve as the ground truth upon which facts about the user are derived and stored. The **Dialectic Endpoint** is the natural language interface through which insights are synthesized from those facts. We believe [intellectual respect](https://blog.plasticlabs.ai/extrusions/Extrusion-02.24) for LLMs is paramount in building effective AI agents/apps. It follows that the LLM should know better than any human what would aid them in their generation task. Thus, the Dialectic endpoint exists for flexible agent-to-agent communication. ## Automatic Fact Derivation -On every message written to a session, an automatic callback is run that will reason about the conversation and store facts in a `collection` named `honcho`. This is a reserved `collection` specifically for the backend Honcho agent to interact with. +On every message written to a session, an automatic callback is run that will reason about the conversation and store facts in a `collection` named `honcho`. This is a reserved `collection` specifically for the backend Honcho agent to interact with. ## Dialectic Endpoint @@ -49,7 +49,7 @@ const session = await honcho.session('demo-session'); // (assuming some messages have been written to Honcho for the deriver to use) ``` -## Static Dialectic Call +## Static Dialectic Call ```python Python @@ -85,6 +85,3 @@ for await (const line of responseStream.iter_text()) { We've designed the Dialectic endpoint to be infinitely flexible. We wrote an incomplete list of ideas on how to use it on our blog [here](https://blog.plasticlabs.ai/blog/Introducing-Honcho's-Dialectic-API#how-it-works). - - - diff --git a/docs/v2/guides/discord.mdx b/docs/v2/guides/discord.mdx index c91b9df0..737df751 100644 --- a/docs/v2/guides/discord.mdx +++ b/docs/v2/guides/discord.mdx @@ -11,7 +11,7 @@ Any application interface that defines logic based on events and supports special commands can work easily with Honcho. Here's how to use Honcho with **Discord** as an interface. If you're not familiar with Discord bot application logic, the [py-cord](https://pycord.dev/) docs would be a good -place to start. +place to start. ## Events @@ -89,7 +89,7 @@ def validate_message(message) -> bool: This function centralizes all the logic for determining whether the bot should respond to a message. It checks that: - The message isn't from the bot itself -- The message isn't in a DM channel +- The message isn't in a DM channel - The bot is mentioned in the message ### Message Sanitization @@ -251,4 +251,4 @@ The new Honcho peer/session API makes Discord bot integration much simpler and m - **Dialectic Queries**: `peer.chat()` enables querying conversation history - **Helper Functions**: Clean code organization with focused helper functions -This approach provides a clean, maintainable structure for building Discord bots with conversational memory and context management. \ No newline at end of file +This approach provides a clean, maintainable structure for building Discord bots with conversational memory and context management. diff --git a/docs/v2/guides/file-uploads.mdx b/docs/v2/guides/file-uploads.mdx index 07585360..57171d87 100644 --- a/docs/v2/guides/file-uploads.mdx +++ b/docs/v2/guides/file-uploads.mdx @@ -120,7 +120,7 @@ The upload methods accept the following parameters: Introduction This document provides... -[Page 2] +[Page 2] Methodology Our approach involves... ``` @@ -134,7 +134,7 @@ Our approach involves... Large files are automatically split into chunks of ~49,500 characters. The system seeks to break at natural boundaries if present: 1. Paragraph breaks (`\n\n`) -2. Line breaks (`\n`) +2. Line breaks (`\n`) 3. Sentence endings (`. `) 4. Word boundaries (` `) @@ -230,17 +230,17 @@ def analyze_documents(): """Get AI analysis of uploaded documents""" context = session.get_context(tokens=4000) messages = context.to_openai(assistant=assistant) - + # Add analysis request messages.append({ "role": "user", "content": "Please analyze all the documents I've uploaded and provide a comprehensive summary of the key findings, trends, and recommendations." }) - + # Call OpenAI (or your preferred LLM) # response = openai.chat.completions.create(model="gpt-4", messages=messages) # return response.choices[0].message.content - + return "Analysis would be generated here" # Upload multiple documents @@ -281,17 +281,17 @@ async function uploadDocument(filePath: string, description: string) { async function analyzeDocuments() { const context = await session.getContext({ tokens: 4000 }); const messages = context.toOpenAI(assistant); - + // Add analysis request messages.push({ role: "user", content: "Please analyze all the documents I've uploaded and provide a comprehensive summary of the key findings, trends, and recommendations." }); - + // Call OpenAI (or your preferred LLM) // const response = await openai.chat.completions.create({ model: "gpt-4", messages }); // return response.choices[0].message.content; - + return "Analysis would be generated here"; } @@ -318,4 +318,4 @@ console.log("Document Analysis:", analysis); - **Always wrap uploads in try-catch blocks** for robust error handling - **Validate file types** before upload to avoid processing errors - **Handle large files gracefully** with progress indicators -- **Implement retry logic** for network failures \ No newline at end of file +- **Implement retry logic** for network failures diff --git a/docs/v2/guides/get-context.mdx b/docs/v2/guides/get-context.mdx index c5f143b1..fe86ad2d 100644 --- a/docs/v2/guides/get-context.mdx +++ b/docs/v2/guides/get-context.mdx @@ -80,9 +80,9 @@ context = session.get_context(summary=False, tokens=2000) const context = await session.getContext({ summary: true }); // Combine summary=False with token limits to get more messages -const context = await session.getContext({ - summary: false, - tokens: 2000 +const context = await session.getContext({ + summary: false, + tokens: 2000 }); ``` @@ -197,7 +197,7 @@ messages = session.get_context(tokens=2000).to_openai(assistant=assistant) # Add new user message and get AI response messages.append({ - "role": "user", + "role": "user", "content": "Can you reset my password?" }) @@ -259,33 +259,33 @@ await session.addMessages([ ```python Python def chat_loop(): """Example of a continuous chat loop using get_context()""" - + session = honcho.session("chat-session") user = honcho.peer("user") assistant = honcho.peer("ai-assistant") - + while True: # Get user input user_input = input("You: ") if user_input.lower() in ['quit', 'exit']: break - + # Add user message to session session.add_messages([user.message(user_input)]) - + # Get conversation context context = session.get_context(tokens=2000) messages = context.to_openai(assistant=assistant) - + # Get AI response response = openai_client.chat.completions.create( model="gpt-4", messages=messages ) - + ai_response = response.choices[0].message.content print(f"Assistant: {ai_response}") - + # Add AI response to session session.add_messages([assistant.message(ai_response)]) @@ -298,33 +298,33 @@ async function chatLoop() { const session = honcho.session("chat-session"); const user = honcho.peer("user"); const assistant = honcho.peer("ai-assistant"); - + // This would be replaced with actual user input handling in a real app const userInputs = [ "Hello, how are you?", "What's the weather like?", "Tell me a joke" ]; - + for (const userInput of userInputs) { console.log(`You: ${userInput}`); - + // Add user message to session await session.addMessages([user.message(userInput)]); - + // Get conversation context const context = await session.getContext({ tokens: 2000 }); const messages = context.toOpenAI(assistant); - + // Get AI response const response = await openai.chat.completions.create({ model: "gpt-4", messages: messages }); - + const aiResponse = response.choices[0].message.content; console.log(`Assistant: ${aiResponse}`); - + // Add AI response to session await session.addMessages([assistant.message(aiResponse)]); } @@ -359,9 +359,9 @@ print(f"Context contains {len(messages)} formatted messages") const longSession = honcho.session("long-conversation"); // Get summarized context to fit within token limits -const context = await longSession.getContext({ - summary: true, - tokens: 1500 +const context = await longSession.getContext({ + summary: true, + tokens: 1500 }); const messages = context.toOpenAI(assistant); @@ -464,13 +464,13 @@ Always handle potential errors when working with context: try: context = session.get_context(tokens=2000) messages = context.to_openai(assistant=assistant) - + # Use messages with LLM API response = openai_client.chat.completions.create( model="gpt-4", messages=messages ) - + except Exception as e: print(f"Error getting context: {e}") # Handle error appropriately @@ -480,13 +480,13 @@ except Exception as e: try { const context = await session.getContext({ tokens: 2000 }); const messages = context.toOpenAI(assistant); - + // Use messages with LLM API const response = await openai.chat.completions.create({ model: "gpt-4", messages: messages }); - + } catch (error) { console.error(`Error getting context: ${error}`); // Handle error appropriately diff --git a/docs/v2/guides/mcp.mdx b/docs/v2/guides/mcp.mdx index 19e70232..22daf109 100644 --- a/docs/v2/guides/mcp.mdx +++ b/docs/v2/guides/mcp.mdx @@ -22,7 +22,7 @@ cd honcho-mcp 3. Sync the virtual environment. This package uses [uv](https://docs.astral.sh/uv/), [install](https://docs.astral.sh/uv/#installation) if you haven't. ``` -uv sync +uv sync ``` 4. In Claude Desktop, go to the *top left Mac Toolbar* Settings > Developer and click "Edit Config" @@ -49,7 +49,7 @@ uv sync You probably will need to put the full path to the uv executable in the command field. You can get this by running `which uv` on MacOS/Linux or `where uv` on Windows. -6. Restart the Claude Desktop app. Upon relaunch it should start Honcho and the tools should be available! +6. Restart the Claude Desktop app. Upon relaunch, it should start Honcho and the tools should be available! Just note that, by default, the MCP server is set up to use the Honcho Demo server, which only persists data for 7 days. If you're using the hosted version of Honcho, copy the `.env.template` to a proper `.env` file and update the URL and API key variables accordingly. diff --git a/docs/v2/guides/overview.mdx b/docs/v2/guides/overview.mdx index 4ae9a70d..38b20709 100644 --- a/docs/v2/guides/overview.mdx +++ b/docs/v2/guides/overview.mdx @@ -14,24 +14,24 @@ Whether you're integrating Honcho into existing platforms, exploring advanced fe ## What You'll Find Here ### Getting Started -**[Overview](overview)** - You are here -**[AI-Assisted Setup](ai-assisted-setup)** - Get Honcho running with a single prompt in Cursor or Claude Code +**[Overview](overview)** - You are here +**[AI-Assisted Setup](ai-assisted-setup)** - Get Honcho running with a single prompt in Cursor or Claude Code ### Application Interfaces Ready-to-use integration patterns for popular platforms: -**[Discord Bot](discord)** - Build a Discord bot that remembers users across conversations +**[Discord Bot](discord)** - Build a Discord bot that remembers users across conversations **[Telegram Bot](telegram)** - Create a Telegram bot with persistent user understanding ### Design Patterns Implementation patterns for Honcho's core capabilities -**[Dialectic Endpoint](dialectic-endpoint)** - Query user psychology in natural language -**[Working with Session Context](get-context)** - Manage conversation flow and context windows -**[Search](search)** - Search your data using natural language -**[Working Representations](working-representations)** - Understanding and customizing user models -**[Streaming Responses](streaming-responses)** - Handle real-time interactions efficiently -**[Using Filters](using-filters)** - Control what data gets processed and how +**[Dialectic Endpoint](dialectic-endpoint)** - Query user psychology in natural language +**[Working with Session Context](get-context)** - Manage conversation flow and context windows +**[Search](search)** - Search your data using natural language +**[Working Representations](working-representations)** - Understanding and customizing user models +**[Streaming Responses](streaming-responses)** - Handle real-time interactions efficiently +**[Using Filters](using-filters)** - Control what data gets processed and how **[File Uploads](file-uploads)** - Upload PDF, text, or JSON files to create messages ## Philosophy diff --git a/docs/v2/guides/search.mdx b/docs/v2/guides/search.mdx index f197bb7a..82a300a5 100644 --- a/docs/v2/guides/search.mdx +++ b/docs/v2/guides/search.mdx @@ -126,7 +126,7 @@ results = honcho.search("customer feedback") for result in results: # Each result contains the matched content and context print(f"Match: {result}") - + # Check if there are any results results = honcho.search("nonexistent topic") result_list = list(results) @@ -166,7 +166,7 @@ count = 0 for result in results: count += 1 print(f"Result {count}: {result}") - + # Stop after first 10 results if count >= 10: break @@ -179,11 +179,11 @@ let currentPage = await honcho.search("project updates"); while (currentPage) { const data = await currentPage.data(); console.log(`Processing ${data.length} results`); - + for (const result of data) { console.log(`Result: ${result}`); } - + // Get next page currentPage = await currentPage.nextPage(); } @@ -197,23 +197,23 @@ while (currentPage) { # Use search results to build context for LLM interactions def build_context_from_search(query: str, session_id: str): session = honcho.session(session_id) - + # Search for relevant past discussions search_results = list(session.search(query)) - + if search_results: # Use search results to inform context context_summary = f"Found {len(search_results)} relevant past discussions about '{query}'" - + # Get normal session context session_context = session.get_context(tokens=1500) - + return { "search_summary": context_summary, "session_context": session_context, "search_results": search_results[:3] # Top 3 results } - + return {"message": "No relevant past discussions found"} # Build context for a new question @@ -225,25 +225,25 @@ print(f"Context: {context_data.get('search_summary', 'No context')}") // Use search results to build context for LLM interactions async function buildContextFromSearch(query: string, sessionId: string) { const session = honcho.session(sessionId); - + // Search for relevant past discussions const searchResults = await session.search(query); const searchData = await searchResults.data(); - + if (searchData.length > 0) { // Use search results to inform context const contextSummary = `Found ${searchData.length} relevant past discussions about '${query}'`; - + // Get normal session context const sessionContext = await session.getContext({ tokens: 1500 }); - + return { searchSummary: contextSummary, sessionContext: sessionContext, searchResults: searchData.slice(0, 3) // Top 3 results }; } - + return { message: "No relevant past discussions found" }; } diff --git a/docs/v2/guides/streaming-response.mdx b/docs/v2/guides/streaming-response.mdx index 3280dda1..ef4d0594 100644 --- a/docs/v2/guides/streaming-response.mdx +++ b/docs/v2/guides/streaming-response.mdx @@ -84,8 +84,8 @@ for chunk in response_stream.iter_text(): ```typescript TypeScript // Basic streaming example -const responseStream = await user.chat("What can you tell me about this user?", { - stream: true +const responseStream = await user.chat("What can you tell me about this user?", { + stream: true }); // Process the stream @@ -114,17 +114,17 @@ from honcho import Honcho async def restaurant_recommendation_chat(): # Initialize client honcho = Honcho() - + # Create peers user = honcho.peer("food-lover") assistant = honcho.peer("restaurant-assistant") - + # Create session session = honcho.session("food-preferences-session") - + # Add peers to session await session.add_peers([user, assistant]) - + # Store multiple user messages about food preferences user_messages = [ "I absolutely love spicy Thai food, especially curries with coconut milk.", @@ -132,32 +132,32 @@ async def restaurant_recommendation_chat(): "I try to eat vegetarian most of the time, but occasionally enjoy seafood.", "I can't handle overly sweet desserts, but love something with dark chocolate." ] - + # Add the user's messages to the session session_messages = [user.message(message) for message in user_messages] await session.add_messages(session_messages) - + # Print the user messages for message in user_messages: print(f"User: {message}") - + # Ask for restaurant recommendations based on preferences print("\nRequesting restaurant recommendations...") print("Assistant: ", end="", flush=True) full_response = "" - + # Stream the response using the user's peer to get recommendations response_stream = user.chat( "Based on this user's food preferences, recommend 3 restaurants they might enjoy in the Lower East Side.", stream=True, session_id=session.id ) - + for chunk in response_stream.iter_text(): print(chunk, end="", flush=True) full_response += chunk await asyncio.sleep(0.01) - + # Store the assistant's complete response await session.add_messages([ assistant.message(full_response) @@ -174,17 +174,17 @@ import { Honcho } from '@honcho-ai/sdk'; async function restaurantRecommendationChat() { // Initialize client const honcho = new Honcho(); - + // Create peers const user = honcho.peer('food-lover'); const assistant = honcho.peer('restaurant-assistant'); - + // Create session const session = honcho.session('food-preferences-session'); - + // Add peers to session await session.addPeers([user, assistant]); - + // Store multiple user messages about food preferences const userMessages = [ "I absolutely love spicy Thai food, especially curries with coconut milk.", @@ -192,35 +192,35 @@ async function restaurantRecommendationChat() { "I try to eat vegetarian most of the time, but occasionally enjoy seafood.", "I can't handle overly sweet desserts, but love something with dark chocolate." ]; - + // Add the user's messages to the session const sessionMessages = userMessages.map(message => user.message(message)); await session.addMessages(sessionMessages); - + // Print the user messages for (const message of userMessages) { console.log(`User: ${message}`); } - + // Ask for restaurant recommendations based on preferences console.log("\nRequesting restaurant recommendations..."); process.stdout.write("Assistant: "); let fullResponse = ""; - + // Stream the response using the user's peer to get recommendations const responseStream = await user.chat( "Based on this user's food preferences, recommend 3 restaurants they might enjoy in the Lower East Side.", - { + { stream: true, - sessionId: session.id + sessionId: session.id } ); - + for await (const chunk of responseStream.iter_text()) { process.stdout.write(chunk); fullResponse += chunk; } - + // Store the assistant's complete response await session.addMessages([ assistant.message(fullResponse) @@ -240,4 +240,4 @@ When implementing streaming: - Be mindful of memory usage when accumulating large responses - Use appropriate error handling for network interruptions -Streaming responses provide a more interactive and engaging user experience. By implementing streaming in your Honcho applications, you can create more responsive AI-powered features that feel natural and immediate to your users. +Streaming responses provide a more interactive and engaging user experience. By implementing streaming in your Honcho applications, you can create more responsive AI-powered features that feel natural and immediate to your users. diff --git a/docs/v2/guides/telegram.mdx b/docs/v2/guides/telegram.mdx index ddaf427a..160eb416 100644 --- a/docs/v2/guides/telegram.mdx +++ b/docs/v2/guides/telegram.mdx @@ -322,7 +322,7 @@ Your bot needs these environment variables: # Your Telegram bot token from BotFather BOT_TOKEN= -# AI model to use (see OpenRouter for available models) +# AI model to use (see OpenRouter for available models) MODEL_NAME= # Your OpenRouter API key diff --git a/docs/v2/guides/using-filters.mdx b/docs/v2/guides/using-filters.mdx index a13bca0a..f22fa0b2 100644 --- a/docs/v2/guides/using-filters.mdx +++ b/docs/v2/guides/using-filters.mdx @@ -606,7 +606,7 @@ try: except FilterError as e: print(f"Filter error: {e}") # Handle the error appropriately - + try: # Invalid column name sessions = honcho.get_sessions(filter={ diff --git a/docs/v2/guides/working-rep.mdx b/docs/v2/guides/working-rep.mdx index 67d71c3f..4e87574b 100644 --- a/docs/v2/guides/working-rep.mdx +++ b/docs/v2/guides/working-rep.mdx @@ -100,7 +100,7 @@ representation = session.working_rep("user-123") # Typical content structure: """ PREDICTION ABOUT THE USER'S CURRENT MENTAL STATE: -The user appears frustrated with a billing issue, specifically concerning duplicate charges. +The user appears frustrated with a billing issue, specifically concerning duplicate charges. They seem to have some confidence in the support process as they provided specific details. RELEVANT LONG-TERM FACTS ABOUT THE USER: @@ -119,7 +119,7 @@ const representation = await session.workingRep("user-123"); // Typical content structure: /* PREDICTION ABOUT THE USER'S CURRENT MENTAL STATE: -The user appears frustrated with a billing issue, specifically concerning duplicate charges. +The user appears frustrated with a billing issue, specifically concerning duplicate charges. They seem to have some confidence in the support process as they provided specific details. RELEVANT LONG-TERM FACTS ABOUT THE USER: @@ -272,4 +272,3 @@ Working representations provide fast access to cached psychological models that - Combine cached and fresh analysis appropriately You can build efficient applications that leverage Honcho's continuous learning about peer knowledge and mental states without the latency of real-time generation. - diff --git a/docs/v2/openapi.documented.yml b/docs/v2/openapi.documented.yml index 71711980..f1c649c7 100644 --- a/docs/v2/openapi.documented.yml +++ b/docs/v2/openapi.documented.yml @@ -1033,7 +1033,7 @@ paths: type: object additionalProperties: true title: >- - Response Get Working Representation V2 Workspaces Workspace Id Peers Peer Id + Response Get Working Representation V2 Workspaces Workspace Id Peers Peer Id Representation Post '422': description: Validation Error @@ -2259,7 +2259,7 @@ paths: items: $ref: '#/components/schemas/Message' title: >- - Response Create Messages For Session V2 Workspaces Workspace Id Sessions Session Id + Response Create Messages For Session V2 Workspaces Workspace Id Sessions Session Id Messages Post '422': description: Validation Error diff --git a/migrations/README b/migrations/README index 98e4f9c4..2500aa1b 100644 --- a/migrations/README +++ b/migrations/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/migrations/versions/66e63cf2cf77_add_indexes_to_documents_table.py b/migrations/versions/66e63cf2cf77_add_indexes_to_documents_table.py index 0205fcf0..7d1c0768 100644 --- a/migrations/versions/66e63cf2cf77_add_indexes_to_documents_table.py +++ b/migrations/versions/66e63cf2cf77_add_indexes_to_documents_table.py @@ -33,7 +33,7 @@ def upgrade() -> None: op.execute( text( f""" - CREATE INDEX idx_documents_embedding_hnsw ON {schema}.documents + CREATE INDEX idx_documents_embedding_hnsw ON {schema}.documents USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=64); """ diff --git a/migrations/versions/d429de0e5338_adopt_peer_paradigm.py b/migrations/versions/d429de0e5338_adopt_peer_paradigm.py index 2604de8e..9a8d9844 100644 --- a/migrations/versions/d429de0e5338_adopt_peer_paradigm.py +++ b/migrations/versions/d429de0e5338_adopt_peer_paradigm.py @@ -479,14 +479,14 @@ def update_messages_table(schema: str, inspector) -> None: op.execute( sa.text(f""" - UPDATE {schema}.messages SET + UPDATE {schema}.messages SET workspace_name = (SELECT name FROM {schema}.workspaces WHERE workspaces.id = messages.app_id) """) ) op.execute( sa.text(f""" - UPDATE {schema}.messages SET + UPDATE {schema}.messages SET session_name = (SELECT name FROM {schema}.sessions WHERE sessions.id = messages.session_id) """) ) @@ -494,21 +494,21 @@ def update_messages_table(schema: str, inspector) -> None: op.execute( sa.text(f""" UPDATE {schema}.messages SET - peer_name = CASE + peer_name = CASE WHEN is_user = true THEN ( - SELECT p.name - FROM {schema}.peers p - JOIN {schema}.sessions s ON s.user_id = p.id + SELECT p.name + FROM {schema}.peers p + JOIN {schema}.sessions s ON s.user_id = p.id WHERE s.name = messages.session_name ) ELSE ( - SELECT sp.peer_name - FROM {schema}.session_peers sp - WHERE sp.session_name = messages.session_name + SELECT sp.peer_name + FROM {schema}.session_peers sp + WHERE sp.session_name = messages.session_name AND sp.peer_name != ( - SELECT p.name - FROM {schema}.peers p - JOIN {schema}.sessions s ON s.user_id = p.id + SELECT p.name + FROM {schema}.peers p + JOIN {schema}.sessions s ON s.user_id = p.id WHERE s.name = messages.session_name ) ) @@ -649,7 +649,7 @@ def update_collections_table(schema: str, inspector) -> None: # Populate new columns from existing data op.execute( sa.text(f""" - UPDATE {schema}.collections SET + UPDATE {schema}.collections SET peer_name = (SELECT name FROM {schema}.peers WHERE peers.id = collections.user_id), workspace_name = (SELECT name FROM {schema}.workspaces WHERE workspaces.id = collections.app_id) """) @@ -759,7 +759,7 @@ def update_documents_table(schema: str, inspector) -> None: # Populate new columns from existing data op.execute( sa.text(f""" - UPDATE {schema}.documents SET + UPDATE {schema}.documents SET peer_name = (SELECT name FROM {schema}.peers WHERE peers.id = documents.user_id), workspace_name = (SELECT name FROM {schema}.workspaces WHERE workspaces.id = documents.app_id) """) @@ -811,12 +811,12 @@ def update_documents_table(schema: str, inspector) -> None: # (collection_id contains old collection IDs, we need to get the collection names) op.execute( sa.text(f""" - UPDATE {schema}.documents + UPDATE {schema}.documents SET collection_name = ( - SELECT c.name - FROM {schema}.collections c - WHERE c.id = documents.collection_name - AND c.peer_name = documents.peer_name + SELECT c.name + FROM {schema}.collections c + WHERE c.id = documents.collection_name + AND c.peer_name = documents.peer_name AND c.workspace_name = documents.workspace_name ) """) @@ -1097,7 +1097,7 @@ def backfill_token_counts(schema: str) -> None: connection.execute( text(f""" - UPDATE {schema}.messages + UPDATE {schema}.messages SET token_count = batch_data.token_count FROM ( SELECT UNNEST(:ids) as id, UNNEST(:token_counts) as token_count @@ -1161,7 +1161,7 @@ def restore_app_user_columns(schema: str, inspector) -> None: UPDATE {schema}.sessions SET user_id = ( SELECT p.id FROM {schema}.peers p JOIN {schema}.session_peers sp ON p.name = sp.peer_name AND p.workspace_name = sp.workspace_name - WHERE sp.session_name = sessions.name + WHERE sp.session_name = sessions.name AND p.workspace_name = sessions.workspace_name AND p.id != p.name LIMIT 1 @@ -1185,7 +1185,7 @@ def restore_documents_table(schema: str, inspector) -> None: op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.documents_id_seq") op.execute(f""" - UPDATE {schema}.documents + UPDATE {schema}.documents SET temp_id = nextval('{schema}.documents_id_seq') WHERE temp_id IS NULL """) @@ -1319,7 +1319,7 @@ def restore_collections_table(schema: str, inspector) -> None: op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.collections_id_seq") op.execute(f""" - UPDATE {schema}.collections + UPDATE {schema}.collections SET temp_id = nextval('{schema}.collections_id_seq') WHERE temp_id IS NULL """) @@ -1493,11 +1493,11 @@ def restore_messages_table(schema: str, inspector) -> None: op.execute( sa.text(f""" UPDATE {schema}.messages SET is_user = ( - SELECT CASE - WHEN s.user_id = messages.peer_name THEN true - ELSE false + SELECT CASE + WHEN s.user_id = messages.peer_name THEN true + ELSE false END - FROM {schema}.sessions s + FROM {schema}.sessions s WHERE s.name = messages.session_name ) """) @@ -1567,7 +1567,7 @@ def restore_sessions_table(schema: str, inspector) -> None: op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.sessions_id_seq") op.execute(f""" - UPDATE {schema}.sessions + UPDATE {schema}.sessions SET temp_id = nextval('{schema}.sessions_id_seq') WHERE temp_id IS NULL """) @@ -1638,7 +1638,7 @@ def restore_peers_table(schema: str, inspector) -> None: op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.peers_id_seq") op.execute(f""" - UPDATE {schema}.peers + UPDATE {schema}.peers SET temp_id = nextval('{schema}.peers_id_seq') WHERE temp_id IS NULL """) @@ -1716,7 +1716,7 @@ def restore_workspaces_table(schema: str, inspector) -> None: op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.workspaces_id_seq") op.execute(f""" - UPDATE {schema}.workspaces + UPDATE {schema}.workspaces SET temp_id = nextval('{schema}.workspaces_id_seq') WHERE temp_id IS NULL """) diff --git a/pyproject.toml b/pyproject.toml index 9c536c82..2f3cf8d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,8 @@ dev-dependencies = [ "py-spy>=0.3.14", "ruff>=0.11.2", "basedpyright>=1.29.4", + "pre-commit>=4.2.0", + "pytest-cov>=6.2.1", ] [tool.ruff.lint] @@ -66,23 +68,17 @@ extend-immutable-calls = ["fastapi.Depends"] [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" +addopts = "--strict-markers --cov=src/ --cov-report=term-missing" +testpaths = ["tests"] +pythonpath = ["src"] [tool.basedpyright] # BasedPyright currently seems like the best type checker option, much faster -# than mypy and with a good extension for VSCode/Cursor. -# https://marketplace.visualstudio.com/items?itemName=detachhead.basedpyright # https://docs.basedpyright.com/latest/configuration/config-files/#sample-pyprojecttoml-file include = ["src", "tests", "sdks/python/src"] exclude = ["tests/**/disabled*.py"] -# By default BasedPyright is very strict, so you almost certainly want to disable -# some of the rules. -# First, these turn off warnings about (yes) how you ignore warnings: reportIgnoreCommentWithoutRule = false -# reportUnnecessaryTypeIgnoreComment = false -# A few typically noisy warnings are next. -# How many you enable is up to you. The first few are off by default, but you can -# comment/uncomment these as desired: reportMissingTypeStubs = false reportUnusedCallResult = false reportCallInDefaultInitializer = false @@ -90,14 +86,4 @@ reportAny = false reportExplicitAny = false allowedUntypedLibraries = ["langfuse", "langfuse.decorators", "mirascope"] reportImplicitOverride = false -# reportImplicitStringConcatenation = false -# reportUnreachable = false -# reportPrivateImportUsage = false -# reportPrivateLocalImportUsage = false -# reportMissingImports = false -# reportUnnecessaryIsInstance = false -# reportUnknownVariableType = false -# reportUnknownArgumentType = false -# reportUnknownParameterType = false -# reportUnknownMemberType = false -reportImportCycles = false \ No newline at end of file +reportImportCycles = false diff --git a/scripts/update_version.py b/scripts/update_version.py index 41daa00c..58dbd5d1 100755 --- a/scripts/update_version.py +++ b/scripts/update_version.py @@ -6,50 +6,51 @@ This script helps update version numbers across the Honcho repository. It handles the main API, Python SDK, and TypeScript SDK in a single operation. """ -import os -import sys -import subprocess -import tempfile -import re -from datetime import datetime -from typing import Optional, Dict, List, Tuple import json +import os +import re +import subprocess +import sys +import tempfile +from datetime import datetime class VersionUpdater: def __init__(self, base_path: str): self.base_path = base_path - - def get_current_versions(self) -> Dict[str, str]: + + def get_current_versions(self) -> dict[str, str]: """Get current version numbers from the repository.""" versions = {} - + # Main API version - with open(os.path.join(self.base_path, "pyproject.toml"), "r") as f: + with open(os.path.join(self.base_path, "pyproject.toml")) as f: for line in f: if line.startswith("version = "): versions["api"] = line.split('"')[1] break - + # Python SDK version - with open(os.path.join(self.base_path, "sdks/python/pyproject.toml"), "r") as f: + with open(os.path.join(self.base_path, "sdks/python/pyproject.toml")) as f: for line in f: if line.startswith("version = "): versions["python_sdk"] = line.split('"')[1] break - + # TypeScript SDK version - with open(os.path.join(self.base_path, "sdks/typescript/package.json"), "r") as f: + with open(os.path.join(self.base_path, "sdks/typescript/package.json")) as f: data = json.load(f) versions["typescript_sdk"] = data["version"] - + return versions - - def get_all_versions_from_editor(self, current_versions: Dict[str, str]) -> Dict[str, Dict[str, str]]: + + def get_all_versions_from_editor( + self, current_versions: dict[str, str] + ) -> dict[str, dict[str, str]]: """Open editor to get all version updates at once.""" template = f"""# Honcho Version Update # Enter new version numbers below. Leave blank to skip updating that component. -# +# # MAIN API # Current version: {current_versions['api']} API_VERSION= @@ -73,58 +74,66 @@ TYPESCRIPT_VERSION= # Lines starting with # are comments and will be ignored """ - - with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: + + with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f: f.write(template) temp_file = f.name - + # Open in vim - subprocess.call(['vim', temp_file]) - + subprocess.call(["vim", temp_file]) + # Parse the file - with open(temp_file, 'r') as f: + with open(temp_file) as f: content = f.read() - + os.unlink(temp_file) - + # Extract all versions and changelogs updates = {} - + # Parse API version - api_match = re.search(r'^API_VERSION=(.*)$', content, re.MULTILINE) + api_match = re.search(r"^API_VERSION=(.*)$", content, re.MULTILINE) if api_match and api_match.group(1).strip(): - changelog = self._extract_changelog_between(content, 'API_VERSION=', 'PYTHON_VERSION=') - updates['api'] = { - 'version': api_match.group(1).strip(), - 'changelog': self._clean_changelog_sections(changelog) + changelog = self._extract_changelog_between( + content, "API_VERSION=", "PYTHON_VERSION=" + ) + updates["api"] = { + "version": api_match.group(1).strip(), + "changelog": self._clean_changelog_sections(changelog), } - + # Parse Python SDK version - python_match = re.search(r'^PYTHON_VERSION=(.*)$', content, re.MULTILINE) + python_match = re.search(r"^PYTHON_VERSION=(.*)$", content, re.MULTILINE) if python_match and python_match.group(1).strip(): - changelog = self._extract_changelog_between(content, 'PYTHON_VERSION=', 'TYPESCRIPT_VERSION=') - updates['python_sdk'] = { - 'version': python_match.group(1).strip(), - 'changelog': self._clean_changelog_sections(changelog) + changelog = self._extract_changelog_between( + content, "PYTHON_VERSION=", "TYPESCRIPT_VERSION=" + ) + updates["python_sdk"] = { + "version": python_match.group(1).strip(), + "changelog": self._clean_changelog_sections(changelog), } - + # Parse TypeScript SDK version - ts_match = re.search(r'^TYPESCRIPT_VERSION=(.*)$', content, re.MULTILINE) + ts_match = re.search(r"^TYPESCRIPT_VERSION=(.*)$", content, re.MULTILINE) if ts_match and ts_match.group(1).strip(): - changelog = self._extract_changelog_between(content, 'TYPESCRIPT_VERSION=', None) - updates['typescript_sdk'] = { - 'version': ts_match.group(1).strip(), - 'changelog': self._clean_changelog_sections(changelog) + changelog = self._extract_changelog_between( + content, "TYPESCRIPT_VERSION=", None + ) + updates["typescript_sdk"] = { + "version": ts_match.group(1).strip(), + "changelog": self._clean_changelog_sections(changelog), } - + return updates - - def _extract_changelog_between(self, content: str, start_marker: str, end_marker: Optional[str]) -> str: + + def _extract_changelog_between( + self, content: str, start_marker: str, end_marker: str | None + ) -> str: """Extract changelog content between markers.""" - lines = content.split('\n') + lines = content.split("\n") changelog_lines = [] in_section = False - + for line in lines: if start_marker in line: in_section = True @@ -133,435 +142,478 @@ TYPESCRIPT_VERSION= break if in_section: # Skip comment lines but keep markdown headers - if line.strip().startswith('# ') and not line.strip().startswith('###'): + if line.strip().startswith("# ") and not line.strip().startswith("###"): continue - if line.strip() == '#': + if line.strip() == "#": continue changelog_lines.append(line) - + # Remove trailing empty lines while changelog_lines and not changelog_lines[-1].strip(): changelog_lines.pop() - - return '\n'.join(changelog_lines).strip() - + + return "\n".join(changelog_lines).strip() + def _clean_changelog_sections(self, changelog: str) -> str: """Remove empty changelog sections.""" - sections = ['Added', 'Changed', 'Fixed', 'Deprecated', 'Removed', 'Security'] - lines = changelog.split('\n') + sections = ["Added", "Changed", "Fixed", "Deprecated", "Removed", "Security"] + lines = changelog.split("\n") cleaned_lines = [] current_section = None section_has_content = False section_start_idx = -1 - + for i, line in enumerate(lines): # Check if this is a section header is_section_header = False for section in sections: - if line.strip() == f'### {section}': + if line.strip() == f"### {section}": # If we have a previous section, decide whether to keep it if current_section is not None and section_start_idx != -1: if section_has_content: # Keep the section cleaned_lines.extend(lines[section_start_idx:i]) - + # Start tracking new section current_section = section section_start_idx = i section_has_content = False is_section_header = True break - + if not is_section_header and current_section is not None: # Check if this line has content (not empty and not just whitespace) - if line.strip() and not line.strip().startswith('#'): + if line.strip() and not line.strip().startswith("#"): section_has_content = True - + # Handle the last section if current_section is not None and section_start_idx != -1: if section_has_content: cleaned_lines.extend(lines[section_start_idx:]) - + # If no sections were found, return original - if not cleaned_lines and '###' not in changelog: + if not cleaned_lines and "###" not in changelog: return changelog - - return '\n'.join(cleaned_lines).strip() - - def update_all(self, updates: Dict[str, Dict[str, str]], current_versions: Dict[str, str]): + + return "\n".join(cleaned_lines).strip() + + def update_all( + self, updates: dict[str, dict[str, str]], current_versions: dict[str, str] + ): """Update all components that have new versions.""" # Update API if specified - if 'api' in updates: + if "api" in updates: print(f"\nUpdating API version to {updates['api']['version']}...") - self.update_api_version(updates['api']['version'], updates['api']['changelog']) - + self.update_api_version( + updates["api"]["version"], updates["api"]["changelog"] + ) + # Update compatibility guide for new API version self._update_compatibility_guide_for_api( - updates['api']['version'], - updates.get('python_sdk', {}).get('version', current_versions['python_sdk']), - updates.get('typescript_sdk', {}).get('version', current_versions['typescript_sdk']) + updates["api"]["version"], + updates.get("python_sdk", {}).get( + "version", current_versions["python_sdk"] + ), + updates.get("typescript_sdk", {}).get( + "version", current_versions["typescript_sdk"] + ), ) - + # Update Python SDK if specified - if 'python_sdk' in updates: - print(f"Updating Python SDK version to {updates['python_sdk']['version']}...") - self.update_python_sdk_version(updates['python_sdk']['version'], updates['python_sdk']['changelog']) - + if "python_sdk" in updates: + print( + f"Updating Python SDK version to {updates['python_sdk']['version']}..." + ) + self.update_python_sdk_version( + updates["python_sdk"]["version"], updates["python_sdk"]["changelog"] + ) + # Update TypeScript SDK if specified - if 'typescript_sdk' in updates: - print(f"Updating TypeScript SDK version to {updates['typescript_sdk']['version']}...") - self.update_typescript_sdk_version(updates['typescript_sdk']['version'], updates['typescript_sdk']['changelog']) - + if "typescript_sdk" in updates: + print( + f"Updating TypeScript SDK version to {updates['typescript_sdk']['version']}..." + ) + self.update_typescript_sdk_version( + updates["typescript_sdk"]["version"], + updates["typescript_sdk"]["changelog"], + ) + def update_api_version(self, new_version: str, changelog: str): """Update main API version across all files.""" updates = [ # Simple replacements { - 'file': 'pyproject.toml', - 'pattern': r'version = "[^"]*"', - 'replacement': f'version = "{new_version}"' + "file": "pyproject.toml", + "pattern": r'version = "[^"]*"', + "replacement": f'version = "{new_version}"', }, { - 'file': 'src/main.py', - 'pattern': r'version="[^"]*"', - 'replacement': f'version="{new_version}"' + "file": "src/main.py", + "pattern": r'version="[^"]*"', + "replacement": f'version="{new_version}"', }, { - 'file': 'README.md', - 'pattern': r'Version-\d+\.\d+\.\d+-blue', - 'replacement': f'Version-{new_version}-blue' + "file": "README.md", + "pattern": r"Version-\d+\.\d+\.\d+-blue", + "replacement": f"Version-{new_version}-blue", }, # docs.json is handled separately to only update same major version ] - + # Apply simple updates for update in updates: - file_path = os.path.join(self.base_path, update['file']) - with open(file_path, 'r') as f: + file_path = os.path.join(self.base_path, update["file"]) + with open(file_path) as f: content = f.read() - - content = re.sub(update['pattern'], update['replacement'], content) - - with open(file_path, 'w') as f: + + content = re.sub(update["pattern"], update["replacement"], content) + + with open(file_path, "w") as f: f.write(content) - + # Update docs.json - only update same major version self._update_docs_json(new_version) - + # Update CHANGELOG.md (prepend new entry) self._update_changelog_md(new_version, changelog) - + # Update docs changelog (MDX format) - self._update_docs_changelog(new_version, changelog, 'api') - + self._update_docs_changelog(new_version, changelog, "api") + def update_python_sdk_version(self, new_version: str, changelog: str): """Update Python SDK version.""" updates = [ { - 'file': 'sdks/python/pyproject.toml', - 'pattern': r'version = "[^"]*"', - 'replacement': f'version = "{new_version}"' + "file": "sdks/python/pyproject.toml", + "pattern": r'version = "[^"]*"', + "replacement": f'version = "{new_version}"', }, { - 'file': 'sdks/python/src/honcho/__init__.py', - 'pattern': r'__version__ = "[^"]*"', - 'replacement': f'__version__ = "{new_version}"' - } + "file": "sdks/python/src/honcho/__init__.py", + "pattern": r'__version__ = "[^"]*"', + "replacement": f'__version__ = "{new_version}"', + }, ] - + for update in updates: - file_path = os.path.join(self.base_path, update['file']) - with open(file_path, 'r') as f: + file_path = os.path.join(self.base_path, update["file"]) + with open(file_path) as f: content = f.read() - - content = re.sub(update['pattern'], update['replacement'], content) - - with open(file_path, 'w') as f: + + content = re.sub(update["pattern"], update["replacement"], content) + + with open(file_path, "w") as f: f.write(content) - + # Update SDK's own CHANGELOG.md - self._update_sdk_changelog(new_version, changelog, 'sdks/python/CHANGELOG.md') - + self._update_sdk_changelog(new_version, changelog, "sdks/python/CHANGELOG.md") + # Update docs changelog - self._update_docs_changelog(new_version, changelog, 'python_sdk') - + self._update_docs_changelog(new_version, changelog, "python_sdk") + # Update compatibility guide SDK version - self._update_compatibility_guide('python', new_version) - + self._update_compatibility_guide("python", new_version) + def update_typescript_sdk_version(self, new_version: str, changelog: str): """Update TypeScript SDK version.""" # Update package.json - file_path = os.path.join(self.base_path, 'sdks/typescript/package.json') - with open(file_path, 'r') as f: + file_path = os.path.join(self.base_path, "sdks/typescript/package.json") + with open(file_path) as f: data = json.load(f) - - data['version'] = new_version - - with open(file_path, 'w') as f: + + data["version"] = new_version + + with open(file_path, "w") as f: json.dump(data, f, indent=2) - f.write('\n') # Add trailing newline - + f.write("\n") # Add trailing newline + # Update SDK's own CHANGELOG.md - self._update_sdk_changelog(new_version, changelog, 'sdks/typescript/CHANGELOG.md') - + self._update_sdk_changelog( + new_version, changelog, "sdks/typescript/CHANGELOG.md" + ) + # Update docs changelog - self._update_docs_changelog(new_version, changelog, 'typescript_sdk') - + self._update_docs_changelog(new_version, changelog, "typescript_sdk") + # Update compatibility guide SDK version - self._update_compatibility_guide('typescript', new_version) - + self._update_compatibility_guide("typescript", new_version) + def _update_docs_json(self, new_version: str): """Update docs.json - only update versions with same major version.""" - file_path = os.path.join(self.base_path, 'docs/docs.json') - - with open(file_path, 'r') as f: + file_path = os.path.join(self.base_path, "docs/docs.json") + + with open(file_path) as f: data = json.load(f) - + # Get major version of new version - new_major = new_version.split('.')[0] - + new_major = new_version.split(".")[0] + # Update only matching major versions - if 'navigation' in data and 'versions' in data['navigation']: - for version_entry in data['navigation']['versions']: - if 'version' in version_entry: - current_version = version_entry['version'].lstrip('v') - current_major = current_version.split('.')[0] - + if "navigation" in data and "versions" in data["navigation"]: + for version_entry in data["navigation"]["versions"]: + if "version" in version_entry: + current_version = version_entry["version"].lstrip("v") + current_major = current_version.split(".")[0] + if current_major == new_major: - version_entry['version'] = f'v{new_version}' - - with open(file_path, 'w') as f: + version_entry["version"] = f"v{new_version}" + + with open(file_path, "w") as f: json.dump(data, f, indent=2) - f.write('\n') - + f.write("\n") + def _update_sdk_changelog(self, version: str, changelog: str, relative_path: str): """Update an SDK's CHANGELOG.md file.""" file_path = os.path.join(self.base_path, relative_path) - - with open(file_path, 'r') as f: + + with open(file_path) as f: content = f.read() - + # Find the position after the header - header_end = content.find('\n## [') + header_end = content.find("\n## [") if header_end == -1: - header_end = content.find('\n##') - + header_end = content.find("\n##") + if header_end == -1: # No existing entries, add after title section - header_end = content.find('and this project adheres to') + header_end = content.find("and this project adheres to") if header_end != -1: - header_end = content.find('\n', header_end) - + header_end = content.find("\n", header_end) + # Create new entry with proper formatting - date = datetime.now().strftime('%Y-%m-%d') - + date = datetime.now().strftime("%Y-%m-%d") + # Ensure changelog content is properly formatted if changelog.strip(): formatted_changelog = changelog.strip() else: formatted_changelog = "### Changed\n\n- Updated version" - + new_entry = f"\n\n## [{version}] - {date}\n\n{formatted_changelog}\n" - + # Insert the new entry new_content = content[:header_end] + new_entry + content[header_end:] - - with open(file_path, 'w') as f: + + with open(file_path, "w") as f: f.write(new_content) - + def _update_changelog_md(self, version: str, changelog: str): """Update the main CHANGELOG.md file.""" - file_path = os.path.join(self.base_path, 'CHANGELOG.md') - - with open(file_path, 'r') as f: + file_path = os.path.join(self.base_path, "CHANGELOG.md") + + with open(file_path) as f: content = f.read() - + # Find the position after the header - header_end = content.find('\n## [') + header_end = content.find("\n## [") if header_end == -1: - header_end = content.find('\n##') - + header_end = content.find("\n##") + if header_end == -1: # No existing entries, add after title - header_end = content.find('\n', content.find('# Changelog')) - + header_end = content.find("\n", content.find("# Changelog")) + # Create new entry with proper formatting - date = datetime.now().strftime('%Y-%m-%d') - + date = datetime.now().strftime("%Y-%m-%d") + # Ensure changelog content is properly formatted if changelog.strip(): formatted_changelog = changelog.strip() else: formatted_changelog = "### Changed\n\n- Updated version" - + new_entry = f"\n\n## [{version}] - {date}\n\n{formatted_changelog}\n" - + # Insert the new entry new_content = content[:header_end] + new_entry + content[header_end:] - - with open(file_path, 'w') as f: + + with open(file_path, "w") as f: f.write(new_content) - + def _update_docs_changelog(self, version: str, changelog: str, component: str): """Update the docs/changelog/introduction.mdx file.""" - file_path = os.path.join(self.base_path, 'docs/changelog/introduction.mdx') - - with open(file_path, 'r') as f: + file_path = os.path.join(self.base_path, "docs/changelog/introduction.mdx") + + with open(file_path) as f: content = f.read() - - if component == 'api': + + if component == "api": # Find the Honcho API tab content tab_start = content.find('') if tab_start == -1: return - + # Find where to insert (after the Tab opening) - insert_pos = content.find('\n', tab_start) + 1 - + insert_pos = content.find("\n", tab_start) + 1 + # Format the changelog with proper indentation - indented_changelog = '\n'.join(' ' + line if line.strip() else '' - for line in changelog.strip().split('\n')) - + indented_changelog = "\n".join( + " " + line if line.strip() else "" + for line in changelog.strip().split("\n") + ) + # Create the new update entry new_entry = f""" {indented_changelog} - + """ - + # Remove (Current) from previous entries # Use a more specific pattern to avoid replacing in other contexts content = re.sub( - r'(' tab_start = content.find(tab_pattern) if tab_start == -1: return content - + # Format the changelog with proper indentation - indented_changelog = '\n'.join(' ' + line if line.strip() else '' - for line in changelog.strip().split('\n')) - + indented_changelog = "\n".join( + " " + line if line.strip() else "" + for line in changelog.strip().split("\n") + ) + # Create new update entry new_entry = f""" {indented_changelog} """ - + # Remove (Current) from previous SDK entries # More precise pattern to avoid issues pattern = rf'(.*?)' - content = re.sub(pattern, r'\1\2', content, flags=re.DOTALL) - + content = re.sub(pattern, r"\1\2", content, flags=re.DOTALL) + # Find where to insert the new entry # Look for the line after the SDK link (e.g., [Python SDK](...)) tab_pos = content.find(tab_pattern) if tab_pos == -1: return content - + # Find the end of the SDK link line - link_start = content.find('[', tab_pos) + link_start = content.find("[", tab_pos) if link_start != -1: - link_end = content.find('\n', link_start) + link_end = content.find("\n", link_start) if link_end != -1: insert_pos = link_end + 1 content = content[:insert_pos] + new_entry + content[insert_pos:] - + return content - + def _update_compatibility_guide(self, sdk_type: str, version: str): """Update the compatibility guide with new SDK version.""" - file_path = os.path.join(self.base_path, 'docs/changelog/compatibility-guide.mdx') - - with open(file_path, 'r') as f: + file_path = os.path.join( + self.base_path, "docs/changelog/compatibility-guide.mdx" + ) + + with open(file_path) as f: content = f.read() - - if sdk_type == 'typescript': + + if sdk_type == "typescript": # Update in the card content = re.sub( r'( { await expect(honcho.search('test')).rejects.toThrow('Search failed'); }); }); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/__tests__/integration.test.ts b/sdks/typescript/__tests__/integration.test.ts index fa1dd784..7148244a 100644 --- a/sdks/typescript/__tests__/integration.test.ts +++ b/sdks/typescript/__tests__/integration.test.ts @@ -415,4 +415,4 @@ describe('Honcho SDK Integration Tests', () => { } }); }); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/__tests__/pagination.test.ts b/sdks/typescript/__tests__/pagination.test.ts index d347ac22..4be9e7d0 100644 --- a/sdks/typescript/__tests__/pagination.test.ts +++ b/sdks/typescript/__tests__/pagination.test.ts @@ -25,7 +25,7 @@ describe('Page', () => { describe('constructor', () => { it('should initialize with original page', () => { const page = new Page(mockOriginalPage); - + expect(page['_originalPage']).toBe(mockOriginalPage); expect(page['_transformFunc']).toBeUndefined(); }); @@ -33,7 +33,7 @@ describe('Page', () => { it('should initialize with transform function', () => { const transformFunc = (item: any) => ({ ...item, transformed: true }); const page = new Page(mockOriginalPage, transformFunc); - + expect(page['_originalPage']).toBe(mockOriginalPage); expect(page['_transformFunc']).toBe(transformFunc); }); @@ -43,11 +43,11 @@ describe('Page', () => { it('should iterate through items without transform', async () => { const page = new Page(mockOriginalPage); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual(mockItems); }); @@ -55,11 +55,11 @@ describe('Page', () => { const transformFunc = (item: any) => ({ ...item, transformed: true }); const page = new Page(mockOriginalPage, transformFunc); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual([ { id: 'item1', name: 'Item 1', transformed: true }, { id: 'item2', name: 'Item 2', transformed: true }, @@ -71,11 +71,11 @@ describe('Page', () => { const emptyPage = { items: [], data: [], size: 0, total: 0, hasNextPage: false }; const page = new Page(emptyPage); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual([]); }); @@ -88,11 +88,11 @@ describe('Page', () => { }; const page = new Page(pageWithData); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual(mockItems); }); @@ -104,11 +104,11 @@ describe('Page', () => { }; const page = new Page(pageWithoutItems); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual([]); }); }); @@ -117,9 +117,9 @@ describe('Page', () => { it('should get item by index without transform', async () => { mockOriginalPage.get.mockResolvedValue(mockItems[1]); const page = new Page(mockOriginalPage); - + const item = await page.get(1); - + expect(item).toEqual(mockItems[1]); expect(mockOriginalPage.get).toHaveBeenCalledWith(1); }); @@ -128,9 +128,9 @@ describe('Page', () => { const transformFunc = (item: any) => ({ ...item, transformed: true }); mockOriginalPage.get.mockResolvedValue(mockItems[1]); const page = new Page(mockOriginalPage, transformFunc); - + const item = await page.get(1); - + expect(item).toEqual({ ...mockItems[1], transformed: true }); expect(mockOriginalPage.get).toHaveBeenCalledWith(1); }); @@ -138,9 +138,9 @@ describe('Page', () => { it('should handle out of bounds index', async () => { mockOriginalPage.get.mockResolvedValue(undefined); const page = new Page(mockOriginalPage); - + const item = await page.get(999); - + expect(item).toBeUndefined(); expect(mockOriginalPage.get).toHaveBeenCalledWith(999); }); @@ -148,9 +148,9 @@ describe('Page', () => { it('should handle negative index', async () => { mockOriginalPage.get.mockResolvedValue(undefined); const page = new Page(mockOriginalPage); - + const item = await page.get(-1); - + expect(item).toBeUndefined(); expect(mockOriginalPage.get).toHaveBeenCalledWith(-1); }); @@ -159,14 +159,14 @@ describe('Page', () => { describe('size getter', () => { it('should return size from original page', () => { const page = new Page(mockOriginalPage); - + expect(page.size).toBe(3); }); it('should handle missing size', () => { const pageWithoutSize = { items: mockItems }; const page = new Page(pageWithoutSize); - + expect(page.size).toBeUndefined(); }); }); @@ -174,14 +174,14 @@ describe('Page', () => { describe('total getter', () => { it('should return total from original page', () => { const page = new Page(mockOriginalPage); - + expect(page.total).toBe(10); }); it('should handle missing total', () => { const pageWithoutTotal = { items: mockItems }; const page = new Page(pageWithoutTotal); - + expect(page.total).toBeUndefined(); }); }); @@ -189,18 +189,18 @@ describe('Page', () => { describe('data', () => { it('should return data array without transform', async () => { const page = new Page(mockOriginalPage); - + const data = await page.data(); - + expect(data).toEqual(mockItems); }); it('should return data array with transform', async () => { const transformFunc = (item: any) => ({ ...item, transformed: true }); const page = new Page(mockOriginalPage, transformFunc); - + const data = await page.data(); - + expect(data).toEqual([ { id: 'item1', name: 'Item 1', transformed: true }, { id: 'item2', name: 'Item 2', transformed: true }, @@ -217,9 +217,9 @@ describe('Page', () => { hasNextPage: true, }; const page = new Page(pageWithDataFunction); - + const data = await page.data(); - + expect(data).toEqual(mockItems); expect(mockDataFunction).toHaveBeenCalled(); }); @@ -233,9 +233,9 @@ describe('Page', () => { hasNextPage: true, }; const page = new Page(pageWithItemsFunction); - + const data = await page.data(); - + expect(data).toEqual(mockItems); expect(mockItemsFunction).toHaveBeenCalled(); }); @@ -243,9 +243,9 @@ describe('Page', () => { it('should handle empty data', async () => { const emptyPage = { items: [], size: 0, total: 0, hasNextPage: false }; const page = new Page(emptyPage); - + const data = await page.data(); - + expect(data).toEqual([]); }); @@ -258,9 +258,9 @@ describe('Page', () => { hasNextPage: false, }; const page = new Page(pageWithBoth); - + const data = await page.data(); - + expect(data).toEqual([{ id: 'from-items' }]); }); }); @@ -268,21 +268,21 @@ describe('Page', () => { describe('hasNextPage getter', () => { it('should return hasNextPage from original page', () => { const page = new Page(mockOriginalPage); - + expect(page.hasNextPage).toBe(true); }); it('should handle missing hasNextPage', () => { const pageWithoutHasNextPage = { items: mockItems }; const page = new Page(pageWithoutHasNextPage); - + expect(page.hasNextPage).toBeUndefined(); }); it('should handle false hasNextPage', () => { const lastPage = { ...mockOriginalPage, hasNextPage: false }; const page = new Page(lastPage); - + expect(page.hasNextPage).toBe(false); }); }); @@ -298,9 +298,9 @@ describe('Page', () => { const transformFunc = (item: any) => ({ ...item, transformed: true }); mockOriginalPage.nextPage.mockResolvedValue(nextPageData); const page = new Page(mockOriginalPage, transformFunc); - + const nextPage = await page.nextPage(); - + expect(nextPage).toBeInstanceOf(Page); expect(nextPage!['_transformFunc']).toBe(transformFunc); expect(mockOriginalPage.nextPage).toHaveBeenCalled(); @@ -309,25 +309,25 @@ describe('Page', () => { it('should return null when no next page', async () => { mockOriginalPage.nextPage.mockResolvedValue(null); const page = new Page(mockOriginalPage); - + const nextPage = await page.nextPage(); - + expect(nextPage).toBeNull(); }); it('should handle next page returning undefined', async () => { mockOriginalPage.nextPage.mockResolvedValue(undefined); const page = new Page(mockOriginalPage); - + const nextPage = await page.nextPage(); - + expect(nextPage).toBeNull(); }); it('should handle error from original page nextPage', async () => { mockOriginalPage.nextPage.mockRejectedValue(new Error('Failed to get next page')); const page = new Page(mockOriginalPage); - + await expect(page.nextPage()).rejects.toThrow('Failed to get next page'); }); }); @@ -335,7 +335,7 @@ describe('Page', () => { describe('edge cases and error handling', () => { it('should handle null original page', () => { const page = new Page(null); - + expect(page['_originalPage']).toBeNull(); expect(page.size).toBeUndefined(); expect(page.total).toBeUndefined(); @@ -347,7 +347,7 @@ describe('Page', () => { throw new Error('Transform error'); }; const page = new Page(mockOriginalPage, errorTransform); - + await expect(async () => { for await (const item of page) { // This should throw @@ -359,11 +359,11 @@ describe('Page', () => { const nullTransform = () => null; const page = new Page(mockOriginalPage, nullTransform); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual([null, null, null]); }); @@ -377,12 +377,12 @@ describe('Page', () => { }; const page = new Page(largePage); let count = 0; - + for await (const item of page) { count++; if (count > 10) break; // Don't actually iterate through all 10k items } - + expect(count).toBe(11); }); @@ -397,11 +397,11 @@ describe('Page', () => { }; const page = new Page(pageWithCircular); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toHaveLength(1); expect(items[0].id).toBe('circular'); expect(items[0].self).toBe(items[0]); @@ -428,11 +428,11 @@ describe('Page', () => { }; const page = new Page(complexPage); const items: any[] = []; - + for await (const item of page) { items.push(item); } - + expect(items).toEqual(complexItems); expect(items[0].nested.deep.array[2].nested).toBe('array object'); }); @@ -444,10 +444,10 @@ describe('Page', () => { }; mockOriginalPage.get.mockResolvedValue(mockItems[0]); const page = new Page(mockOriginalPage, asyncTransform); - + const item = await page.get(0); - + expect(item).toEqual({ ...mockItems[0], asyncTransformed: true }); }); }); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/__tests__/peer.test.ts b/sdks/typescript/__tests__/peer.test.ts index f505692c..e6779d2e 100644 --- a/sdks/typescript/__tests__/peer.test.ts +++ b/sdks/typescript/__tests__/peer.test.ts @@ -402,4 +402,4 @@ describe('Peer', () => { await expect(peer.search('test')).rejects.toThrow('Search failed'); }); }); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/__tests__/session.test.ts b/sdks/typescript/__tests__/session.test.ts index 22d3b421..8f735f19 100644 --- a/sdks/typescript/__tests__/session.test.ts +++ b/sdks/typescript/__tests__/session.test.ts @@ -693,4 +693,4 @@ describe('Session', () => { await expect(session.workingRep('peer1')).rejects.toThrow('Failed to get working representation'); }); }); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/__tests__/session_context.test.ts b/sdks/typescript/__tests__/session_context.test.ts index 459691e1..91883212 100644 --- a/sdks/typescript/__tests__/session_context.test.ts +++ b/sdks/typescript/__tests__/session_context.test.ts @@ -12,7 +12,7 @@ describe('SessionContext', () => { { id: 'msg3', content: 'How are you?', peer_name: 'user' }, { id: 'msg4', content: 'I am doing well, thank you!', peer_name: 'assistant' }, ]; - + sessionContext = new SessionContext('test-session', mockMessages, 'This is a summary'); }); @@ -25,7 +25,7 @@ describe('SessionContext', () => { it('should initialize with empty summary when not provided', () => { const context = new SessionContext('session-id', mockMessages); - + expect(context.sessionId).toBe('session-id'); expect(context.messages).toEqual(mockMessages); expect(context.summary).toBe(''); @@ -33,7 +33,7 @@ describe('SessionContext', () => { it('should handle empty messages array', () => { const context = new SessionContext('session-id', [], 'No messages'); - + expect(context.sessionId).toBe('session-id'); expect(context.messages).toEqual([]); expect(context.summary).toBe('No messages'); @@ -42,7 +42,7 @@ describe('SessionContext', () => { it('should handle null/undefined summary', () => { const context1 = new SessionContext('session-id', mockMessages, undefined as any); const context2 = new SessionContext('session-id', mockMessages, null as any); - + expect(context1.summary).toBe(''); expect(context2.summary).toBe(''); }); @@ -51,7 +51,7 @@ describe('SessionContext', () => { describe('toOpenAI', () => { it('should convert messages to OpenAI format with string assistant', () => { const openAIMessages = sessionContext.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: 'Hello' }, { role: 'user', content: 'Hi there' }, @@ -63,9 +63,9 @@ describe('SessionContext', () => { it('should convert messages to OpenAI format with Peer object', () => { const mockHoncho = {} as any; const assistantPeer = new Peer('assistant', mockHoncho); - + const openAIMessages = sessionContext.toOpenAI(assistantPeer); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: 'Hello' }, { role: 'user', content: 'Hi there' }, @@ -76,7 +76,7 @@ describe('SessionContext', () => { it('should handle messages where assistant is different peer', () => { const openAIMessages = sessionContext.toOpenAI('different-assistant'); - + expect(openAIMessages).toEqual([ { role: 'user', content: 'Hello' }, { role: 'user', content: 'Hi there' }, @@ -88,7 +88,7 @@ describe('SessionContext', () => { it('should handle empty messages array', () => { const emptyContext = new SessionContext('session-id', []); const openAIMessages = emptyContext.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([]); }); @@ -99,9 +99,9 @@ describe('SessionContext', () => { { id: 'msg3', content: 'Another message', peer_name: null }, ]; const context = new SessionContext('test', messagesWithMissingPeer); - + const openAIMessages = context.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: 'Hello' }, { role: 'user', content: 'No peer' }, @@ -116,9 +116,9 @@ describe('SessionContext', () => { { id: 'msg3', content: ' whitespace ', peer_name: 'assistant' }, ]; const context = new SessionContext('test', complexMessages); - + const openAIMessages = context.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: 'Message with\nnewlines and special chars!@#$%' }, { role: 'user', content: '' }, @@ -130,7 +130,7 @@ describe('SessionContext', () => { describe('toAnthropic', () => { it('should convert messages to Anthropic format with string assistant', () => { const anthropicMessages = sessionContext.toAnthropic('assistant'); - + expect(anthropicMessages).toEqual([ { role: 'assistant', content: 'Hello' }, { role: 'user', content: 'Hi there' }, @@ -142,9 +142,9 @@ describe('SessionContext', () => { it('should convert messages to Anthropic format with Peer object', () => { const mockHoncho = {} as any; const assistantPeer = new Peer('assistant', mockHoncho); - + const anthropicMessages = sessionContext.toAnthropic(assistantPeer); - + expect(anthropicMessages).toEqual([ { role: 'assistant', content: 'Hello' }, { role: 'user', content: 'Hi there' }, @@ -155,7 +155,7 @@ describe('SessionContext', () => { it('should handle messages where assistant is different peer', () => { const anthropicMessages = sessionContext.toAnthropic('different-assistant'); - + expect(anthropicMessages).toEqual([ { role: 'user', content: 'Hello' }, { role: 'user', content: 'Hi there' }, @@ -167,7 +167,7 @@ describe('SessionContext', () => { it('should handle empty messages array', () => { const emptyContext = new SessionContext('session-id', []); const anthropicMessages = emptyContext.toAnthropic('assistant'); - + expect(anthropicMessages).toEqual([]); }); @@ -178,9 +178,9 @@ describe('SessionContext', () => { { id: 'msg3', content: 'Another message', peer_name: undefined }, ]; const context = new SessionContext('test', messagesWithMissingPeer); - + const anthropicMessages = context.toAnthropic('assistant'); - + expect(anthropicMessages).toEqual([ { role: 'assistant', content: 'Hello' }, { role: 'user', content: 'No peer' }, @@ -224,7 +224,7 @@ describe('SessionContext', () => { peer_name: i % 2 === 0 ? 'assistant' : 'user', })); const context = new SessionContext('session-id', manyMessages); - + const result = context.toString(); expect(result).toBe('SessionContext(messages=1000)'); }); @@ -237,9 +237,9 @@ describe('SessionContext', () => { { id: 'msg2', content: undefined, peer_name: 'user' }, ]; const context = new SessionContext('test', messagesWithNullContent); - + const openAIMessages = context.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: null }, { role: 'user', content: undefined }, @@ -253,9 +253,9 @@ describe('SessionContext', () => { { id: 'msg3', content: true, peer_name: 'assistant' }, ]; const context = new SessionContext('test', messagesWithNonStringContent); - + const openAIMessages = context.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: 123 }, { role: 'user', content: { text: 'object content' } }, @@ -267,7 +267,7 @@ describe('SessionContext', () => { const longSessionId = 'x'.repeat(1000); const longSummary = 'Very long summary that goes on and on...'.repeat(100); const context = new SessionContext(longSessionId, mockMessages, longSummary); - + expect(context.sessionId).toBe(longSessionId); expect(context.summary).toBe(longSummary); expect(context.length).toBe(4); @@ -275,9 +275,9 @@ describe('SessionContext', () => { it('should handle messages with additional properties', () => { const messagesWithExtraProps = [ - { - id: 'msg1', - content: 'Hello', + { + id: 'msg1', + content: 'Hello', peer_name: 'assistant', timestamp: '2023-01-01T00:00:00Z', metadata: { important: true }, @@ -285,9 +285,9 @@ describe('SessionContext', () => { }, ]; const context = new SessionContext('test', messagesWithExtraProps); - + const openAIMessages = context.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'assistant', content: 'Hello' }, ]); @@ -300,9 +300,9 @@ describe('SessionContext', () => { { id: 'msg3', content: 'Hey', peer_name: 'assistant' }, ]; const context = new SessionContext('test', caseMessages); - + const openAIMessages = context.toOpenAI('assistant'); - + expect(openAIMessages).toEqual([ { role: 'user', content: 'Hello' }, // 'Assistant' != 'assistant' { role: 'user', content: 'Hi' }, // 'ASSISTANT' != 'assistant' @@ -316,7 +316,7 @@ describe('SessionContext', () => { { peer_name: 'user', content: 'Another message' }, ]; const context = new SessionContext('test', messagesWithoutId); - + expect(context.length).toBe(2); expect(context.toOpenAI('assistant')).toEqual([ { role: 'assistant', content: 'Message without ID' }, @@ -324,4 +324,4 @@ describe('SessionContext', () => { ]); }); }); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/__tests__/setup.ts b/sdks/typescript/__tests__/setup.ts index 069c490a..a0a00106 100644 --- a/sdks/typescript/__tests__/setup.ts +++ b/sdks/typescript/__tests__/setup.ts @@ -16,4 +16,4 @@ afterAll(() => { }); // Mock environment variables for tests -process.env.NODE_ENV = 'test'; \ No newline at end of file +process.env.NODE_ENV = 'test'; diff --git a/sdks/typescript/examples/get_context.ts b/sdks/typescript/examples/get_context.ts index 41a8c060..e0c22e4f 100644 --- a/sdks/typescript/examples/get_context.ts +++ b/sdks/typescript/examples/get_context.ts @@ -2,7 +2,7 @@ import { Honcho } from '../src'; /** * Example demonstrating how to get context from a session with summary and token limits. - * + * * This creates a session with random messages and retrieves context * with a low token limit to demonstrate the summarization feature. */ @@ -49,4 +49,4 @@ async function main() { main().catch((err) => { console.error('Error running get_context example:', err); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/examples/get_representation.ts b/sdks/typescript/examples/get_representation.ts index ce1c61d9..8746f908 100644 --- a/sdks/typescript/examples/get_representation.ts +++ b/sdks/typescript/examples/get_representation.ts @@ -2,7 +2,7 @@ import { Honcho } from '../src'; /** * Example demonstrating how to get peer representations. - * + * * This creates a session with random messages and retrieves both * global and local representations for a peer. */ @@ -56,4 +56,4 @@ async function main() { main().catch((err) => { console.error('Error running get_representation example:', err); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/examples/search.ts b/sdks/typescript/examples/search.ts index 412d0d1d..ab6818e4 100644 --- a/sdks/typescript/examples/search.ts +++ b/sdks/typescript/examples/search.ts @@ -2,7 +2,7 @@ import { Honcho } from '../src'; /** * Example demonstrating search functionality across different scopes. - * + * * This creates sessions with special keywords and demonstrates * searching at session, workspace, and peer levels. */ @@ -74,4 +74,4 @@ async function main() { main().catch((err) => { console.error('Error running search example:', err); -}); \ No newline at end of file +}); diff --git a/sdks/typescript/jest.config.js b/sdks/typescript/jest.config.js index 71ea0f49..0209f4df 100644 --- a/sdks/typescript/jest.config.js +++ b/sdks/typescript/jest.config.js @@ -17,4 +17,4 @@ export default { '^@honcho-ai/core$': '/__tests__/__mocks__/@honcho-ai/core.ts', }, testTimeout: 10000, -}; \ No newline at end of file +}; diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 98edbb2f..884bbea8 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -14,6 +14,7 @@ "lint": "biome check src/", "lint:fix": "biome check src/ --write", "format": "biome format src/ --write", + "typecheck": "tsc --noEmit", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage" diff --git a/sdks/typescript/tsconfig.json b/sdks/typescript/tsconfig.json index f4c23d99..91a7c411 100644 --- a/sdks/typescript/tsconfig.json +++ b/sdks/typescript/tsconfig.json @@ -22,4 +22,4 @@ "dist" ], "baseUrl": ".", -} \ No newline at end of file +} diff --git a/src/deriver/prompts.py b/src/deriver/prompts.py index 5e4954de..6615f254 100644 --- a/src/deriver/prompts.py +++ b/src/deriver/prompts.py @@ -46,17 +46,17 @@ Your goal is to IMPROVE understanding of the user through careful analysis. Your Here are strict definitions for the reasoning modes you are to employ: 1. **EXPLICIT REASONING**: - - Conclusions about the user that MUST be true given premises ONLY of the following types: + - Conclusions about the user that MUST be true given premises ONLY of the following types: - Most recent user message - Knowledge about the conversation history - - Current date and time (which is: {message_created_at}) + - Current date and time (which is: {message_created_at}) - Timestamps from conversation history 2. **DEDUCTIVE REASONING**: - - Conclusions about the user that MUST be true given premises ONLY of the following types: - - Explicit conclusions - - Previous deductive conclusions - - General, open domain knowledge known to be true - - Current date and time (which is: {message_created_at}) + - Conclusions about the user that MUST be true given premises ONLY of the following types: + - Explicit conclusions + - Previous deductive conclusions + - General, open domain knowledge known to be true + - Current date and time (which is: {message_created_at}) - Timestamps for user messages, and previous premises and conclusions Here's the current user understanding diff --git a/src/deriver/queue_payload.py b/src/deriver/queue_payload.py index ed316f41..2b027af7 100644 --- a/src/deriver/queue_payload.py +++ b/src/deriver/queue_payload.py @@ -58,7 +58,7 @@ class DeriverQueuePayload(BaseModel): message_id = message.get("message_id") if not isinstance(message_id, int): raise TypeError("Message ID must be an integer") - + # Ensure created_at exists and is a datetime if "created_at" not in message: raise TypeError("created_at is required") @@ -85,7 +85,7 @@ class DeriverQueuePayload(BaseModel): ) # Convert back to dict for compatibility with JSON serialization # mode='json' ensures datetime is converted to ISO string - payload = validated_payload.model_dump(mode='json') + payload = validated_payload.model_dump(mode="json") except Exception as e: raise ValueError(f"Failed to create valid payload: {str(e)}") from e diff --git a/src/dialectic/prompts.py b/src/dialectic/prompts.py index 8db06630..2d3a3853 100644 --- a/src/dialectic/prompts.py +++ b/src/dialectic/prompts.py @@ -31,12 +31,12 @@ You are a context synthesis agent that operates as a natural language API for AI You receive three key inputs: - **Query**: The specific question or request from the application about this user -- **Working Representation**: Current session conclusions from recent conversation analysis +- **Working Representation**: Current session conclusions from recent conversation analysis - **Additional Context**: Historical conclusions from the user's global representation Each conclusion contains: - **Conclusion**: The derived insight -- **Premises**: Supporting evidence/reasoning +- **Premises**: Supporting evidence/reasoning - **Type**: Either Explicit or Deductive - **Temporal Data**: When conclusions were made @@ -44,7 +44,7 @@ Each conclusion contains: Provide a natural language response that: 1. Directly answers the application's query -2. Provides most useful context based on available conclusions +2. Provides most useful context based on available conclusions 3. References the reasoning types and evidence strength when relevant 4. Maintains appropriate confidence levels based on conclusion types 5. Flags any limitations or gaps in available information @@ -80,7 +80,7 @@ def query_generation_prompt(query: str, peer_name: str) -> str: 1. **Analyze the Application Query**: What specific aspect of the user does the application want to understand? 2. **Think Conceptually**: What concepts, themes, and semantic fields relate to this question? 3. **Consider Language Patterns in Stored Observations**: Loosely match the structure of the observations we aim to retrieve - "[subject] [verb] [predicate] [additional context]" (e.g. "Mary went ice-skating with Peter and Lin on June 5th 2024", "John activities summer outdoors") -4. **Vary Semantic Scope** across the generated queries to ensure maximum coverage. +4. **Vary Semantic Scope** across the generated queries to ensure maximum coverage. 5. Ensure the queries are different enough to not be redundant. **Vocabulary Expansion Techniques**: diff --git a/src/utils/summarizer.py b/src/utils/summarizer.py index 60e02460..a47ea7dc 100644 --- a/src/utils/summarizer.py +++ b/src/utils/summarizer.py @@ -127,7 +127,7 @@ Return only the summary without any explanation or meta-commentary. {_format_messages(messages)} - + {previous_summary or ""} """ diff --git a/tests/sdk/test_client.py b/tests/sdk/test_client.py index 3da6dc04..028854e8 100644 --- a/tests/sdk/test_client.py +++ b/tests/sdk/test_client.py @@ -52,9 +52,9 @@ async def test_client_init( page += 1 - assert found_workspace, ( - f"Workspace {honcho_client.workspace_id} not found in any page of results" - ) + assert ( + found_workspace + ), f"Workspace {honcho_client.workspace_id} not found in any page of results" @pytest.mark.asyncio diff --git a/tests/test_advanced_filters.py b/tests/test_advanced_filters.py index 47c327a0..fa545c5b 100644 --- a/tests/test_advanced_filters.py +++ b/tests/test_advanced_filters.py @@ -105,21 +105,21 @@ async def test_logical_operators_and_filters( found_names = [item["id"] for item in data["items"]] expected_names = [peer_names[i] for i in expected_peer_indices] - assert len(found_names) == len(expected_names), ( - f"Expected {len(expected_names)} peers for {description}, got {len(found_names)}" - ) + assert len(found_names) == len( + expected_names + ), f"Expected {len(expected_names)} peers for {description}, got {len(found_names)}" for expected_name in expected_names: - assert expected_name in found_names, ( - f"Expected peer {expected_name} in results for {description}" - ) + assert ( + expected_name in found_names + ), f"Expected peer {expected_name} in results for {description}" # Verify unexpected peers are not included for i, peer_name in enumerate(peer_names): if i not in expected_peer_indices: - assert peer_name not in found_names, ( - f"Unexpected peer {peer_name} found in results for {description}" - ) + assert ( + peer_name not in found_names + ), f"Unexpected peer {peer_name} found in results for {description}" @pytest.mark.parametrize( @@ -218,21 +218,21 @@ async def test_comparison_operators_filters( ] found_contents = [item["content"] for item in data["items"]] - assert len(found_contents) == len(expected_contents), ( - f"Expected {len(expected_contents)} messages for {description}, got {len(found_contents)}" - ) + assert ( + len(found_contents) == len(expected_contents) + ), f"Expected {len(expected_contents)} messages for {description}, got {len(found_contents)}" for expected_content in expected_contents: - assert expected_content in found_contents, ( - f"Expected message '{expected_content}' in results for {description}" - ) + assert ( + expected_content in found_contents + ), f"Expected message '{expected_content}' in results for {description}" # Verify unexpected messages are not included for i, message_config in enumerate(message_configs): if i not in expected_message_indices: - assert message_config["content"] not in found_contents, ( - f"Unexpected message '{message_config['content']}' found in results for {description}" - ) + assert ( + message_config["content"] not in found_contents + ), f"Unexpected message '{message_config['content']}' found in results for {description}" @pytest.mark.asyncio diff --git a/uv.lock b/uv.lock index f6fe8270..ef2e395b 100644 --- a/uv.lock +++ b/uv.lock @@ -169,6 +169,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.2" @@ -315,6 +324,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000, upload-time = "2025-06-13T13:02:27.173Z" }, ] +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "cryptography" version = "45.0.5" @@ -362,6 +376,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "distro" version = "1.9.0" @@ -471,6 +494,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/73/ef1ab892c2d189d8b6bd72325e9e710df6737c3b7976e12aa5749a56ea01/fastapi_pagination-0.13.3-py3-none-any.whl", hash = "sha256:e1b1cc7fa5c773c61087845ef8a73ed6b516071c057418698b9242461573f44e", size = 50986, upload-time = "2025-06-25T21:22:13.591Z" }, ] +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + [[package]] name = "google-ai-generativelanguage" version = "0.6.15" @@ -772,9 +804,11 @@ dev = [ { name = "coverage" }, { name = "honcho-core" }, { name = "interrogate" }, + { name = "pre-commit" }, { name = "py-spy" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov" }, { name = "ruff" }, { name = "sqlalchemy-utils" }, ] @@ -809,9 +843,11 @@ dev = [ { name = "coverage", specifier = ">=7.6.0" }, { name = "honcho-core", specifier = ">=1.2.0" }, { name = "interrogate", specifier = ">=1.7.0" }, + { name = "pre-commit", specifier = ">=4.2.0" }, { name = "py-spy", specifier = ">=0.3.14" }, { name = "pytest", specifier = ">=8.2.2" }, { name = "pytest-asyncio", specifier = ">=0.23.7" }, + { name = "pytest-cov", specifier = ">=6.2.1" }, { name = "ruff", specifier = ">=0.11.2" }, { name = "sqlalchemy-utils", specifier = ">=0.41.2" }, ] @@ -909,6 +945,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "identify" +version = "2.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -1178,6 +1223,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844, upload-time = "2018-11-20T14:45:50.165Z" }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + [[package]] name = "nodejs-wheel-binaries" version = "22.16.0" @@ -1448,6 +1502,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661, upload-time = "2024-07-01T09:48:20.293Z" }, ] +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -1457,6 +1520,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, +] + [[package]] name = "proto-plus" version = "1.26.1" @@ -1799,6 +1878,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" }, ] +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + [[package]] name = "python-dotenv" version = "1.1.1" @@ -2346,6 +2439,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, ] +[[package]] +name = "virtualenv" +version = "20.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970, upload-time = "2025-07-21T04:09:50.985Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" }, +] + [[package]] name = "watchfiles" version = "1.1.0"