198 lines
6.0 KiB
Python
198 lines
6.0 KiB
Python
import logging
|
|
from collections.abc import Sequence
|
|
|
|
import sentry_sdk
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src import crud, models, schemas
|
|
from src.config import settings
|
|
from src.dependencies import tracked_db
|
|
from src.dreamer.prompts import consolidation_prompt
|
|
from src.embedding_client import embedding_client
|
|
from src.utils.clients import honcho_llm_call
|
|
from src.utils.formatting import format_datetime_utc
|
|
from src.utils.logging import conditional_observe
|
|
from src.utils.queue_payload import DreamPayload
|
|
from src.utils.representation import (
|
|
ExplicitObservation,
|
|
Representation,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@sentry_sdk.trace
|
|
async def process_dream(
|
|
payload: DreamPayload,
|
|
) -> None:
|
|
"""
|
|
Process a dream task by performing collection maintenance operations.
|
|
|
|
Args:
|
|
payload: The dream task payload containing workspace, peer, and dream type information
|
|
"""
|
|
logger.info(
|
|
f"Processing dream task: {payload.dream_type} for {payload.workspace_name}/{payload.observer}/{payload.observed}"
|
|
)
|
|
|
|
try:
|
|
if payload.dream_type == "consolidate":
|
|
await _process_consolidate_dream(payload)
|
|
## TODO other dream types
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Error processing dream task {payload.dream_type} for {payload.observer}/{payload.observed}: {str(e)}",
|
|
exc_info=True,
|
|
)
|
|
if settings.SENTRY.ENABLED:
|
|
sentry_sdk.capture_exception(e)
|
|
# Don't re-raise - we want to mark the dream task as processed even if it fails
|
|
|
|
|
|
async def _process_consolidate_dream(payload: DreamPayload) -> None:
|
|
"""
|
|
Process a consolidation dream task.
|
|
|
|
Consolidation means taking all the documents in a collection and merging
|
|
similar observations into a single, best-quality observation document.
|
|
|
|
TODO: need to determine a way to do this on a subset of documents since
|
|
collections will grow very large.
|
|
"""
|
|
logger.info(
|
|
f"""
|
|
(っ- ‸ - ς)ᶻ z 𐰁 ᶻ z 𐰁 ᶻ z 𐰁\n
|
|
DREAM: consolidating documents for {payload.workspace_name}/{payload.observer}/{payload.observed}\n
|
|
𐰁 z ᶻ 𐰁 z ᶻ 𐰁 z ᶻ(っ- ‸ - ς)"""
|
|
)
|
|
|
|
# get all documents in the collection
|
|
async with tracked_db("dream_consolidate") as db:
|
|
documents = await crud.get_all_documents(
|
|
db,
|
|
payload.workspace_name,
|
|
observer=payload.observer,
|
|
observed=payload.observed,
|
|
)
|
|
|
|
logger.info("found %d documents to consolidate", len(documents))
|
|
|
|
# TODO: create clusters of documents based on cosine similarity
|
|
# clusters = await create_document_clusters(documents)
|
|
|
|
# logger.info("created %d clusters", len(clusters))
|
|
clusters = [documents]
|
|
|
|
# for each cluster, call llm to consolidate the representation if possible
|
|
for cluster in clusters:
|
|
await _consolidate_cluster(
|
|
cluster,
|
|
payload.workspace_name,
|
|
db,
|
|
observer=payload.observer,
|
|
observed=payload.observed,
|
|
)
|
|
|
|
|
|
async def _consolidate_cluster(
|
|
cluster: Sequence[models.Document],
|
|
workspace_name: str,
|
|
db: AsyncSession,
|
|
*,
|
|
observer: str,
|
|
observed: str,
|
|
) -> None:
|
|
"""
|
|
Consolidate a cluster of documents, treated as a Representation, into a smaller one.
|
|
Removes old documents and replaces them with consolidated versions while preserving metadata.
|
|
"""
|
|
if len(cluster) <= 1:
|
|
logger.info("Cluster has %d documents, skipping consolidation", len(cluster))
|
|
return
|
|
|
|
cluster_representation = Representation.from_documents(cluster)
|
|
logger.info("unconsolidated representation:\n%s", cluster_representation)
|
|
|
|
consolidated_representation = await consolidate_call(cluster_representation)
|
|
logger.info("consolidated representation:\n%s", consolidated_representation)
|
|
|
|
# TODO: less hacky preservation of times_derived
|
|
total_times_derived = sum(
|
|
doc.internal_metadata.get("times_derived", 1) for doc in cluster
|
|
)
|
|
|
|
new_documents = [
|
|
*consolidated_representation.explicit,
|
|
*consolidated_representation.deductive,
|
|
]
|
|
|
|
documents_to_create: list[schemas.DocumentCreate] = []
|
|
|
|
for obs in new_documents:
|
|
if isinstance(obs, ExplicitObservation):
|
|
content = obs.content
|
|
level = "explicit"
|
|
premises = None
|
|
else:
|
|
content = obs.conclusion
|
|
level = "deductive"
|
|
premises = obs.premises
|
|
# NOTE: other kinds of observations here in the future
|
|
|
|
metadata = schemas.DocumentMetadata(
|
|
times_derived=total_times_derived,
|
|
message_ids=obs.message_ids,
|
|
message_created_at=format_datetime_utc(obs.created_at),
|
|
level=level,
|
|
premises=premises,
|
|
)
|
|
|
|
embedding = await embedding_client.embed(content)
|
|
|
|
documents_to_create.append(
|
|
schemas.DocumentCreate(
|
|
content=content,
|
|
session_name=obs.session_name,
|
|
metadata=metadata,
|
|
embedding=embedding,
|
|
)
|
|
)
|
|
|
|
# bulk create documents
|
|
await crud.create_documents(
|
|
db, documents_to_create, workspace_name, observer=observer, observed=observed
|
|
)
|
|
|
|
# delete old documents
|
|
for doc in cluster:
|
|
await db.delete(doc)
|
|
|
|
await db.commit()
|
|
|
|
logger.info(
|
|
"consolidated %d documents into %d new documents",
|
|
len(cluster),
|
|
len(new_documents),
|
|
)
|
|
|
|
|
|
@conditional_observe(name="[Dream] Consolidate Call")
|
|
async def consolidate_call(
|
|
representation: Representation,
|
|
) -> Representation:
|
|
prompt = consolidation_prompt(representation)
|
|
|
|
response = await honcho_llm_call(
|
|
provider=settings.DREAM.PROVIDER,
|
|
model=settings.DREAM.MODEL,
|
|
prompt=prompt,
|
|
max_tokens=settings.DREAM.MAX_OUTPUT_TOKENS,
|
|
track_name="Dream Call",
|
|
response_model=Representation,
|
|
enable_retry=True,
|
|
retry_attempts=3,
|
|
)
|
|
|
|
return response.content
|