Skip to content

Commit

Permalink
v0.0.8 release (#53)
Browse files Browse the repository at this point in the history
* Fix DEV-291

* API Patches

* SDK compatibility

* FastAPI Security Bearer Token

* Error Handling for Unique Keys
  • Loading branch information
VVoruganti authored May 9, 2024
1 parent 62d397a commit 107b40b
Show file tree
Hide file tree
Showing 18 changed files with 398 additions and 129 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Honcho is a platform for making AI agents and LLM powered applications that are personalized
to their end users.

Read about the motivation of this project [here](https://blog.plasticlabs.ai).
Read about the motivation of this project [here](https://blog.plasticlabs.ai/blog/A-Simple-Honcho-Primer).

Read the user documenation [here](https://docs.honcho.dev)

Expand Down
96 changes: 95 additions & 1 deletion api/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ psycopg = {extras = ["binary"], version = "^3.1.18"}
langchain = "^0.1.12"
langchain-openai = "^0.0.8"
httpx = "^0.27.0"
uvloop = "^0.19.0"
httptools = "^0.6.1"

[tool.ruff.lint]
# from https://docs.astral.sh/ruff/linter/#rule-selection example
Expand Down
39 changes: 31 additions & 8 deletions api/src/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
llm: ChatOpenAI = ChatOpenAI(model_name="gpt-4")


async def chat(
async def prep_inference(
db: AsyncSession,
app_id: uuid.UUID,
user_id: uuid.UUID,
session_id: uuid.UUID,
query: str,
db: AsyncSession,
):
collection = await crud.get_collection_by_name(db, app_id, user_id, "honcho")
retrieved_facts = None
Expand All @@ -58,6 +58,19 @@ async def chat(

dialectic_prompt = ChatPromptTemplate.from_messages([system_dialectic])
chain = dialectic_prompt | llm
return (chain, retrieved_facts)


async def chat(
app_id: uuid.UUID,
user_id: uuid.UUID,
session_id: uuid.UUID,
query: str,
db: AsyncSession,
):
(chain, retrieved_facts) = await prep_inference(
db, app_id, user_id, session_id, query
)
response = await chain.ainvoke(
{
"agent_input": query,
Expand All @@ -68,9 +81,19 @@ async def chat(
return schemas.AgentChat(content=response.content)


async def hydrate():
pass


async def insight():
pass
async def stream(
app_id: uuid.UUID,
user_id: uuid.UUID,
session_id: uuid.UUID,
query: str,
db: AsyncSession,
):
(chain, retrieved_facts) = await prep_inference(
db, app_id, user_id, session_id, query
)
return chain.astream(
{
"agent_input": query,
"retrieved_facts": retrieved_facts if retrieved_facts else "None",
}
)
2 changes: 1 addition & 1 deletion api/src/deriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
SUPABASE_ID = os.getenv("SUPABASE_ID")
SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY")

llm = ChatOpenAI(model_name="gpt-3.5")
llm = ChatOpenAI(model_name="gpt-3.5-turbo")
output_parser = NumberedListOutputParser()

SYSTEM_DERIVE_FACTS = load_prompt(
Expand Down
98 changes: 35 additions & 63 deletions api/src/main.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import json
import logging
import os
import re
import uuid
from contextlib import asynccontextmanager
from typing import Optional, Sequence

import httpx
import sentry_sdk
from fastapi import (
APIRouter,
FastAPI,
Request,
)
from fastapi import APIRouter, FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import PlainTextResponse
from fastapi_pagination import add_pagination
from opentelemetry import trace
Expand Down Expand Up @@ -46,8 +39,6 @@
from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response

from src.routers import (
apps,
Expand Down Expand Up @@ -199,12 +190,43 @@ async def lifespan(app: FastAPI):
await engine.dispose()


app = FastAPI(lifespan=lifespan)
app = FastAPI(
lifespan=lifespan,
servers=[
{"url": "http://127.0.0.1:8000", "description": "Local Development Server"},
{"url": "https:/demo.honcho.dev", "description": "Demo Server"},
],
title="Honcho API",
summary="An API for adding personalization to AI Apps",
description="""This API is used to store data and get insights about users for AI
applications""",
version="0.1.0",
contact={
"name": "Plastic Labs",
"url": "https://plasticlabs.ai",
"email": "[email protected]",
},
license_info={
"name": "GNU Affero General Public License v3.0",
"identifier": "AGPL-3.0-only",
"url": "https://github.com/plastic-labs/honcho/blob/main/LICENSE",
},
)

origins = ["http://localhost", "http://127.0.0.1:8000", "https://demo.honcho.dev"]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


if OPENTELEMTRY_ENABLED:
FastAPIInstrumentor().instrument_app(app)


router = APIRouter(prefix="/apps/{app_id}/users/{user_id}")

# Create a Limiter instance
Expand All @@ -221,56 +243,6 @@ async def lifespan(app: FastAPI):

add_pagination(app)

USE_AUTH_SERVICE = os.getenv("USE_AUTH_SERVICE", "False").lower() == "true"
AUTH_SERVICE_URL = os.getenv("AUTH_SERVICE_URL", "http://localhost:8001")


class BearerTokenMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
authorization: Optional[str] = request.headers.get("Authorization")
if authorization:
scheme, _, token = authorization.partition(" ")
if scheme.lower() == "bearer" and token:
id_pattern = r"\/apps\/([^\/]+)"
name_pattern = r"\/apps\/name\/([^\/]+)|\/apps\/get_or_create\/([^\/]+)"
match_id = re.search(id_pattern, request.url.path)
match_name = re.search(name_pattern, request.url.path)
payload = {"token": token}
if match_name:
payload["name"] = match_name.group(1)
elif match_id:
payload["app_id"] = match_id.group(1)

res = httpx.get(
f"{AUTH_SERVICE_URL}/validate",
params=payload,
)
data = res.json()
if (
data["app_id"] or data["name"]
): # Anything that checks app_id if True is valid
return await call_next(request)
if data["token"]:
check_pattern = r"^\/apps$|^\/apps\/get_or_create"
match = re.search(check_pattern, request.url.path)
if match:
return await call_next(request)

return Response(content="Invalid token.", status_code=400)
else:
return Response(
content="Invalid authentication scheme.", status_code=400
)

exclude_paths = ["/docs", "/redoc", "/openapi.json"]
if request.url.path in exclude_paths:
return await call_next(request)
return Response(content="Authorization header missing.", status_code=401)


if USE_AUTH_SERVICE:
app.add_middleware(BearerTokenMiddleware)


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
Expand Down
Loading

0 comments on commit 107b40b

Please sign in to comment.