From d4774b75618fe59eadde6da1e3abb932aceb4318 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:23:46 -0800 Subject: [PATCH] MVP SDK --- api/Dockerfile | 37 ++++++++++++++++++++ api/src/main.py | 4 +-- example/cli/main.py | 42 ++++++++++++++++------- sdk/honcho/__init__.py | 2 ++ sdk/honcho/cache.py | 45 +++--------------------- sdk/honcho/client.py | 78 +++++++++++++++++++++++------------------- sdk/poetry.lock | 17 +-------- sdk/pyproject.toml | 3 +- 8 files changed, 120 insertions(+), 108 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index e69de29b..e47b7600 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -0,0 +1,37 @@ +# https://pythonspeed.com/articles/base-image-python-docker-images/ +# https://testdriven.io/blog/docker-best-practices/ +FROM python:3.10-slim-bullseye + +RUN apt-get update && apt-get install -y build-essential + +WORKDIR /app + +# https://stackoverflow.com/questions/53835198/integrating-python-poetry-with-docker +ENV PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 \ + POETRY_VERSION=1.4.1 + +RUN pip install "poetry==$POETRY_VERSION" + +# Copy only requirements to cache them in docker layer +WORKDIR /app +COPY poetry.lock pyproject.toml /app/ + +# Project initialization: +RUN poetry config virtualenvs.create false \ + && poetry install --no-root --no-interaction --no-ansi --without dev + +WORKDIR /app + +RUN addgroup --system app && adduser --system --group app +USER app + +COPY src/ src/ + +# https://stackoverflow.com/questions/29663459/python-app-does-not-print-anything-when-running-detached-in-docker +CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"] +# diff --git a/api/src/main.py b/api/src/main.py index c88f2ddf..ad284852 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -70,7 +70,7 @@ def get_session(user_id: str, session_id: int, db: Session = Depends(get_db)): ######################################################## @app.post( - "/users/{user_id}/sessions/{session_id}/messages/", + "/users/{user_id}/sessions/{session_id}/messages", response_model=schemas.Message ) def create_message_for_session( @@ -85,7 +85,7 @@ def create_message_for_session( raise HTTPException(status_code=404, detail="Session not found") @app.get( - "/users/{user_id}/sessions/{session_id}/messages/", + "/users/{user_id}/sessions/{session_id}/messages", response_model=list[schemas.Message] ) def get_messages_for_session( diff --git a/example/cli/main.py b/example/cli/main.py index a31dab9e..d7212f01 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -1,32 +1,50 @@ -from langchain.prompts import ChatPromptTemplate -# from langchain_community.chat_models import ChatOllama -from langchain_community.chat_models.fake import FakeListChatModel -from langchain.schema import HumanMessage, SystemMessage, AIMessage +from typing import List + +from langchain.prompts import ChatPromptTemplate +from langchain.schema import AIMessage, HumanMessage, SystemMessage +from langchain_community.chat_models.fake import FakeListChatModel + +from honcho.client import HonchoClient +# from client import HonchoClient + +honcho = HonchoClient(base_url="http://localhost:8000") -# llm = ChatOllama(model="phi") responses = ["Fake LLM Response :)"] llm = FakeListChatModel(responses=responses) system = SystemMessage(content="You are world class technical documentation writer. Be as concise as possible") -memory = [] +user = "CLI-Test" +session = honcho.create_session(user_id=user) +session_id = session["id"] + +def langchain_message_converter(messages: List): + new_messages = [] + for message in messages: + if message["is_user"]: + new_messages.append(HumanMessage(content=message["content"])) + else: + new_messages.append(AIMessage(content=message["content"])) + return new_messages def chat(): while True: user_input = input("User: ") if user_input == "exit": + honcho.delete_session(user, session_id) break - # print(user_input) - user_mesage = HumanMessage(content=user_input) + user_message = HumanMessage(content=user_input) + history = honcho.get_messages_for_session(user, session_id) + langchain_history = langchain_message_converter(history) prompt = ChatPromptTemplate.from_messages([ system, - *memory, - user_mesage + *langchain_history, + user_message ]) chain = prompt | llm response = chain.invoke({}) print(type(response)) print(f"AI: {response.content}") - memory.append(HumanMessage(content=user_input)) - memory.append(response) + honcho.create_message_for_session(user, session_id, is_user=True, content=user_input) + honcho.create_message_for_session(user, session_id, is_user=False, content=response.content) chat() diff --git a/sdk/honcho/__init__.py b/sdk/honcho/__init__.py index e69de29b..911723c1 100644 --- a/sdk/honcho/__init__.py +++ b/sdk/honcho/__init__.py @@ -0,0 +1,2 @@ +from .client import HonchoClient +from .cache import LRUCache diff --git a/sdk/honcho/cache.py b/sdk/honcho/cache.py index 35cc2134..cc0e2d68 100644 --- a/sdk/honcho/cache.py +++ b/sdk/honcho/cache.py @@ -1,45 +1,10 @@ - -""" -Below is an implementation of a basic LRUcache that utilizes the built -in OrderedDict data structure. -""" from collections import OrderedDict -import uuid -from typing import List, Dict - -class Session: - "Wrapper Class for storing contexts between channels. Using an object to pass by reference avoid additional cache hits" - def __init__(self, user_id: str, session_id: str = str(uuid.uuid4()), location_id: str = "web", metadata: Dict = {}): - self.user_id: str = user_id - self.session_id: str = session_id - self.location_id: str = location_id - self.metadata: Dict = metadata - - def add_message(self, message_type: str, message: str,) -> None: - self.mediator.add_message(self.session_id, self.user_id, message_type, message) - - def messages(self, message_type: str) -> List[str]: - return self.mediator.messages(self.session_id, self.user_id, message_type) - - def delete(self) -> None: - self.mediator.delete_session(self.session_id) - - def restart(self) -> None: - self.delete() - representation = self.mediator.add_session(user_id=self.user_id, location_id=self.location_id) - self.session_id: str = representation["id"] - self.metadata = representation["metadata"] - - # vector DB fn - def add_texts(self, texts: List[str]) -> None: - metadatas = [{"session_id": self.session_id, "user_id": self.user_id} for _ in range(len(texts))] - self.mediator.vector_table.add_texts(texts, metadatas) - - # vector DB fn - def similarity_search(self, query: str, match_count: int = 5) : - return self.mediator.vector_table.similarity_search(query=query, k=match_count, filter={"user_id": self.user_id}) class LRUCache: + """ + An implementation of a basic LRUcache that utilizes the built + in OrderedDict data structure. + """ def __init__(self, capacity: int): self.capacity = capacity self.cache = OrderedDict() @@ -52,7 +17,7 @@ class LRUCache: self.cache.move_to_end(key) return self.cache[key] - def put(self, key: str, value: Session): + def put(self, key: str, value): if key in self.cache: # If the key already exists, move it to the end and update the value self.cache.move_to_end(key) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index e17eb20b..12e327ea 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,47 +1,53 @@ +from typing import Dict import requests -from dotenv import load_dotenv -load_dotenv() - - -class Client: +class HonchoClient: def __init__(self, base_url): self.base_url = base_url - def get_sessions(self, user_id): - url = f"{self.base_url}/" - pass - - def get_session(self, user_id, session_id): - url = f"{self.base_url}/" - pass - - def create_session(self, user_id, location_id): - url = f"{self.base_url}/" - pass - - def update_session(self, user_id, session_id): - url = f"{self.base_url}/" - pass - - def delete_session(self, user_id, session_id): - url = f"{self.base_url}/" - pass - - def get_messages_for_session(self, user_id, session_id): - url = f"{self.base_url}/" - pass - - def create_message_for_session(self, user_id, session_id): - url = f"{self.base_url}/" - pass - - def get_data(self, endpoint, params=None): - url = f"{self.base_url}/{endpoint}" - response = requests.get(url, params=params) + def get_sessions(self, user_id: str): + url = f"{self.base_url}/users/{user_id}/sessions" + response = requests.get(url) return response.json() + def get_session(self, user_id: str, session_id: int): + url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" + response = requests.get(url) + return response.json() + def create_session(self, user_id: str, location_id: str = "default", session_data: Dict = {}): + data = { + "location_id": location_id, + "session_data": session_data + } + url = f"{self.base_url}/users/{user_id}/sessions" + response = requests.post(url, json=data) + return response.json() + def update_session(self, user_id: str, session_id: int, session_data: Dict): + data = { + "session_data": session_data + } + url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" + response = requests.put(url, json=data) + return response.json() + def delete_session(self, user_id: str, session_id: int): + url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" + response = requests.delete(url) + return response.json() + + def get_messages_for_session(self, user_id: str, session_id: int): + url = f"{self.base_url}/users/{user_id}/sessions/{session_id}/messages" + response = requests.get(url) + return response.json() + + def create_message_for_session(self, user_id: str, session_id: int, is_user: bool, content: str): + data = { + "is_user": is_user, + "content": content + } + url = f"{self.base_url}/users/{user_id}/sessions/{session_id}/messages" + response = requests.post(url, json=data) + return response.json() diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 1f497969..6ab39367 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -124,21 +124,6 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] -[[package]] -name = "python-dotenv" -version = "1.0.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - [[package]] name = "requests" version = "2.31.0" @@ -181,4 +166,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "fddcdf1dfabb47768a391b0ef9c6a75ceed0e5cc69c7dd3c003512a7ff49275e" +content-hash = "a5780ef8e06df616beb6eb67292099db49b8fe658fcbf22940e5e1af96a7c14e" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 4e5235fb..94232050 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "honcho-sdk" +name = "honcho" version = "0.1.0" description = "Python Client SDK for Honcho" authors = ["Plastic Labs "] @@ -9,7 +9,6 @@ packages = [{include = "honcho"}] [tool.poetry.dependencies] python = "^3.11" -python-dotenv = "^1.0.0" requests = "^2.31.0"