Single prompt tom (#89)
* Add TOM method switching * Add system prompt and note on format * Add persistence tweaks * Specify format for each section of user representation * Parse XML tags before saving representation metamessage * Clean up * Use Claude 3.5 Haiku and refine prompt * Simplify message processing * chore: update token limit on dialectic and model for deriver * chore: Update to Contributing Docs and .env template * Contributing Docs * chore: Add Deriver Template Variables * chore: Remove healthcheck endpoint * chore: Changelog updates --------- Co-authored-by: Daniel Balcells <dbalcells@gmail.com>
This commit is contained in:
parent
c2a1bb1796
commit
e68aceaab2
|
|
@ -2,20 +2,22 @@ CONNECTION_URI=postgresql+psycopg://testuser:testpwd@localhost:5432/honcho # sam
|
|||
|
||||
# CONNECTION_URI=postgresql+psycopg://testuser:testpwd@database:5432/honcho # sample for docker-compose database
|
||||
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_API_KEY= # Used for vector embeddings
|
||||
ANTHROPIC_API_KEY= # Used for the deriver and dialectic API
|
||||
|
||||
# Logging
|
||||
|
||||
SENTRY_ENABLED=false # Set to true to enable Sentry logging and tracing
|
||||
LOGFIRE_TOKEN= # optional logfire config
|
||||
|
||||
# Auth
|
||||
|
||||
USE_AUTH_SERVICE=false
|
||||
AUTH_SERVICE_URL=http://localhost:8001
|
||||
SECRET_KEY=
|
||||
|
||||
# Sentry
|
||||
SENTRY_DSN=
|
||||
|
||||
# Deriver
|
||||
DERIVER_WORKERS=1
|
||||
TOM_METHOD="single_prompt"
|
||||
USER_REPRESENTATION_METHOD="single_prompt"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
{
|
||||
"label": "start",
|
||||
"type": "shell",
|
||||
"command": "poetry install --no-root && poetry run uvicorn src.main:app --reload",
|
||||
"command": "uv sync && uv run fastapi dev src/main.py",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
|
|
|
|||
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -5,6 +5,19 @@ 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/).
|
||||
|
||||
## [0.0.16]
|
||||
|
||||
## Changed
|
||||
|
||||
- Deriver to use a new cognitive architecture that only updates on user messages
|
||||
and updates user representation to apply more confidence scores to its known
|
||||
facts
|
||||
- Dialectic API token cutoff from 150 tokens to 300
|
||||
|
||||
## Fixed
|
||||
|
||||
- Self-hosting documentation and README to mention `uv` instead of `poetry`
|
||||
|
||||
## [0.0.15]
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
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
|
||||
|
|
@ -22,15 +22,15 @@ Server.
|
|||
|
||||
### Prerequisites and Dependencies
|
||||
|
||||
Honcho is developed using [python](https://www.python.org/) and [poetry](https://python-poetry.org/).
|
||||
Honcho is developed using [python](https://www.python.org/) and [uv](https://docs.astral.sh/uv/).
|
||||
|
||||
The minimum python version is `3.9`
|
||||
The minimum poetry version is `1.4.1`
|
||||
The minimum poetry version is `0.4.9`
|
||||
|
||||
### Setup
|
||||
|
||||
Once the dependencies are installed on the system run the following steps to get
|
||||
the local project setup.
|
||||
the local project setup.
|
||||
|
||||
1. Clone the repository
|
||||
|
||||
|
|
@ -41,28 +41,34 @@ git clone https://github.com/plastic-labs/honcho.git
|
|||
2. Enter the repository and install the python dependencies
|
||||
|
||||
We recommend using a virtual environment to isolate the dependencies for Honcho
|
||||
from other projects on the same system. With `poetry` a virtual environment can
|
||||
be generated using the `poetry shell` command. Once the virtual environment is
|
||||
created and activated install the dependencies with `poetry install`
|
||||
from other projects on the same system. `uv` will create a virtual environment
|
||||
when you sync your dependencies in the project.
|
||||
|
||||
Putting this together:
|
||||
|
||||
```bash
|
||||
cd honcho
|
||||
poetry shell
|
||||
poetry install
|
||||
uv sync
|
||||
```
|
||||
|
||||
This will create a virtual environment and install the dependencies for Honcho.
|
||||
The default virtual environment will be located at `honcho/.venv`. Activate the
|
||||
virtual environment via:
|
||||
|
||||
```bash
|
||||
source honcho/.venv/bin/activate
|
||||
```
|
||||
|
||||
3. Set up a database
|
||||
|
||||
Honcho utilized [Postgres](https://www.postgresql.org/) for its database with
|
||||
Honcho utilized [Postgres](https://www.postgresql.org/) for its database with
|
||||
pgvector. An easy way to get started with a postgresdb is to create a project
|
||||
with [Supabase](https://supabase.com/)
|
||||
|
||||
A `docker-compose` template is also available with a database configuration
|
||||
available.
|
||||
available.
|
||||
|
||||
4. Edit the environment variables.
|
||||
4. Edit the environment variables.
|
||||
|
||||
Honcho uses a `.env` file for managing runtime environment variables. A
|
||||
`.env.template` file is included for convenience. Several of the configurations
|
||||
|
|
@ -73,18 +79,19 @@ Below are the required configurations
|
|||
|
||||
```env
|
||||
CONNECTION_URI= # Connection uri for a postgres database
|
||||
OPENAI_API_KEY= # API Key for OpenAI used for insights
|
||||
OPENAI_API_KEY= # API Key for OpenAI used for embedding documents
|
||||
ANTHROPIC_API_KEY= # API Key for Anthropic used for the deriver and dialectic API
|
||||
```
|
||||
|
||||
> Note that the `CONNECTION_URI` must have the prefix `postgresql+psycopg` to
|
||||
> function properly. This is a requirement brought by `sqlalchemy`
|
||||
|
||||
The template has the additional functionality disabled by default. To ensure
|
||||
that they are disabled you can verify the following environment variables are
|
||||
set to false.
|
||||
set to false.
|
||||
|
||||
```env
|
||||
USE_AUTH_SERVICE=false
|
||||
OPENTELEMETRY_ENABLED=false
|
||||
SENTRY_ENABLED=false
|
||||
```
|
||||
|
||||
|
|
@ -95,8 +102,9 @@ and the environment variables setup you can now launch a local instance of
|
|||
Honcho. The following command will launch the storage API for Honcho
|
||||
|
||||
```bash
|
||||
python -m uvicorn src.main:app --reload --port 8000
|
||||
fastapi dev src/main.py
|
||||
```
|
||||
|
||||
This is a development server that will reload whenever code is changed. When
|
||||
first launching the API with a connection the database it will provision the
|
||||
necessary tables for Honcho to operate.
|
||||
|
|
@ -105,10 +113,10 @@ necessary tables for Honcho to operate.
|
|||
|
||||
As mentioned earlier a `docker-compose` template is included for running Honcho.
|
||||
As an alternative to running Honcho locally it can also be run with the compose
|
||||
template.
|
||||
template.
|
||||
|
||||
The docker-compose template is set to use an environment file called `.env`.
|
||||
You can also copy the `.env.template` and fill with the appropriate values.
|
||||
You can also copy the `.env.template` and fill with the appropriate values.
|
||||
|
||||
Copy the template and update the appropriate environment variables before
|
||||
launching the service.
|
||||
|
|
@ -116,7 +124,7 @@ launching the service.
|
|||
```bash
|
||||
cd honcho/api
|
||||
cp .env.template .env
|
||||
# update the file with openai key and other wanted environment variables
|
||||
# update the file with openai key and other wanted environment variables
|
||||
cp docker-compose.yml.example docker-compose.yml
|
||||
docker compose up
|
||||
```
|
||||
|
|
@ -140,4 +148,3 @@ flyctl launch --no-deploy # Follow the prompts and edit as you see fit
|
|||
cat .env | flyctl secrets import # Load in your secrets
|
||||
flyctl deploy # Deploy with appropriate environment variables
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
# honcho-docs
|
||||
# Honcho Docs
|
||||
|
||||
## Setting Up `honcho-docs` Locally
|
||||
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-docs.git
|
||||
git clone git@github.com:plastic-labs/honcho.git
|
||||
```
|
||||
|
||||
2. Navigate into the `honcho-docs` folder:
|
||||
2. Navigate into the `docs` folder:
|
||||
```
|
||||
cd honcho-docs/
|
||||
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:
|
||||
|
||||
`index.mdx`: The main documentation file.
|
||||
`_app.js`: This file is used to customize the default Next.js application shell.
|
||||
`theme.config.jsx`: This file is for configuring the Nextra theme for the documentation.
|
||||
|
||||
3. Verify that you have Node.js and npm installed in your system. You can check by running:
|
||||
```
|
||||
|
|
@ -30,7 +29,7 @@ npm --version
|
|||
npm install -g pnpm
|
||||
```
|
||||
|
||||
6. Install the project dependencies using yarn:
|
||||
6. Install the project dependencies using pnpm:
|
||||
```
|
||||
pnpm i
|
||||
```
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ icon: 'cloud'
|
|||
|
||||
### Prerequisites and Dependencies
|
||||
|
||||
Honcho is developed using [python](https://www.python.org/) and [poetry](https://python-poetry.org/).
|
||||
Honcho is developed using [python](https://www.python.org/) and [uv](https://docs.astral.sh/uv/).
|
||||
|
||||
The minimum python version is `3.9`
|
||||
The minimum poetry version is `1.4.1`
|
||||
The minimum poetry version is `0.4.9`
|
||||
|
||||
### Setup
|
||||
|
||||
|
|
@ -29,16 +29,22 @@ git clone https://github.com/plastic-labs/honcho.git
|
|||
2. Enter the repository and install the python dependencies
|
||||
|
||||
We recommend using a virtual environment to isolate the dependencies for Honcho
|
||||
from other projects on the same system. With `poetry` a virtual environment can
|
||||
be generated using the `poetry shell` command. Once the virtual environment is
|
||||
created and activated install the dependencies with `poetry install`
|
||||
from other projects on the same system. `uv` will create a virtual environment
|
||||
when you sync your dependencies in the project.
|
||||
|
||||
Putting this together:
|
||||
|
||||
```bash
|
||||
cd honcho
|
||||
poetry shell
|
||||
poetry install
|
||||
uv sync
|
||||
```
|
||||
|
||||
This will create a virtual environment and install the dependencies for Honcho.
|
||||
The default virtual environment will be located at `honcho/.venv`. Activate the
|
||||
virtual environment via:
|
||||
|
||||
```bash
|
||||
source honcho/.venv/bin/activate
|
||||
```
|
||||
|
||||
3. Set up a database
|
||||
|
|
@ -61,7 +67,8 @@ Below are the required configurations
|
|||
|
||||
```env
|
||||
CONNECTION_URI= # Connection uri for a postgres database
|
||||
OPENAI_API_KEY= # API Key for OpenAI used for insights
|
||||
OPENAI_API_KEY= # API Key for OpenAI used for embedding documents
|
||||
ANTHROPIC_API_KEY= # API Key for Anthropic used for the deriver and dialectic API
|
||||
```
|
||||
> Note that the `CONNECTION_URI` must have the prefix `postgresql+psycopg` to
|
||||
> function properly. This is a requirement brought by `sqlalchemy`
|
||||
|
|
@ -72,7 +79,6 @@ set to false.
|
|||
|
||||
```env
|
||||
USE_AUTH_SERVICE=false
|
||||
OPENTELEMETRY_ENABLED=false
|
||||
SENTRY_ENABLED=false
|
||||
```
|
||||
|
||||
|
|
@ -83,7 +89,7 @@ and the environment variables setup you can now launch a local instance of
|
|||
Honcho. The following command will launch the storage API for Honcho
|
||||
|
||||
```bash
|
||||
python -m uvicorn src.main:app --reload --port 8000
|
||||
fastapi dev src/main.py
|
||||
```
|
||||
This is a development server that will reload whenever code is changed. When
|
||||
first launching the API with a connection the database it will provision the
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,18 +1,19 @@
|
|||
---
|
||||
title: 🫡 Welcome to Honcho
|
||||
sidebarTitle: 'Overview'
|
||||
description: 'Honcho is an open source platform for building personalized AI experiences.'
|
||||
description: 'The identity layer for the agentic world'
|
||||
icon: 'face-saluting'
|
||||
---
|
||||
|
||||
<Snippet file="overview-shields.mdx" />
|
||||
|
||||
Honcho provides an simple API store data for AI applications in a user-centric
|
||||
fashion and derive insights about users that improve the ability of
|
||||
applications to quickly deliver value to the User.
|
||||
Honcho is an infrastructure layer that empowers AI agents with social cognition and deeper understanding of their users.
|
||||
|
||||
Now you can focus on improving your service instead of spending
|
||||
countless hours figuring out how to get it to scale and be personalized.
|
||||
The API is built on two core concepts: *Storage* and *Insights*. The Storage API provides a flexible memory framework that can store both chat conversations and vector embeddings of user interactions. This forms the foundation for building more socially aware agents.
|
||||
|
||||
The Insights layer analyzes these stored interactions to build rich user profiles, which can be queried through a natural language dialectic endpoint. This allows agents to develop a nuanced understanding of users' preferences, behaviors, and needs over time.
|
||||
|
||||
By handling the complex infrastructure needed for memory and user understanding, Honcho lets you focus on building amazing AI experiences. No more worrying about how to scale personalization - we've got you covered.
|
||||
|
||||
To learn more about the project, check out our [blog
|
||||
post](https://blog.plasticlabs.ai/blog/A-Simple-Honcho-Primer).
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class Dialectic:
|
|||
system=self.system_prompt,
|
||||
messages=messages,
|
||||
model=self.model,
|
||||
max_tokens=150,
|
||||
max_tokens=300,
|
||||
)
|
||||
return response.content
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,16 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from .. import models
|
||||
from .voe import tom_inference, user_representation
|
||||
from .tom import get_tom_inference, get_user_representation
|
||||
|
||||
# Turn off SQLAlchemy Echo logging
|
||||
logging.getLogger("sqlalchemy.engine.Engine").disabled = True
|
||||
|
||||
console = Console(markup=False)
|
||||
|
||||
TOM_METHOD = os.getenv("TOM_METHOD", "single_prompt")
|
||||
USER_REPRESENTATION_METHOD = os.getenv("USER_REPRESENTATION_METHOD", "single_prompt")
|
||||
|
||||
|
||||
# FIXME see if this is SAFE
|
||||
async def add_metamessage(db, message_id, metamessage_type, content):
|
||||
|
|
@ -34,6 +37,29 @@ def parse_xml_content(text, tag):
|
|||
return match.group(1).strip() if match else ""
|
||||
|
||||
|
||||
async def get_chat_history(db, session_id, message_id) -> str:
|
||||
subquery = (
|
||||
select(models.Message.id)
|
||||
.where(models.Message.public_id == message_id)
|
||||
.scalar_subquery()
|
||||
)
|
||||
messages_stmt = (
|
||||
select(models.Message)
|
||||
.where(models.Message.session_id == session_id)
|
||||
.order_by(models.Message.id.desc())
|
||||
.where(models.Message.id < subquery)
|
||||
.limit(10)
|
||||
)
|
||||
|
||||
result = await db.execute(messages_stmt)
|
||||
messages = result.scalars().all()[::-1]
|
||||
|
||||
chat_history_str = "\n".join(
|
||||
[f"human: {m.content}" if m.is_user else f"ai: {m.content}" for m in messages]
|
||||
)
|
||||
return chat_history_str
|
||||
|
||||
|
||||
async def process_item(db: AsyncSession, payload: dict):
|
||||
processing_args = [
|
||||
payload["content"],
|
||||
|
|
@ -65,54 +91,6 @@ async def process_ai_message(
|
|||
"""
|
||||
console.print(f"Processing AI message: {content}", style="bright_magenta")
|
||||
|
||||
subquery = (
|
||||
select(models.Message.id)
|
||||
.where(models.Message.public_id == message_id)
|
||||
.scalar_subquery()
|
||||
)
|
||||
messages_stmt = (
|
||||
select(models.Message)
|
||||
.where(models.Message.session_id == session_id)
|
||||
.order_by(models.Message.id.desc())
|
||||
.where(models.Message.id < subquery)
|
||||
.limit(10)
|
||||
)
|
||||
|
||||
result = await db.execute(messages_stmt)
|
||||
messages = result.scalars().all()[::-1]
|
||||
|
||||
chat_history_str = "\n".join(
|
||||
[f"human: {m.content}" if m.is_user else f"ai: {m.content}" for m in messages]
|
||||
)
|
||||
# append current message to chat history
|
||||
chat_history_str = f"{chat_history_str}\nai: {content}"
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
release=os.getenv("SENTRY_RELEASE"),
|
||||
metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")},
|
||||
)
|
||||
|
||||
tom_inference_response = await tom_inference(
|
||||
chat_history_str, session_id=session_id
|
||||
)
|
||||
|
||||
prediction = parse_xml_content(tom_inference_response, "prediction")
|
||||
|
||||
await add_metamessage(
|
||||
db,
|
||||
message_id,
|
||||
"tom_inference",
|
||||
prediction,
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
console.print("Tom Inference:", style="blue")
|
||||
content_lines = str(prediction)
|
||||
console.print(content_lines, style="blue")
|
||||
|
||||
|
||||
@sentry_sdk.trace
|
||||
@observe()
|
||||
|
|
@ -125,112 +103,88 @@ async def process_user_message(
|
|||
db: AsyncSession,
|
||||
):
|
||||
"""
|
||||
Process a user message. If there are revised user predictions to run VoE against, run it. Otherwise pass.
|
||||
Process a user message by:
|
||||
- Getting TOM inference
|
||||
- Getting user representation
|
||||
"""
|
||||
console.print(f"Processing User Message: {content}", style="orange1")
|
||||
subquery = (
|
||||
select(models.Message.id)
|
||||
.where(models.Message.public_id == message_id)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
messages_stmt = (
|
||||
select(models.Message)
|
||||
.where(models.Message.session_id == session_id)
|
||||
.where(models.Message.is_user == False)
|
||||
.order_by(models.Message.id.desc())
|
||||
.where(models.Message.id < subquery)
|
||||
# Get chat history and append current message
|
||||
chat_history_str = await get_chat_history(db, session_id, message_id)
|
||||
chat_history_str = f"{chat_history_str}\nhuman: {content}"
|
||||
|
||||
# Get TOM inference, parse and save it
|
||||
tom_inference_response = await get_tom_inference(
|
||||
chat_history_str, session_id, method=TOM_METHOD
|
||||
)
|
||||
tom_inference = parse_xml_content(tom_inference_response, "prediction")
|
||||
await add_metamessage(
|
||||
db,
|
||||
message_id,
|
||||
"tom_inference",
|
||||
tom_inference,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# Fetch the latest user representation
|
||||
user_representation_stmt = (
|
||||
select(models.Metamessage)
|
||||
.join(
|
||||
models.Message,
|
||||
models.Message.public_id == models.Metamessage.message_id,
|
||||
)
|
||||
.join(
|
||||
models.Session,
|
||||
models.Message.session_id == models.Session.public_id,
|
||||
)
|
||||
.join(models.User, models.User.public_id == models.Session.user_id)
|
||||
.join(models.App, models.App.public_id == models.User.app_id)
|
||||
.where(models.App.public_id == app_id)
|
||||
.where(models.User.public_id == user_id)
|
||||
.where(models.Metamessage.metamessage_type == "user_representation")
|
||||
.order_by(models.Metamessage.id.desc()) # get the most recent
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
response = await db.execute(messages_stmt)
|
||||
ai_message = response.scalar_one_or_none()
|
||||
response = await db.execute(user_representation_stmt)
|
||||
existing_representation = response.scalar_one_or_none()
|
||||
|
||||
if ai_message and ai_message.content:
|
||||
console.print(f"AI Message: {ai_message.content}", style="bright_magenta")
|
||||
existing_representation_content = (
|
||||
existing_representation.content if existing_representation else "None"
|
||||
)
|
||||
print(f"Existing Representation: {existing_representation_content}")
|
||||
|
||||
# Fetch the tom_inference metamessage
|
||||
tom_inference_stmt = (
|
||||
select(models.Metamessage)
|
||||
.where(models.Metamessage.message_id == ai_message.public_id)
|
||||
.where(models.Metamessage.metamessage_type == "tom_inference")
|
||||
.order_by(
|
||||
models.Metamessage.id.asc()
|
||||
) # Get the earliest tom inference on this message
|
||||
.limit(1)
|
||||
)
|
||||
response = await db.execute(tom_inference_stmt)
|
||||
tom_inference_metamessage = response.scalar_one_or_none()
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
release=os.getenv("SENTRY_RELEASE"),
|
||||
metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")},
|
||||
)
|
||||
|
||||
if tom_inference_metamessage and tom_inference_metamessage.content:
|
||||
console.print(
|
||||
f"Tom Inference: {tom_inference_metamessage.content}", style="blue"
|
||||
)
|
||||
# Call user_representation
|
||||
user_representation_response = await get_user_representation(
|
||||
chat_history=chat_history_str,
|
||||
session_id=session_id,
|
||||
user_representation=existing_representation_content,
|
||||
tom_inference=tom_inference,
|
||||
method=USER_REPRESENTATION_METHOD,
|
||||
)
|
||||
|
||||
# Fetch the latest user representation
|
||||
user_representation_stmt = (
|
||||
select(models.Metamessage)
|
||||
.join(
|
||||
models.Message,
|
||||
models.Message.public_id == models.Metamessage.message_id,
|
||||
)
|
||||
.join(
|
||||
models.Session,
|
||||
models.Message.session_id == models.Session.public_id,
|
||||
)
|
||||
.join(models.User, models.User.public_id == models.Session.user_id)
|
||||
.join(models.App, models.App.public_id == models.User.app_id)
|
||||
.where(models.App.public_id == app_id)
|
||||
.where(models.User.public_id == user_id)
|
||||
.where(models.Metamessage.metamessage_type == "user_representation")
|
||||
.order_by(models.Metamessage.id.desc()) # get the most recent
|
||||
.limit(1)
|
||||
)
|
||||
# parse the user_representation response
|
||||
user_representation_response = parse_xml_content(
|
||||
user_representation_response, "representation"
|
||||
)
|
||||
|
||||
response = await db.execute(user_representation_stmt)
|
||||
existing_representation = response.scalar_one_or_none()
|
||||
# Store the user_representation response as a metamessage
|
||||
await add_metamessage(
|
||||
db,
|
||||
message_id,
|
||||
"user_representation",
|
||||
user_representation_response,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
existing_representation_content = (
|
||||
existing_representation.content if existing_representation else "None"
|
||||
)
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
release=os.getenv("SENTRY_RELEASE"),
|
||||
metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")},
|
||||
)
|
||||
|
||||
# Call user_representation
|
||||
user_representation_response = await user_representation(
|
||||
chat_history=f"{ai_message.content}\nhuman: {content}",
|
||||
session_id=session_id,
|
||||
user_representation=existing_representation_content,
|
||||
tom_inference=tom_inference_metamessage.content,
|
||||
)
|
||||
|
||||
# Store the user_representation response as a metamessage
|
||||
await add_metamessage(
|
||||
db,
|
||||
message_id,
|
||||
"user_representation",
|
||||
user_representation_response,
|
||||
)
|
||||
|
||||
# parse the user_representation response
|
||||
user_representation_response = parse_xml_content(
|
||||
user_representation_response, "representation"
|
||||
)
|
||||
|
||||
console.print(
|
||||
f"User Representation:\n{user_representation_response}",
|
||||
style="bright_green",
|
||||
)
|
||||
|
||||
else:
|
||||
raise Exception(
|
||||
f"\033[91mTom Inference NOT READY YET on message {message_id}"
|
||||
)
|
||||
else:
|
||||
console.print("No AI message before this user message", style="red")
|
||||
return
|
||||
console.print(
|
||||
f"User Representation:\n{user_representation_response}",
|
||||
style="bright_green",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
from .conversational import get_tom_inference_conversational, get_user_representation_conversational
|
||||
from .single_prompt import get_tom_inference_single_prompt, get_user_representation_single_prompt
|
||||
|
||||
async def get_tom_inference(chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
method: str = "conversational",
|
||||
**kwargs
|
||||
) -> str:
|
||||
if method == "conversational":
|
||||
return await get_tom_inference_conversational(chat_history, session_id, user_representation, **kwargs)
|
||||
elif method == "single_prompt":
|
||||
return await get_tom_inference_single_prompt(chat_history, session_id, user_representation, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
|
||||
|
||||
async def get_user_representation(chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
tom_inference: str = "None",
|
||||
method: str = "conversational",
|
||||
**kwargs
|
||||
) -> str:
|
||||
if method == "conversational":
|
||||
return await get_user_representation_conversational(chat_history, session_id, user_representation, tom_inference, **kwargs)
|
||||
elif method == "single_prompt":
|
||||
return await get_user_representation_single_prompt(chat_history, session_id, user_representation, tom_inference, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
|
|
@ -16,7 +16,7 @@ anthropic = Anthropic(
|
|||
|
||||
@ai_track("Tom Inference")
|
||||
@observe(as_type="generation")
|
||||
async def tom_inference(
|
||||
async def get_tom_inference_conversational(
|
||||
chat_history: str, session_id: str, user_representation: str = "None"
|
||||
) -> str:
|
||||
with sentry_sdk.start_transaction(op="tom-inference", name="ToM Inference"):
|
||||
|
|
@ -82,7 +82,7 @@ async def tom_inference(
|
|||
|
||||
@ai_track("User Representation")
|
||||
@observe(as_type="generation")
|
||||
async def user_representation(
|
||||
async def get_user_representation_conversational(
|
||||
chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
import os
|
||||
|
||||
import sentry_sdk
|
||||
from anthropic import Anthropic
|
||||
from langfuse.decorators import langfuse_context, observe
|
||||
from sentry_sdk.ai.monitoring import ai_track
|
||||
|
||||
# Place the code below at the beginning of your application to initialize the tracer
|
||||
|
||||
# Initialize the Anthropic client
|
||||
anthropic = Anthropic(
|
||||
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
||||
max_retries=5,
|
||||
)
|
||||
|
||||
ANTHROPIC_MODEL = "claude-3-5-haiku-20241022"
|
||||
|
||||
|
||||
@ai_track("Tom Inference")
|
||||
@observe(as_type="generation")
|
||||
async def get_tom_inference_single_prompt(
|
||||
chat_history: str, session_id: str, user_representation: str = "None", **kwargs
|
||||
) -> str:
|
||||
with sentry_sdk.start_transaction(op="tom-inference", name="ToM Inference"):
|
||||
system_prompt = """You are a system for analyzing conversations to make evidence-based inferences about user mental states.
|
||||
|
||||
REQUIREMENTS:
|
||||
1. Only make inferences that are directly supported by conversation evidence
|
||||
2. For each inference, cite the specific message that supports it
|
||||
3. Use uncertainty qualifiers (may, might, possibly) for speculative inferences
|
||||
4. Do not make assumptions about demographics unless explicitly stated
|
||||
5. Focus on current mental state and immediate context
|
||||
6. Consider your own knowledge gaps and violations of expectations (what would surprise you)
|
||||
7. Always wrap your prediction in <prediction> tags.
|
||||
|
||||
OUTPUT FORMAT:
|
||||
<prediction>
|
||||
CURRENT STATE:
|
||||
- Immediate Context: User's current situation
|
||||
- Active Goals: What user is trying to achieve
|
||||
- Present Mood: Observable emotional state
|
||||
|
||||
SUPPORTED OBSERVATIONS:
|
||||
- List only behaviors/preferences with direct evidence
|
||||
- Format: "OBSERVATION: [detail] (SOURCE: [exact message])"
|
||||
|
||||
TENTATIVE INFERENCES:
|
||||
- List possible but uncertain interpretations
|
||||
- Format: "POSSIBLE: [interpretation] (BASIS: [supporting message])"
|
||||
|
||||
KNOWLEDGE GAPS:
|
||||
- List important unknown information
|
||||
- Format: "UNKNOWN: [topic/question]"
|
||||
|
||||
EXPECTATION VIOLATIONS:
|
||||
- Based on the above information, if the next message were to surprise you, what could it contain?
|
||||
- Format: "POTENTIAL SURPRISE: [possible content] [reason] [confidence level]"
|
||||
- Include 3-5 possible surprises
|
||||
</prediction>
|
||||
"""
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Please analyze this conversation and provide a prediction following the format above:\n{chat_history}",
|
||||
}
|
||||
]
|
||||
|
||||
# Add existing user representation if available
|
||||
if user_representation != "None":
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Consider this existing user representation for context, but focus on current state:\n{user_representation}",
|
||||
}
|
||||
)
|
||||
|
||||
langfuse_context.update_current_observation(
|
||||
input=messages, model=ANTHROPIC_MODEL
|
||||
)
|
||||
message = anthropic.messages.create(
|
||||
model=ANTHROPIC_MODEL,
|
||||
max_tokens=1000,
|
||||
temperature=0,
|
||||
messages=messages,
|
||||
system=system_prompt,
|
||||
)
|
||||
print(f"tom_inference in single_prompt.py: {message.content[0].text=}")
|
||||
message = message.content[0].text
|
||||
return message
|
||||
|
||||
|
||||
@ai_track("User Representation")
|
||||
@observe(as_type="generation")
|
||||
async def get_user_representation_single_prompt(
|
||||
chat_history: str,
|
||||
session_id: str,
|
||||
user_representation: str = "None",
|
||||
tom_inference: str = "None",
|
||||
**kwargs,
|
||||
) -> str:
|
||||
with sentry_sdk.start_transaction(
|
||||
op="user-representation-inference", name="User Representation"
|
||||
):
|
||||
system_prompt = """You are a system for maintaining factual user representations based on conversation history and theory of mind analysis.
|
||||
|
||||
Your job is to update the existing user representation (if provided) with the new information from the conversation history and theory of mind analysis.
|
||||
|
||||
Copy over information as-is from the existing user representation. Add new information as needed. Only remove content from this section if new information contradicts it. This is especially important for Persistent Information and Tentative Patterns.
|
||||
|
||||
If the existing user representation contains sources, copy them over to the new user representation.
|
||||
|
||||
Sometimes, especially at the beginning of a conversation, the user representation might contain information from previous conversations. In this case, preserve all previous information unless new information contradicts it, especially in regards to traits, interests, style, and other information about the user that is likely to be useful across conversations.
|
||||
|
||||
Always use the format below. If the existing user representation is in another format, convert it to the format below.
|
||||
|
||||
Always wrap your output in <representation> tags.
|
||||
|
||||
REQUIREMENTS:
|
||||
1. Distinguish between temporary states and persistent patterns
|
||||
2. Only incorporate verified information into core profile
|
||||
3. Track certainty levels for all information
|
||||
4. Maintain areas of uncertainty explicitly
|
||||
5. Update representation incrementally
|
||||
|
||||
OUTPUT FORMAT:
|
||||
<representation>
|
||||
CURRENT STATE:
|
||||
- ACTIVE CONTEXT: [detail on situation/activity/location] (SOURCE: [exact message]) # Current situation/activity/location
|
||||
- TEMPORARY CONDITIONS: [detail on immediate circumstances] (SOURCE: [exact message]) # Immediate circumstances
|
||||
- PRESENT MOOD/ACTIVITY: [what user is doing right now] (SOURCE: [exact message]) # What user is doing right now
|
||||
|
||||
PERSISTENT INFORMATION:
|
||||
- STYLE: [pattern] (SOURCE: [exact message]) # Communication style: observed patterns in language use
|
||||
- STATEMENT: [fact] (SOURCE: [exact message]) # Explicitly stated information
|
||||
|
||||
TENTATIVE PATTERNS:
|
||||
- LIKELY PATTERN: [pattern] (SOURCE: [exact message]) # Patterns that are almost certain to be true
|
||||
- POTENTIAL PATTERN: [pattern] (SOURCE: [exact message]) # Patterns that are possible but less likely
|
||||
- SPECULATIVE PATTERN: [pattern] (SOURCE: [exact message]) # Patterns that are highly uncertain but remotely possible
|
||||
|
||||
KNOWLEDGE GAPS:
|
||||
- List key missing information
|
||||
- Note areas needing clarification
|
||||
|
||||
EXPECTATION VIOLATIONS:
|
||||
- Based on the above information, if the next message were to surprise you, what could it contain?
|
||||
- Format: "POTENTIAL SURPRISE: [possible content] [reason] [confidence level]"
|
||||
- Include 3-5 possible surprises
|
||||
|
||||
UPDATES:
|
||||
- New Information: Recent observations
|
||||
- Changes: Modified interpretations
|
||||
- Removals: Information no longer supported
|
||||
</representation>
|
||||
"""
|
||||
|
||||
messages = []
|
||||
|
||||
print(f"in single_prompt.py: chat_history: {chat_history}")
|
||||
print(f"in single_prompt.py: user_representation: {user_representation}")
|
||||
# Build the context message
|
||||
context_str = f"CONVERSATION:\n{chat_history}\n\n"
|
||||
if tom_inference != "None":
|
||||
context_str += f"PREDICTION OF USER MENTAL STATE - MIGHT BE INCORRECT:\n{tom_inference}\n\n"
|
||||
if user_representation != "None":
|
||||
context_str += f"EXISTING USER REPRESENTATION - INCOMPLETE, TO BE UPDATED:\n{user_representation}"
|
||||
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Please analyze this information and provide an updated user representation:\n{context_str}",
|
||||
}
|
||||
)
|
||||
|
||||
langfuse_context.update_current_observation(
|
||||
input=messages, model=ANTHROPIC_MODEL
|
||||
)
|
||||
message = anthropic.messages.create(
|
||||
model=ANTHROPIC_MODEL,
|
||||
max_tokens=1000,
|
||||
temperature=0,
|
||||
messages=messages,
|
||||
system=system_prompt,
|
||||
)
|
||||
message = message.content[0].text
|
||||
return message
|
||||
Loading…
Reference in New Issue