v1.1.0 Release Notes (#110)

* fix (docs): Update Changelog, versions, and mintlify

* chore: linter errors

* chore: Add uv.lock updates

* fix (util): Fix save summary metamessages

* chore (docs): Fix spelling mistake

* fix (db): Fix Query Tagging
This commit is contained in:
Vineeth Voruganti 2025-05-15 17:09:40 -04:00 committed by GitHub
parent 5a2fcfed7d
commit a6d52ab8c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 3851 additions and 764 deletions

View File

@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.1.0]
### Added
- Normalize resources to remove joins and increase query performance
- Query tracing for debugging
### Changed
- `/list` endpoints to not require a request body
- `metamessage_type` to `label` with backwards compatability
- Database Provisioning to rely on alembic
- Database Session Manager to explicitly rollback transactions before closing
the connection
### Fixed
- Alembic Migrations to include initial database migrations
- Sentry Middleware to not report Honcho Exceptions
## [1.0.0]
### Added

View File

@ -1,6 +1,6 @@
# 🫡 Honcho
![Static Badge](https://img.shields.io/badge/Version-1.0.0-blue)
![Static Badge](https://img.shields.io/badge/Version-1.1.0-blue)
[![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs)
[![arXiv](https://img.shields.io/badge/arXiv-2310.06983-b31b1b.svg)](https://arxiv.org/abs/2310.06983)
![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho)

View File

@ -9,7 +9,7 @@
"url": "https://honcho.dev/",
"email": "hello@plasticlabs.ai"
},
"version": "1.0.0"
"version": "1.1.0"
},
"servers": [
{
@ -168,12 +168,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AppGet",
"description": "Filtering and pagination options for the apps list"
"anyOf": [
{ "$ref": "#/components/schemas/AppGet" },
{ "type": "null" }
],
"description": "Filtering and pagination options for the apps list",
"title": "Options"
}
}
}
@ -549,12 +552,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserGet",
"description": "Filtering options for the users list"
"anyOf": [
{ "$ref": "#/components/schemas/UserGet" },
{ "type": "null" }
],
"description": "Filtering options for the users list",
"title": "Options"
}
}
}
@ -996,12 +1002,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionGet",
"description": "Filtering and pagination options for the sessions list"
"anyOf": [
{ "$ref": "#/components/schemas/SessionGet" },
{ "type": "null" }
],
"description": "Filtering and pagination options for the sessions list",
"title": "Options"
}
}
}
@ -1616,12 +1625,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageGet",
"description": "Filtering options for the messages list"
"anyOf": [
{ "$ref": "#/components/schemas/MessageGet" },
{ "type": "null" }
],
"description": "Filtering options for the messages list",
"title": "Options"
}
}
}
@ -1894,11 +1906,11 @@
"x-codeSamples": [
{
"lang": "JavaScript",
"source": "import Honcho from 'honcho-ai';\n\nconst client = new Honcho({\n apiKey: process.env['HONCHO_API_KEY'], // This is the default and can be omitted\n});\n\nasync function main() {\n const metamessage = await client.apps.users.metamessages.create('app_id', 'user_id', {\n content: 'content',\n label: 'x',\n });\n\n console.log(metamessage.id);\n}\n\nmain();"
"source": "import Honcho from 'honcho-ai';\n\nconst client = new Honcho({\n apiKey: process.env['HONCHO_API_KEY'], // This is the default and can be omitted\n});\n\nasync function main() {\n const metamessage = await client.apps.users.metamessages.create('app_id', 'user_id', {\n content: 'content',\n metamessage_type: 'x',\n });\n\n console.log(metamessage.id);\n}\n\nmain();"
},
{
"lang": "Python",
"source": "import os\nfrom honcho import Honcho\n\nclient = Honcho(\n api_key=os.environ.get(\"HONCHO_API_KEY\"), # This is the default and can be omitted\n)\nmetamessage = client.apps.users.metamessages.create(\n user_id=\"user_id\",\n app_id=\"app_id\",\n content=\"content\",\n label=\"x\",\n)\nprint(metamessage.id)"
"source": "import os\nfrom honcho import Honcho\n\nclient = Honcho(\n api_key=os.environ.get(\"HONCHO_API_KEY\"), # This is the default and can be omitted\n)\nmetamessage = client.apps.users.metamessages.create(\n user_id=\"user_id\",\n app_id=\"app_id\",\n content=\"content\",\n metamessage_type=\"x\",\n)\nprint(metamessage.id)"
}
]
}
@ -1974,12 +1986,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MetamessageGet",
"description": "Filtering options for the metamessages list"
"anyOf": [
{ "$ref": "#/components/schemas/MetamessageGet" },
{ "type": "null" }
],
"description": "Filtering options for the metamessages list",
"title": "Options"
}
}
}
@ -2380,12 +2395,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CollectionGet",
"description": "Filtering options for the collections list"
"anyOf": [
{ "$ref": "#/components/schemas/CollectionGet" },
{ "type": "null" }
],
"description": "Filtering options for the collections list",
"title": "Options"
}
}
}
@ -2723,12 +2741,15 @@
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DocumentGet",
"description": "Filtering options for the documents list"
"anyOf": [
{ "$ref": "#/components/schemas/DocumentGet" },
{ "type": "null" }
],
"description": "Filtering options for the documents list",
"title": "Options"
}
}
}
@ -3286,7 +3307,8 @@
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
},
"created_at": {
"type": "string",
@ -3295,9 +3317,8 @@
}
},
"type": "object",
"required": ["id", "name", "metadata", "created_at"],
"title": "App",
"exclude": ["h_metadata", "public_id"]
"required": ["id", "name", "created_at"],
"title": "App"
},
"AppCreate": {
"properties": {
@ -3353,10 +3374,12 @@
"id": { "type": "string", "title": "Id" },
"name": { "type": "string", "title": "Name" },
"user_id": { "type": "string", "title": "User Id" },
"app_id": { "type": "string", "title": "App Id" },
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
},
"created_at": {
"type": "string",
@ -3365,9 +3388,8 @@
}
},
"type": "object",
"required": ["id", "name", "user_id", "metadata", "created_at"],
"title": "Collection",
"exclude": ["h_metadata", "public_id"]
"required": ["id", "name", "user_id", "app_id", "created_at"],
"title": "Collection"
},
"CollectionCreate": {
"properties": {
@ -3446,25 +3468,28 @@
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
},
"created_at": {
"type": "string",
"format": "date-time",
"title": "Created At"
},
"collection_id": { "type": "string", "title": "Collection Id" }
"collection_id": { "type": "string", "title": "Collection Id" },
"app_id": { "type": "string", "title": "App Id" },
"user_id": { "type": "string", "title": "User Id" }
},
"type": "object",
"required": [
"id",
"content",
"metadata",
"created_at",
"collection_id"
"collection_id",
"app_id",
"user_id"
],
"title": "Document",
"exclude": ["h_metadata", "public_id"]
"title": "Document"
},
"DocumentCreate": {
"properties": {
@ -3569,13 +3594,16 @@
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
},
"created_at": {
"type": "string",
"format": "date-time",
"title": "Created At"
}
},
"app_id": { "type": "string", "title": "App Id" },
"user_id": { "type": "string", "title": "User Id" }
},
"type": "object",
"required": [
@ -3583,11 +3611,11 @@
"content",
"is_user",
"session_id",
"metadata",
"created_at"
"created_at",
"app_id",
"user_id"
],
"title": "Message",
"exclude": ["h_metadata", "public_id"]
"title": "Message"
},
"MessageBatchCreate": {
"properties": {
@ -3654,6 +3682,7 @@
"label": { "type": "string", "title": "Label" },
"content": { "type": "string", "title": "Content" },
"user_id": { "type": "string", "title": "User Id" },
"app_id": { "type": "string", "title": "App Id" },
"session_id": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Session Id"
@ -3665,12 +3694,18 @@
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
},
"created_at": {
"type": "string",
"format": "date-time",
"title": "Created At"
},
"metamessage_type": {
"type": "string",
"title": "Metamessage Type",
"readOnly": true
}
},
"type": "object",
@ -3679,17 +3714,17 @@
"label",
"content",
"user_id",
"app_id",
"session_id",
"message_id",
"metadata",
"created_at"
"created_at",
"metamessage_type"
],
"title": "Metamessage",
"exclude": ["h_metadata", "public_id"]
"title": "Metamessage"
},
"MetamessageCreate": {
"properties": {
"label": {
"metamessage_type": {
"type": "string",
"maxLength": 50,
"minLength": 1,
@ -3717,12 +3752,12 @@
}
},
"type": "object",
"required": ["label", "content"],
"required": ["metamessage_type", "content"],
"title": "MetamessageCreate"
},
"MetamessageGet": {
"properties": {
"label": {
"metamessage_type": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Metamessage Type"
},
@ -3755,7 +3790,7 @@
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Message Id"
},
"label": {
"metamessage_type": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Metamessage Type"
},
@ -3887,10 +3922,12 @@
"id": { "type": "string", "title": "Id" },
"is_active": { "type": "boolean", "title": "Is Active" },
"user_id": { "type": "string", "title": "User Id" },
"app_id": { "type": "string", "title": "App Id" },
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
},
"created_at": {
"type": "string",
@ -3899,9 +3936,8 @@
}
},
"type": "object",
"required": ["id", "is_active", "user_id", "metadata", "created_at"],
"title": "Session",
"exclude": ["h_metadata", "public_id"]
"required": ["id", "is_active", "user_id", "app_id", "created_at"],
"title": "Session"
},
"SessionCreate": {
"properties": {
@ -3958,13 +3994,13 @@
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
"title": "Metadata",
"default": {}
}
},
"type": "object",
"required": ["id", "name", "app_id", "created_at", "metadata"],
"title": "User",
"exclude": ["h_metadata", "public_id"]
"required": ["id", "name", "app_id", "created_at"],
"title": "User"
},
"UserCreate": {
"properties": {
@ -4033,3 +4069,4 @@
"securitySchemes": { "HTTPBearer": { "type": "http", "scheme": "bearer" } }
}
}

View File

@ -13,6 +13,6 @@
"dependencies": {
"@mintlify/scraping": "^3.0.92",
"honcho-ai": "^0.0.11",
"mintlify": "^4.0.245"
"mintlify": "^4.0.531"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,30 @@
from os import getenv
from typing import Optional
from alembic import op
import sqlalchemy as sa
from alembic import op
def get_schema() -> str:
return getenv("DATABASE_SCHEMA", "public")
def column_exists(table_name: str, column_name: str, inspector: Optional[sa.Inspector] = None) -> bool:
def column_exists(
table_name: str, column_name: str, inspector: Optional[sa.Inspector] = None
) -> bool:
"""Check if a column exists in a table."""
if inspector is None:
inspector = sa.inspect(op.get_bind())
schema = get_schema()
existing_columns = [col["name"] for col in inspector.get_columns(table_name, schema=schema)]
existing_columns = [
col["name"] for col in inspector.get_columns(table_name, schema=schema)
]
return column_name in existing_columns
def fk_exists(table_name: str, fk_name: str, inspector: Optional[sa.Inspector] = None) -> bool:
def fk_exists(
table_name: str, fk_name: str, inspector: Optional[sa.Inspector] = None
) -> bool:
"""Check if a foreign key exists in a table."""
if inspector is None:
inspector = sa.inspect(op.get_bind())
@ -23,10 +32,14 @@ def fk_exists(table_name: str, fk_name: str, inspector: Optional[sa.Inspector] =
foreign_keys = inspector.get_foreign_keys(table_name, schema=schema)
return any(fk.get("name") == fk_name for fk in foreign_keys)
def index_exists(table_name: str, index_name: str, inspector: Optional[sa.Inspector] = None) -> bool:
def index_exists(
table_name: str, index_name: str, inspector: Optional[sa.Inspector] = None
) -> bool:
"""Check if an index exists in a table."""
if inspector is None:
inspector = sa.inspect(op.get_bind())
schema = get_schema()
indexes = inspector.get_indexes(table_name, schema=schema)
return any(idx["name"] == index_name for idx in indexes)
return any(idx["name"] == index_name for idx in indexes)

View File

@ -6,7 +6,8 @@ Create Date: 2025-05-13 16:11:54.859842
"""
from typing import Sequence, Union
from collections.abc import Sequence
from typing import Union
import sqlalchemy as sa
from alembic import op

View File

@ -5,22 +5,24 @@ Revises: b765d82110bd
Create Date: 2025-05-13 17:10:33.805495
"""
from typing import Sequence, Union
from alembic import op
from collections.abc import Sequence
from typing import Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.exc import IntegrityError, ProgrammingError
from migrations.utils import (
get_schema,
column_exists,
fk_exists,
get_schema,
index_exists,
)
# revision identifiers, used by Alembic.
revision: str = '556a16564f50'
down_revision: Union[str, None] = 'b765d82110bd'
revision: str = "556a16564f50"
down_revision: Union[str, None] = "b765d82110bd"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@ -32,7 +34,9 @@ def upgrade() -> None:
# 1. SESSIONS TABLE
if not column_exists("sessions", "app_id", inspector):
op.add_column("sessions", sa.Column("app_id", sa.Text(), nullable=True), schema=schema)
op.add_column(
"sessions", sa.Column("app_id", sa.Text(), nullable=True), schema=schema
)
print("Added app_id column to sessions table")
else:
print("app_id column already exists in sessions table")
@ -47,11 +51,13 @@ def upgrade() -> None:
["app_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created app_id foreign key for sessions table")
except IntegrityError:
print("Cannot create app_id foreign key for sessions table - integrity error")
print(
"Cannot create app_id foreign key for sessions table - integrity error"
)
else:
print("app_id foreign key already exists for sessions table")
@ -77,11 +83,17 @@ def upgrade() -> None:
# Create index for sessions.app_id
if not index_exists("sessions", "ix_sessions_app_id"):
op.create_index(op.f("ix_sessions_app_id"), "sessions", ["app_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_sessions_app_id"),
"sessions",
["app_id"],
unique=False,
schema=schema,
)
print("Created index ix_sessions_app_id")
else:
print("Index ix_sessions_app_id already exists")
# 2. MESSAGES TABLE
if not column_exists("messages", "app_id", inspector):
op.add_column("messages", sa.Column("app_id", sa.Text()), schema=schema)
@ -105,14 +117,16 @@ def upgrade() -> None:
["app_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created app_id foreign key for messages table")
except IntegrityError:
print("Cannot create app_id foreign key for messages table - integrity error")
print(
"Cannot create app_id foreign key for messages table - integrity error"
)
else:
print("app_id foreign key already exists for messages table")
if not fk_exists("messages", "messages_user_id_fkey", inspector):
try:
op.create_foreign_key(
@ -122,11 +136,13 @@ def upgrade() -> None:
["user_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created user_id foreign key for messages table")
except IntegrityError:
print("Cannot create user_id foreign key for messages table - integrity error")
print(
"Cannot create user_id foreign key for messages table - integrity error"
)
else:
print("user_id foreign key already exists for messages table")
@ -145,7 +161,7 @@ def upgrade() -> None:
print("Updated app_id and user_id values from sessions for messages table")
except Exception as e:
print(f"Error updating app_id and user_id values for messages table: {e}")
# Make app_id and user_id non-nullable
try:
op.alter_column("messages", "app_id", nullable=False, schema=schema)
@ -156,17 +172,29 @@ def upgrade() -> None:
# Create indices for messages foreign keys
if not index_exists("messages", "ix_messages_app_id"):
op.create_index(op.f("ix_messages_app_id"), "messages", ["app_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_messages_app_id"),
"messages",
["app_id"],
unique=False,
schema=schema,
)
print("Created index ix_messages_app_id")
else:
print("Index ix_messages_app_id already exists")
if not index_exists("messages", "ix_messages_user_id"):
op.create_index(op.f("ix_messages_user_id"), "messages", ["user_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_messages_user_id"),
"messages",
["user_id"],
unique=False,
schema=schema,
)
print("Created index ix_messages_user_id")
else:
print("Index ix_messages_user_id already exists")
print("Index ix_messages_user_id already exists")
# 3. METAMESSAGES TABLE
if not column_exists("metamessages", "app_id", inspector):
op.add_column("metamessages", sa.Column("app_id", sa.Text()), schema=schema)
@ -184,14 +212,16 @@ def upgrade() -> None:
["app_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created app_id foreign key for metamessages table")
except IntegrityError:
print("Cannot create app_id foreign key for metamessages table - integrity error")
print(
"Cannot create app_id foreign key for metamessages table - integrity error"
)
else:
print("app_id foreign key already exists for metamessages table")
# Data migration: Fill app_id from users table
try:
op.execute("""
@ -214,7 +244,13 @@ def upgrade() -> None:
# Create index for metamessages.app_id
if not index_exists("metamessages", "ix_metamessages_app_id"):
op.create_index(op.f("ix_metamessages_app_id"), "metamessages", ["app_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_metamessages_app_id"),
"metamessages",
["app_id"],
unique=False,
schema=schema,
)
print("Created index ix_metamessages_app_id")
else:
print("Index ix_metamessages_app_id already exists")
@ -236,14 +272,16 @@ def upgrade() -> None:
["app_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created app_id foreign key for collections table")
except IntegrityError:
print("Cannot create app_id foreign key for collections table - integrity error")
print(
"Cannot create app_id foreign key for collections table - integrity error"
)
else:
print("app_id foreign key already exists for collections table")
# Data migration: Fill app_id from users table
try:
op.execute("""
@ -266,11 +304,17 @@ def upgrade() -> None:
# Create index for collections.app_id
if not index_exists("collections", "ix_collections_app_id"):
op.create_index(op.f("ix_collections_app_id"), "collections", ["app_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_collections_app_id"),
"collections",
["app_id"],
unique=False,
schema=schema,
)
print("Created index ix_collections_app_id")
else:
print("Index ix_collections_app_id already exists")
# 5. DOCUMENTS TABLE
if not column_exists("documents", "app_id", inspector):
op.add_column("documents", sa.Column("app_id", sa.Text()), schema=schema)
@ -294,14 +338,16 @@ def upgrade() -> None:
["app_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created app_id foreign key for documents table")
except IntegrityError:
print("Cannot create app_id foreign key for documents table - integrity error")
print(
"Cannot create app_id foreign key for documents table - integrity error"
)
else:
print("app_id foreign key already exists for documents table")
if not fk_exists("documents", "documents_user_id_fkey", inspector):
try:
op.create_foreign_key(
@ -311,14 +357,16 @@ def upgrade() -> None:
["user_id"],
["public_id"],
source_schema=schema,
referent_schema=schema
referent_schema=schema,
)
print("Created user_id foreign key for documents table")
except IntegrityError:
print("Cannot create user_id foreign key for documents table - integrity error")
print(
"Cannot create user_id foreign key for documents table - integrity error"
)
else:
print("user_id foreign key already exists for documents table")
# Data migration: Fill app_id and user_id from collections table
try:
op.execute("""
@ -345,23 +393,36 @@ def upgrade() -> None:
# Create indices for documents foreign keys
if not index_exists("documents", "ix_documents_app_id"):
op.create_index(op.f("ix_documents_app_id"), "documents", ["app_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_documents_app_id"),
"documents",
["app_id"],
unique=False,
schema=schema,
)
print("Created index ix_documents_app_id")
else:
print("Index ix_documents_app_id already exists")
if not index_exists("documents", "ix_documents_user_id"):
op.create_index(op.f("ix_documents_user_id"), "documents", ["user_id"], unique=False, schema=schema)
op.create_index(
op.f("ix_documents_user_id"),
"documents",
["user_id"],
unique=False,
schema=schema,
)
print("Created index ix_documents_user_id")
else:
print("Index ix_documents_user_id already exists")
def downgrade():
schema = get_schema()
inspector = sa.inspect(op.get_bind())
# 5. Documents table
# Drop indices
if index_exists("documents", "ix_documents_app_id", inspector):
op.drop_index("ix_documents_app_id", table_name="documents", schema=schema)
@ -374,27 +435,27 @@ def downgrade():
op.alter_column("documents", "app_id", nullable=True, schema=schema)
op.alter_column("documents", "user_id", nullable=True, schema=schema)
print("Made app_id and user_id nullable again for documents table")
# Drop foreign key constraints
try:
op.drop_constraint("documents_user_id_fkey", "documents", schema=schema)
print("Dropped user_id foreign key for documents table")
except Exception as e:
print(f"Error dropping user_id foreign key for documents table: {e}")
try:
op.drop_constraint("documents_app_id_fkey", "documents", schema=schema)
print("Dropped app_id foreign key for documents table")
except Exception as e:
print(f"Error dropping app_id foreign key for documents table: {e}")
# Drop the columns
op.drop_column("documents", "user_id", schema=schema)
op.drop_column("documents", "app_id", schema=schema)
print("Dropped app_id and user_id columns from documents table")
# 4. Collections table
# Drop indices
if index_exists("collections", "ix_collections_app_id", inspector):
op.drop_index("ix_collections_app_id", table_name="collections", schema=schema)
@ -403,42 +464,44 @@ def downgrade():
# Make app_id nullable again
op.alter_column("collections", "app_id", nullable=True, schema=schema)
print("Made app_id nullable again for collections table")
# Drop foreign key constraint
try:
op.drop_constraint("collections_app_id_fkey", "collections", schema=schema)
print("Dropped app_id foreign key for collections table")
except Exception as e:
print(f"Error dropping app_id foreign key for collections table: {e}")
# Drop the column
op.drop_column("collections", "app_id", schema=schema)
print("Dropped app_id column from collections table")
# 3. Metamessages table
# Drop indices
if index_exists("metamessages", "ix_metamessages_app_id", inspector):
op.drop_index("ix_metamessages_app_id", table_name="metamessages", schema=schema)
op.drop_index(
"ix_metamessages_app_id", table_name="metamessages", schema=schema
)
print("Dropped index ix_metamessages_app_id")
# Make app_id nullable again
op.alter_column("metamessages", "app_id", nullable=True, schema=schema)
print("Made app_id nullable again for metamessages table")
# Drop foreign key constraint
try:
op.drop_constraint("metamessages_app_id_fkey", "metamessages", schema=schema)
print("Dropped app_id foreign key for metamessages table")
except Exception as e:
print(f"Error dropping app_id foreign key for metamessages table: {e}")
# Drop the column
op.drop_column("metamessages", "app_id", schema=schema)
print("Dropped app_id column from metamessages table")
# 2. Messages table
# Drop indices
if index_exists("messages", "ix_messages_app_id", inspector):
op.drop_index("ix_messages_app_id", table_name="messages", schema=schema)
@ -451,27 +514,27 @@ def downgrade():
op.alter_column("messages", "app_id", nullable=True, schema=schema)
op.alter_column("messages", "user_id", nullable=True, schema=schema)
print("Made app_id and user_id nullable again for messages table")
# Drop foreign key constraints
try:
op.drop_constraint("messages_user_id_fkey", "messages", schema=schema)
print("Dropped user_id foreign key for messages table")
except Exception as e:
print(f"Error dropping user_id foreign key for messages table: {e}")
try:
op.drop_constraint("messages_app_id_fkey", "messages", schema=schema)
print("Dropped app_id foreign key for messages table")
except Exception as e:
print(f"Error dropping app_id foreign key for messages table: {e}")
# Drop the columns
op.drop_column("messages", "user_id", schema=schema)
op.drop_column("messages", "app_id", schema=schema)
print("Dropped app_id and user_id columns from messages table")
# 1. Sessions table
# Drop indices
if index_exists("sessions", "ix_sessions_app_id", inspector):
op.drop_index("ix_sessions_app_id", table_name="sessions", schema=schema)
@ -480,14 +543,15 @@ def downgrade():
# Make app_id nullable again
op.alter_column("sessions", "app_id", nullable=True, schema=schema)
print("Made app_id nullable again for sessions table")
# Drop foreign key constraint
try:
op.drop_constraint("sessions_app_id_fkey", "sessions", schema=schema)
print("Dropped app_id foreign key for sessions table")
except Exception as e:
print(f"Error dropping app_id foreign key for sessions table: {e}")
# Drop the column
op.drop_column("sessions", "app_id", schema=schema)
print("Dropped app_id column from sessions table")
print("Dropped app_id column from sessions table")

View File

@ -1,6 +1,6 @@
[project]
name = "honcho"
version = "1.0.0"
version = "1.1.0"
description = "Honcho Server"
authors = [
{name = "Plastic Labs", email = "hello@plasticlabs.ai"},

View File

@ -192,15 +192,15 @@ async def chat(
)
stmt = (
select(models.Message)
.where(models.Message.app_id == app_id)
.where(models.Message.user_id == user_id)
.where(models.Message.session_id == session_id)
.where(models.Message.is_user)
.order_by(models.Message.id.desc())
.limit(1)
)
select(models.Message)
.where(models.Message.app_id == app_id)
.where(models.Message.user_id == user_id)
.where(models.Message.session_id == session_id)
.where(models.Message.is_user)
.order_by(models.Message.id.desc())
.limit(1)
)
latest_messages = await db.execute(stmt)
latest_message = latest_messages.scalar_one_or_none()
latest_message_id = latest_message.public_id if latest_message else None
@ -451,11 +451,10 @@ async def generate_user_representation(
logger.debug(f"Fetching latest representation for session {session_id}")
latest_representation_stmt = (
select(models.Metamessage)
.where(models.Metamessage.session_id == session_id) # only from the same session
.where(
models.Metamessage.label
== USER_REPRESENTATION_METAMESSAGE_TYPE
)
models.Metamessage.session_id == session_id
) # only from the same session
.where(models.Metamessage.label == USER_REPRESENTATION_METAMESSAGE_TYPE)
.order_by(models.Metamessage.id.desc())
.limit(1)
)
@ -519,6 +518,7 @@ RELEVANT LONG-TERM FACTS ABOUT THE USER:
message_id = None
else:
metamessage = models.Metamessage(
app_id=app_id,
user_id=user_id,
session_id=session_id,
message_id=message_id if message_id else None,

View File

@ -1,6 +1,6 @@
from collections.abc import Sequence
from logging import getLogger
from typing import List, Optional
from typing import Optional
from dotenv import load_dotenv
from openai import AsyncOpenAI
@ -54,7 +54,6 @@ async def get_app(db: AsyncSession, app_id: str) -> models.App:
async def get_all_apps(
db: AsyncSession,
reverse: Optional[bool] = False,
filter: Optional[dict] = None,
) -> Select:
@ -260,7 +259,6 @@ async def get_user_by_name(db: AsyncSession, app_id: str, name: str) -> models.U
async def get_users(
db: AsyncSession,
app_id: str,
reverse: bool = False,
filter: Optional[dict] = None,
@ -409,7 +407,7 @@ async def create_session(
"""
try:
# This will raise ResourceNotFoundException if user not found
_honcho_user = await get_user(db, app_id=app_id, user_id=user_id)
await get_user(db, app_id=app_id, user_id=user_id)
honcho_session = models.Session(
user_id=user_id,
@ -715,7 +713,6 @@ async def create_messages(
async def get_messages(
db: AsyncSession,
app_id: str,
user_id: str,
session_id: str,
@ -749,7 +746,7 @@ async def get_message(
) -> Optional[models.Message]:
stmt = (
select(models.Message)
.where(models.Message.app_id == app_id)
.where(models.Message.app_id == app_id)
.where(models.Message.user_id == user_id)
.where(models.Message.session_id == session_id)
.where(models.Message.public_id == message_id)
@ -845,7 +842,6 @@ async def create_metamessage(
async def get_metamessages(
db: AsyncSession,
app_id: str,
user_id: str,
session_id: Optional[str] = None,
@ -942,7 +938,7 @@ async def update_metamessage(
db,
app_id=app_id,
session_id=metamessage.session_id,
user_id=metamessage.user_id,
user_id=user_id,
message_id=metamessage.message_id,
)
if message is None:
@ -975,7 +971,6 @@ async def update_metamessage(
async def get_collections(
db: AsyncSession,
app_id: str,
user_id: str,
reverse: Optional[bool] = False,
@ -1134,6 +1129,9 @@ async def create_user_protected_collection(
name=DEF_PROTECTED_COLLECTION_NAME,
)
try:
# This will raise ResourceNotFoundException if user not found
await get_user(db, app_id=app_id, user_id=user_id)
db.add(honcho_collection)
await db.commit()
except IntegrityError:
@ -1272,7 +1270,6 @@ async def delete_collection(
async def get_documents(
db: AsyncSession,
app_id: str,
user_id: str,
collection_id: str,
@ -1400,7 +1397,7 @@ async def create_document(
"""
# This will raise ResourceNotFoundException if collection not found
collection = await get_collection_by_id(
await get_collection_by_id(
db, app_id=app_id, collection_id=collection_id, user_id=user_id
)
@ -1518,7 +1515,7 @@ async def get_duplicate_documents(
collection_id: str,
content: str,
similarity_threshold: float = 0.85,
) -> List[models.Document]:
) -> list[models.Document]:
"""Check if a document with similar content already exists in the collection.
Args:
@ -1542,6 +1539,8 @@ async def get_duplicate_documents(
# Find documents with similar embeddings
stmt = (
select(models.Document)
.where(models.Document.app_id == app_id)
.where(models.Document.user_id == user_id)
.where(models.Document.collection_id == collection_id)
.where(
models.Document.embedding.cosine_distance(embedding)

View File

@ -51,7 +51,11 @@ async def process_item(db: AsyncSession, payload: dict):
await process_ai_message(*processing_args)
logger.debug(f"Finished processing message: {payload['message_id']}")
await summarize_if_needed(
db, payload["session_id"], payload["user_id"], payload["message_id"]
db,
payload["app_id"],
payload["session_id"],
payload["user_id"],
payload["message_id"],
)
return
@ -147,7 +151,7 @@ async def process_user_message(
async def summarize_if_needed(
db: AsyncSession, session_id: str, user_id: str, message_id: str
db: AsyncSession, app_id: str, session_id: str, user_id: str, message_id: str
):
summary_start = os.times()[4]
logger.debug("Checking if summaries should be created")
@ -193,6 +197,7 @@ async def summarize_if_needed(
# Save the long summary as a metamessage and capture the returned object
latest_long_summary = await history.save_summary_metamessage(
db=db,
app_id=app_id,
user_id=user_id,
session_id=session_id,
message_id=message_id,
@ -225,6 +230,7 @@ async def summarize_if_needed(
# Save the short summary as a metamessage
await history.save_summary_metamessage(
db=db,
app_id=app_id,
user_id=user_id,
session_id=session_id,
message_id=message_id,

View File

@ -1,5 +1,6 @@
import logging
import os
import re
import uuid
from contextlib import asynccontextmanager
@ -110,7 +111,7 @@ app = FastAPI(
title="Honcho API",
summary="The Identity Layer for the Agentic World",
description="""Honcho is a platform for giving agents user-centric memory and social cognition""",
version="1.0.0",
version="1.1.0",
contact={
"name": "Plastic Labs",
"url": "https://honcho.dev",
@ -173,8 +174,8 @@ async def global_exception_handler(request: Request, exc: Exception):
@app.middleware("http")
async def track_request(request: Request, call_next):
# Create a request ID that includes endpoint information
endpoint = request.url.path.replace("/", "_")
request_id = f"{endpoint}:{str(uuid.uuid4())[:8]}"
endpoint = re.sub(r"/[A-Za-z0-9_-]{21}", "", request.url.path).replace("/", "_")
request_id = f"{request.method}:{endpoint}:{str(uuid.uuid4())[:8]}"
# Store in request state and context var
request.state.request_id = request_id

View File

@ -62,7 +62,7 @@ async def get_all_apps(
):
"""Get all Apps"""
filter_param = None
if options and hasattr(options, 'filter'):
if options and hasattr(options, "filter"):
filter_param = options.filter
if filter_param == {}:
filter_param = None
@ -70,7 +70,6 @@ async def get_all_apps(
return await paginate(
db,
await crud.get_all_apps(
db,
reverse=reverse,
filter=filter_param,
),

View File

@ -81,15 +81,15 @@ async def get_collections(
):
"""Get All Collections for a User"""
filter_param = None
if options and hasattr(options, 'filter'):
if options and hasattr(options, "filter"):
filter_param = options.filter
if filter_param == {}: # Explicitly check for empty dict
if filter_param == {}: # Explicitly check for empty dict
filter_param = None
return await paginate(
db,
await crud.get_collections(
db, app_id=app_id, user_id=user_id, filter=filter_param, reverse=reverse
app_id=app_id, user_id=user_id, filter=filter_param, reverse=reverse
),
)

View File

@ -48,7 +48,6 @@ async def get_documents(
try:
documents_query = await crud.get_documents(
db,
app_id=app_id,
user_id=user_id,
collection_id=collection_id,

View File

@ -1,6 +1,6 @@
import logging
import os
from typing import List, Optional
from typing import Optional
from fastapi import APIRouter, BackgroundTasks, Body, Depends, Path, Query
from fastapi_pagination import Page
@ -189,7 +189,7 @@ async def create_message_for_session(
raise ResourceNotFoundException("Session not found") from e
@router.post("/batch", response_model=List[schemas.Message])
@router.post("/batch", response_model=list[schemas.Message])
async def create_batch_messages_for_session(
background_tasks: BackgroundTasks,
app_id: str = Path(..., description="ID of the app"),
@ -254,13 +254,12 @@ async def get_messages(
"""Get all messages for a session"""
try:
filter = None
if options and hasattr(options, 'filter'):
if options and hasattr(options, "filter"):
filter = options.filter
if filter == {}:
filter = None
messages_query = await crud.get_messages(
db,
app_id=app_id,
user_id=user_id,
session_id=session_id,

View File

@ -73,20 +73,19 @@ async def get_metamessages(
filter_param = None
if options:
if hasattr(options, 'session_id') and options.session_id:
if hasattr(options, "session_id") and options.session_id:
session_id_param = options.session_id
if hasattr(options, 'message_id') and options.message_id:
if hasattr(options, "message_id") and options.message_id:
message_id_param = options.message_id
if hasattr(options, 'label') and options.label:
if hasattr(options, "label") and options.label:
label_param = options.label
if hasattr(options, 'filter') and options.filter:
if hasattr(options, "filter") and options.filter:
filter_param = options.filter
if filter_param == {}: # Explicitly check for empty dict
if filter_param == {}: # Explicitly check for empty dict
filter_param = None
try:
metamessages_query = await crud.get_metamessages(
db,
app_id=app_id,
user_id=user_id,
session_id=session_id_param,

View File

@ -51,14 +51,14 @@ async def get_users(
):
"""Get All Users for an App"""
filter_param = None
if options and hasattr(options, 'filter'):
if options and hasattr(options, "filter"):
filter_param = options.filter
if filter_param == {}:
filter_param = None
return await paginate(
db,
await crud.get_users(db, app_id=app_id, reverse=reverse, filter=filter_param),
await crud.get_users(app_id=app_id, reverse=reverse, filter=filter_param),
)

View File

@ -1,7 +1,7 @@
import datetime
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field, field_validator, computed_field
from pydantic import BaseModel, ConfigDict, Field, computed_field, field_validator
class AppBase(BaseModel):

View File

@ -1,5 +1,6 @@
from collections import OrderedDict
import asyncio
from collections import OrderedDict
class LRUCache:
"""

View File

@ -220,6 +220,7 @@ Provide a {"comprehensive" if summary_type == SummaryType.LONG else "concise"} s
async def save_summary_metamessage(
db: AsyncSession,
app_id: str,
user_id: str,
session_id: str,
message_id: str,
@ -247,6 +248,7 @@ async def save_summary_metamessage(
# Create and save the metamessage
metamessage = models.Metamessage(
app_id=app_id,
user_id=user_id,
session_id=session_id,
message_id=message_id,
@ -389,4 +391,3 @@ def format_messages(messages: list[models.Message]) -> str:
return "\n".join(
[f"{'user' if msg.is_user else 'assistant'}: {msg.content}" for msg in messages]
)

View File

@ -463,7 +463,7 @@ wheels = [
[[package]]
name = "honcho"
version = "1.0.0"
version = "1.1.0"
source = { virtual = "." }
dependencies = [
{ name = "alembic" },