From 16e1695f4207f364a2f69f3459792c57bb888e22 Mon Sep 17 00:00:00 2001 From: hyusap Date: Tue, 23 Jan 2024 15:33:01 -0500 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20orient=20the=20objects?= =?UTF-8?q?,=20tests,=20fix=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + api/src/crud.py | 39 +++++-- api/src/schemas.py | 3 + sdk/honcho/client.py | 223 ++++++++++++++++++++++++++------------- sdk/poetry.lock | 106 +++++++++++++++++-- sdk/pyproject.toml | 3 + sdk/tests/test_honcho.py | 43 ++++++++ 7 files changed, 331 insertions(+), 88 deletions(-) create mode 100644 sdk/tests/test_honcho.py diff --git a/.gitignore b/.gitignore index 1903f983..07b2f685 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +api/**/*.db + .vscode/ # Byte-compiled / optimized / DLL files diff --git a/api/src/crud.py b/api/src/crud.py index 73162362..c653e329 100644 --- a/api/src/crud.py +++ b/api/src/crud.py @@ -11,12 +11,19 @@ def get_session(db: Session, session_id: int, user_id: Optional[str] = None): stmt = select(models.Session).where(models.Session.id == session_id) if user_id is not None: stmt = stmt.where(models.Session.user_id == user_id) - return db.scalars(stmt).one_or_none() + session = db.scalars(stmt).one_or_none() + return session # return db.query(models.Session).filter(models.Session.id == session_id).first() -def get_sessions(db: Session, user_id: str, location_id: str | None = None) -> Sequence[schemas.Session]: - stmt = select(models.Session).where(models.Session.user_id == user_id).where(models.Session.is_active.is_(True)) +def get_sessions( + db: Session, user_id: str, location_id: str | None = None +) -> Sequence[schemas.Session]: + stmt = ( + select(models.Session) + .where(models.Session.user_id == user_id) + .where(models.Session.is_active.is_(True)) + ) if location_id is not None: stmt = stmt.where(models.Session.location_id == location_id) @@ -37,7 +44,10 @@ def get_sessions(db: Session, user_id: str, location_id: str | None = None) -> S # .all() # ) -def create_session(db: Session, user_id: str, session: schemas.SessionCreate) -> models.Session: + +def create_session( + db: Session, user_id: str, session: schemas.SessionCreate +) -> models.Session: honcho_session = models.Session( user_id=user_id, location_id=session.location_id, @@ -48,6 +58,7 @@ def create_session(db: Session, user_id: str, session: schemas.SessionCreate) -> db.refresh(honcho_session) return honcho_session + def update_session(db: Session, user_id: str, session_id: int, metadata: dict) -> bool: # stmt = select(models.Session).where(models.Session.id == session_id).where(models.Session.user_id == user_id) # honcho_session = db.scalars(stmt).one_or_none() @@ -55,13 +66,18 @@ def update_session(db: Session, user_id: str, session_id: int, metadata: dict) - # honcho_session = db.get(models.Session, session_id) if honcho_session is None: raise ValueError("Session not found or does not belong to user") - honcho_session.metadata = metadata + honcho_session.session_data = json.dumps(metadata) db.commit() db.refresh(honcho_session) return honcho_session + def delete_session(db: Session, user_id: str, session_id: int) -> bool: - stmt = select(models.Session).where(models.Session.id == session_id).where(models.Session.user_id == user_id) + stmt = ( + select(models.Session) + .where(models.Session.id == session_id) + .where(models.Session.user_id == user_id) + ) honcho_session = db.scalars(stmt).one_or_none() # honcho_session = db.get(models.Session, session_id) if honcho_session is None: @@ -70,7 +86,10 @@ def delete_session(db: Session, user_id: str, session_id: int) -> bool: db.commit() return True -def create_message(db: Session, message: schemas.MessageCreate, user_id: str, session_id: int) -> models.Message: + +def create_message( + db: Session, message: schemas.MessageCreate, user_id: str, session_id: int +) -> models.Message: honcho_session = get_session(db, session_id, user_id) if honcho_session is None: raise ValueError("Session not found or does not belong to user") @@ -85,7 +104,10 @@ def create_message(db: Session, message: schemas.MessageCreate, user_id: str, se db.refresh(honcho_message) return honcho_message -def get_messages(db: Session, user_id: str, session_id: int) -> Sequence[schemas.Message]: + +def get_messages( + db: Session, user_id: str, session_id: int +) -> Sequence[schemas.Message]: session = get_session(db, session_id, user_id) if session is None: raise ValueError("Session not found or does not belong to user") @@ -97,6 +119,7 @@ def get_messages(db: Session, user_id: str, session_id: int) -> Sequence[schemas # .all() # ) + # def get_metacognitions(db: Session, message_id: int): # return ( # db.query(models.Metacognitions) diff --git a/api/src/schemas.py b/api/src/schemas.py index 404cd169..34058b9d 100644 --- a/api/src/schemas.py +++ b/api/src/schemas.py @@ -35,6 +35,9 @@ class Session(SessionBase): id: int messages: list[Message] is_active: bool + user_id: str + location_id: str + session_data: str class Config: orm_mode = True diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 5a56aadf..6858fe74 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -1,24 +1,12 @@ +import json from typing import Dict import requests + class Client: def __init__(self, base_url): """Constructor for Client""" - self.base_url = base_url # Base URL for the instance of the Honcho API - - def get_sessions(self, user_id: str): - """Return sessions associated with a user - - Args: - user_id (str): The User ID representing the user, managed by the user - - Returns: - list[Dict]: List of Session objects - - """ - url = f"{self.base_url}/users/{user_id}/sessions" - response = requests.get(url) - return response.json() + self.base_url = base_url # Base URL for the instance of the Honcho API def get_session(self, user_id: str, session_id: int): """Get a specific session for a user by ID @@ -33,11 +21,33 @@ class Client: """ url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" response = requests.get(url) - return response.json() + data = response.json() + return Session( + client=self, + id=data["id"], + user_id=data["user_id"], + location_id=data["location_id"], + is_active=data["is_active"], + session_data=data["session_data"], + ) + + def get_sessions(self, user_id: str): + """Return sessions associated with a user + + Args: + user_id (str): The User ID representing the user, managed by the user + + Returns: + list[Dict]: List of Session objects + + """ + url = f"{self.base_url}/users/{user_id}/sessions" + response = requests.get(url) + return [Session(self, **session) for session in response.json()] def get_sessions_by_location(self, user_id: str, location_id: str): """Get all sessions for a user for a specific location - + Args: user_id (str): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session @@ -48,9 +58,11 @@ class Client: """ url = f"{self.base_url}/users/{user_id}/sessions?location_id={location_id}" response = requests.get(url) - return response.json() + return [Session(self, **session) for session in response.json()] - def create_session(self, user_id: str, location_id: str = "default", session_data: Dict = {}): + def create_session( + self, user_id: str, location_id: str = "default", session_data: Dict = {} + ): """Create a session for a user Args: @@ -59,54 +71,99 @@ class Client: session_data (Dict, optional): Optional session metadata Returns: - Dict: The Session object of the new Session + Dict: The Session object of the new Session` """ - data = { - "location_id": location_id, - "session_data": session_data - } + 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() + data = response.json() + return Session( + self, + id=data["id"], + user_id=user_id, + location_id=location_id, + session_data=session_data, + is_active=data["is_active"], + ) - def update_session(self, user_id: str, session_id: int, session_data: Dict): - """Update the metadata of a session + # def update_session(self, user_id: str, session_id: int, session_data: Dict): + # """Update the metadata of a session + + # Args: + # user_id (str): The User ID representing the user, managed by the user + # session_id (int): The ID of the Session to update + # session_data (Dict): The Session object containing any new metadata + + # Returns: + # Dict: The Session object of the updated Session + + # """ + # data = {"session_data": session_data} + # url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" + # response = requests.put(url, json=data) + # return Session(self, **response.json()) + + # def delete_session(self, user_id: str, session_id: int): + # """Delete a session by marking it as inactive + + # Args: + # user_id (str): The User ID representing the user, managed by the user + # session_id (int): The ID of the Session to delete + + # Returns: + # Bool: Whether the session was successfully deleted + + # """ + # url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" + # response = requests.delete(url) + # success = response.status_code != 404 + # return success + + +class Session: + def __init__( + self, + client: Client, + id: int, + user_id: str, + location_id: str, + session_data: dict | str, + is_active: bool, + ): + """Constructor for Session""" + self.base_url = client.base_url + self.id = id + self.user_id = user_id + self.location_id = location_id + self.session_data = ( + session_data if isinstance(session_data, dict) else json.loads(session_data) + ) + self.is_active = is_active + + def __str__(self): + return f"Session(id={self.id}, user_id={self.user_id}, location_id={self.location_id}, session_data={self.session_data}, is_active={self.is_active})" + + def create_message(self, is_user: bool, content: str): + """Adds a message to the session Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to update - session_data (Dict): The Session object containing any new metadata + is_user (bool): Whether the message is from the user + content (str): The content of the message Returns: - Dict: The Session object of the updated Session + Dict: The Message object of the added message """ - 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() + data = {"is_user": is_user, "content": content} + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" + response = requests.post(url, json=data) + data = response.json() + return Message(self, id=data["id"], is_user=is_user, content=content) - def delete_session(self, user_id: str, session_id: int): - """Delete a session by marking it as inactive - - Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to delete - - Returns: - Dict: A message indicating that the session was deleted - - """ - 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): + def get_messages(self): """Get all messages for a session - + Args: user_id (str): The User ID representing the user, managed by the user session_id (int): The ID of the Session to retrieve @@ -115,28 +172,50 @@ class Client: list[Dict]: List of Message objects """ - url = f"{self.base_url}/users/{user_id}/sessions/{session_id}/messages" + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" response = requests.get(url) - return response.json() + data = response.json() + return [ + Message( + self, + id=message["id"], + is_user=message["is_user"], + content=message["content"], + ) + for message in data + ] + + def update(self, session_data: Dict): + """Update the metadata of a session - def create_message_for_session(self, user_id: str, session_id: int, is_user: bool, content: str): - """Adds a message to a session - Args: - user_id (str): The User ID representing the user, managed by the user - session_id (int): The ID of the Session to add the message to - is_user (bool): Whether the message is from the user - content (str): The content of the message + session_data (Dict): The Session object containing any new metadata + Returns: - Dict: The Message object of the added message - + boolean: Whether the session was successfully updated """ - 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() + info = {"session_data": session_data} + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}" + response = requests.put(url, json=info) + success = response.status_code < 400 + self.session_data = session_data + return success + def delete(self): + """Delete a session by marking it as inactive""" + url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}" + response = requests.delete(url) + self.is_active = False + + +class Message: + def __init__(self, session: Session, id: int, is_user: bool, content: str): + """Constructor for Message""" + self.session = session + self.id = id + self.is_user = is_user + self.content = content + + def __str__(self): + return f"Message(id={self.id}, is_user={self.is_user}, content={self.content})" diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 6ab39367..40c58fa0 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -16,7 +15,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -112,11 +110,35 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -124,11 +146,69 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -146,11 +226,21 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -165,5 +255,5 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" -python-versions = "^3.11" -content-hash = "a5780ef8e06df616beb6eb67292099db49b8fe658fcbf22940e5e1af96a7c14e" +python-versions = "^3.10" +content-hash = "0aa81b5a08a235ff5f275db8e6d0265d97fd1e350567862ee6b5afddaaf15620" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index c65e6ea5..fcf44d45 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -11,6 +11,9 @@ packages = [{include = "honcho"}] python = "^3.10" requests = "^2.31.0" +[tool.poetry.group.test.dependencies] +pytest = "^7.4.4" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/sdk/tests/test_honcho.py b/sdk/tests/test_honcho.py new file mode 100644 index 00000000..4720b60a --- /dev/null +++ b/sdk/tests/test_honcho.py @@ -0,0 +1,43 @@ +from honcho import Client + + +def test_session_creation_retrieval(): + client = Client("http://localhost:8000") + created_session = client.create_session("ayush") + retrieved_session = client.get_session("ayush", created_session.id) + assert retrieved_session.id == created_session.id + assert retrieved_session.is_active == True + assert retrieved_session.location_id == "default" + assert retrieved_session.session_data == {} + + +def test_session_update(): + client = Client("http://localhost:8000") + created_session = client.create_session("ayush") + assert created_session.update({"foo": "bar"}) + retrieved_session = client.get_session("ayush", created_session.id) + assert retrieved_session.session_data == {"foo": "bar"} + + +def test_session_deletion(): + client = Client("http://localhost:8000") + created_session = client.create_session("ayush") + created_session.delete() + retrieved_session = client.get_session("ayush", created_session.id) + assert retrieved_session.is_active == False + assert retrieved_session.id == created_session.id + + +def test_messages(): + client = Client("http://localhost:8000") + created_session = client.create_session("ayush") + created_session.create_message(is_user=True, content="Hello") + created_session.create_message(is_user=False, content="Hi") + retrieved_session = client.get_session("ayush", created_session.id) + messages = retrieved_session.get_messages() + assert len(messages) == 2 + user_message, ai_message = messages + assert user_message.content == "Hello" + assert user_message.is_user == True + assert ai_message.content == "Hi" + assert ai_message.is_user == False From 5d50a65316838b8fa45a381a731ff4611a2eaa32 Mon Sep 17 00:00:00 2001 From: hyusap Date: Tue, 23 Jan 2024 15:49:29 -0500 Subject: [PATCH 2/4] update cli --- example/cli/main.py | 33 ++++++++++++++++++--------------- example/cli/pyproject.toml | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/example/cli/main.py b/example/cli/main.py index 0fbf7810..84910495 100644 --- a/example/cli/main.py +++ b/example/cli/main.py @@ -5,46 +5,49 @@ from langchain.schema import AIMessage, HumanMessage, SystemMessage from langchain_community.chat_models.fake import FakeListChatModel from honcho import Client as HonchoClient + # from client import HonchoClient honcho = HonchoClient(base_url="http://localhost:8000") responses = ["Fake LLM Response :)"] llm = FakeListChatModel(responses=responses) -system = SystemMessage(content="You are world class technical documentation writer. Be as concise as possible") +system = SystemMessage( + content="You are world class technical documentation writer. Be as concise as possible" +) 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"])) + if message.is_user: + new_messages.append(HumanMessage(content=message.content)) else: - new_messages.append(AIMessage(content=message["content"])) + 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) + session.delete() break user_message = HumanMessage(content=user_input) - history = honcho.get_messages_for_session(user, session_id) + history = session.get_messages() langchain_history = langchain_message_converter(history) - prompt = ChatPromptTemplate.from_messages([ - system, - *langchain_history, - user_message - ]) - chain = prompt | llm + prompt = ChatPromptTemplate.from_messages( + [system, *langchain_history, user_message] + ) + chain = prompt | llm response = chain.invoke({}) print(type(response)) print(f"AI: {response.content}") - 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) + session.create_message(is_user=True, content=user_input) + session.create_message(is_user=False, content=response.content) + chat() diff --git a/example/cli/pyproject.toml b/example/cli/pyproject.toml index 66533b2e..6ba8e47f 100644 --- a/example/cli/pyproject.toml +++ b/example/cli/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -honcho-ai = {path = "../../sdk/dist/honcho_ai-0.0.1-py3-none-any.whl"} +honcho-ai = {path = "../../sdk/dist/honcho_ai-0.0.0.dev1-py3-none-any.whl"} [tool.poetry.group.dev.dependencies] langchain = "^0.1.0" From 476f3bd4ed28bed8c8003beec30de71e9a762cec Mon Sep 17 00:00:00 2001 From: hyusap Date: Thu, 25 Jan 2024 15:46:30 -0500 Subject: [PATCH 3/4] add inactivity --- sdk/honcho/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 6858fe74..3aee9fe9 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -155,6 +155,8 @@ class Session: Dict: The Message object of the added message """ + if not self.is_active: + raise Exception("Session is inactive") data = {"is_user": is_user, "content": content} url = f"{self.base_url}/users/{self.user_id}/sessions/{self.id}/messages" response = requests.post(url, json=data) From e6e6a4843da737cea81e90192a6f27c32ddc3dd3 Mon Sep 17 00:00:00 2001 From: hyusap Date: Thu, 25 Jan 2024 16:44:31 -0500 Subject: [PATCH 4/4] update discord, fix get_sessions, add test --- example/cli/poetry.lock | 53 ++++-------------- example/cli/pyproject.toml | 2 +- example/discord/.env.template | 1 + example/discord/main.py | 55 ++++++++----------- example/discord/poetry.lock | 99 +++++++++++----------------------- example/discord/pyproject.toml | 2 +- sdk/honcho/client.py | 65 ++++++---------------- sdk/tests/test_honcho.py | 12 +++++ 8 files changed, 93 insertions(+), 196 deletions(-) create mode 100644 example/discord/.env.template diff --git a/example/cli/poetry.lock b/example/cli/poetry.lock index 86e9825e..02b66a96 100644 --- a/example/cli/poetry.lock +++ b/example/cli/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -100,7 +99,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -115,7 +113,6 @@ frozenlist = ">=1.1.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -127,7 +124,6 @@ files = [ name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -148,7 +144,6 @@ trio = ["trio (>=0.23)"] name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -168,7 +163,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -180,7 +174,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -280,7 +273,6 @@ files = [ name = "dataclasses-json" version = "0.6.3" description = "Easily serialize dataclasses to and from JSON." -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -296,7 +288,6 @@ typing-inspect = ">=0.4.0,<1" name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -383,7 +374,6 @@ files = [ name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -453,27 +443,24 @@ test = ["objgraph", "psutil"] [[package]] name = "honcho-ai" -version = "0.0.1" +version = "0.0.0.dev1" description = "Python Client SDK for Honcho" -category = "main" optional = false -python-versions = ">=3.11,<4.0" -files = [ - {file = "honcho_ai-0.0.1-py3-none-any.whl", hash = "sha256:35297603dc8904600d3bbf43a27f7465f53fbd06e2b03410d070b0107c2a6066"}, -] +python-versions = "^3.10" +files = [] +develop = false [package.dependencies] -requests = ">=2.31.0,<3.0.0" +requests = "^2.31.0" [package.source] -type = "file" -url = "../../sdk/dist/honcho_ai-0.0.1-py3-none-any.whl" +type = "directory" +url = "../../sdk" [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -485,7 +472,6 @@ files = [ name = "jsonpatch" version = "1.33" description = "Apply JSON-Patches (RFC 6902)" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -500,7 +486,6 @@ jsonpointer = ">=1.9" name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -512,7 +497,6 @@ files = [ name = "langchain" version = "0.1.0" description = "Building applications with LLMs through composability" -category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -552,7 +536,6 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"] name = "langchain-community" version = "0.0.11" description = "Community contributed LangChain integrations." -category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -579,7 +562,6 @@ extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15. name = "langchain-core" version = "0.1.10" description = "Building applications with LLMs through composability" -category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -604,7 +586,6 @@ extended-testing = ["jinja2 (>=3,<4)"] name = "langsmith" version = "0.0.80" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -620,7 +601,6 @@ requests = ">=2,<3" name = "marshmallow" version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -641,7 +621,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -725,7 +704,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -737,7 +715,6 @@ files = [ name = "numpy" version = "1.26.3" description = "Fundamental package for array computing in Python" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -783,7 +760,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -795,7 +771,6 @@ files = [ name = "pydantic" version = "2.5.3" description = "Data validation using Python type hints" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -815,7 +790,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.6" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -933,7 +907,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -983,7 +956,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1005,7 +977,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1017,7 +988,6 @@ files = [ name = "sqlalchemy" version = "2.0.25" description = "Database Abstraction Library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1105,7 +1075,6 @@ sqlcipher = ["sqlcipher3_binary"] name = "tenacity" version = "8.2.3" description = "Retry code until it succeeds" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1120,7 +1089,6 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1132,7 +1100,6 @@ files = [ name = "typing-inspect" version = "0.9.0" description = "Runtime inspection utilities for typing module." -category = "dev" optional = false python-versions = "*" files = [ @@ -1148,7 +1115,6 @@ typing-extensions = ">=3.7.4" name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1165,7 +1131,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "yarl" version = "1.9.4" description = "Yet another URL library" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1268,4 +1233,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "9d481494a2edb3ad69103ea4a01e8362dafc7dd17a836c49bbd637df01615111" +content-hash = "4052c9e4cc54a4b0a9c7c41b0e2d97b709cd936bab480b9210ef661729aa2f24" diff --git a/example/cli/pyproject.toml b/example/cli/pyproject.toml index 6ba8e47f..3107b28e 100644 --- a/example/cli/pyproject.toml +++ b/example/cli/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -honcho-ai = {path = "../../sdk/dist/honcho_ai-0.0.0.dev1-py3-none-any.whl"} +honcho-ai = {path = "../../sdk"} [tool.poetry.group.dev.dependencies] langchain = "^0.1.0" diff --git a/example/discord/.env.template b/example/discord/.env.template new file mode 100644 index 00000000..978ac4cd --- /dev/null +++ b/example/discord/.env.template @@ -0,0 +1 @@ +BOT_TOKEN= \ No newline at end of file diff --git a/example/discord/main.py b/example/discord/main.py index 9a2a1c6c..f6223e86 100644 --- a/example/discord/main.py +++ b/example/discord/main.py @@ -1,36 +1,24 @@ import os - import discord from dotenv import load_dotenv load_dotenv() from honcho import Client as HonchoClient -from honcho import LRUCache intents = discord.Intents.default() intents.messages = True intents.message_content = True -CACHE = LRUCache(50) # Support 50 concurrent active conversations cached in memory honcho = HonchoClient("http://localhost:8000") - bot = discord.Bot(intents=intents) -def get_or_create(user_id, location_id) -> int: - key = f"{user_id}+{location_id}" - session_id = CACHE.get(key) - if session_id is None: - session = honcho.create_session(user_id, location_id) - print(session) - session_id = session["id"] - CACHE.put(key, session_id) - return session_id @bot.event async def on_ready(): - print(f'We have logged in as {bot.user}') + print(f"We have logged in as {bot.user}") + @bot.event async def on_message(message): @@ -38,32 +26,33 @@ async def on_message(message): return user_id = f"discord_{str(message.author.id)}" - location_id=str(message.channel.id) - # key = f"{user_id}+{location_id}" + location_id = str(message.channel.id) - session_id = get_or_create(user_id, location_id) + sessions = honcho.get_sessions(user_id, location_id) + if len(sessions) > 0: + session = sessions[0] + else: + session = honcho.create_session(user_id, location_id) inp = message.content - honcho.create_message_for_session(user_id, session_id, True, inp) + session.create_message(is_user=True, content=inp) async with message.channel.typing(): - await message.channel.send("Fake LLM Message") + output = "Fake LLM Message" + await message.channel.send(output) - honcho.create_message_for_session(user_id, session_id, False, "Fake LLM Message") + session.create_message(is_user=False, content=output) -@bot.slash_command(name = "restart", description = "Restart the Conversation") + +@bot.slash_command(name="restart", description="Restart the Conversation") async def restart(ctx): - user_id=f"discord_{str(ctx.author.id)}" - location_id=str(ctx.channel_id) - key = f"{user_id}+{location_id}" - session_id = CACHE.get(key) - if session_id is not None: - honcho.delete_session(user_id, session_id) - session = honcho.create_session(user_id, location_id) - session_id = session["id"] - CACHE.put(key, session_id) + user_id = f"discord_{str(ctx.author.id)}" + location_id = str(ctx.channel_id) + sessions = honcho.get_sessions(user_id, location_id) + sessions[0].delete() if len(sessions) > 0 else None + + await ctx.respond( + "Great! The conversation has been restarted. What would you like to talk about?" + ) - msg = "Great! The conversation has been restarted. What would you like to talk about?" - honcho.create_message_for_session(user_id, session_id, False, msg) - await ctx.respond(msg) bot.run(os.environ["BOT_TOKEN"]) diff --git a/example/discord/poetry.lock b/example/discord/poetry.lock index 271c5752..6686fa8a 100644 --- a/example/discord/poetry.lock +++ b/example/discord/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.6" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -113,7 +112,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -128,7 +126,6 @@ frozenlist = ">=1.1.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -140,7 +137,6 @@ files = [ name = "anyio" version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -161,7 +157,6 @@ trio = ["trio (>=0.23)"] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -173,7 +168,6 @@ files = [ name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -193,7 +187,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -205,7 +198,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -305,7 +297,6 @@ files = [ name = "dataclasses-json" version = "0.6.3" description = "Easily serialize dataclasses to and from JSON." -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -321,7 +312,6 @@ typing-inspect = ">=0.4.0,<1" name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -408,7 +398,6 @@ files = [ name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -480,22 +469,22 @@ test = ["objgraph", "psutil"] name = "honcho-ai" version = "0.0.0.dev1" description = "Python Client SDK for Honcho" -category = "main" optional = false -python-versions = ">=3.11,<4.0" -files = [ - {file = "honcho_ai-0.0.0.dev1-py3-none-any.whl", hash = "sha256:969ddda79f87044ba6728df9cce1a8e48487a46a582d2e59c184bbc13b90baa4"}, - {file = "honcho_ai-0.0.0.dev1.tar.gz", hash = "sha256:c099a56853e34f7abb9292095b71f2e9a5f767fc484d29423773823621eeb924"}, -] +python-versions = "^3.10" +files = [] +develop = false [package.dependencies] -requests = ">=2.31.0,<3.0.0" +requests = "^2.31.0" + +[package.source] +type = "directory" +url = "../../sdk" [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -507,7 +496,6 @@ files = [ name = "jsonpatch" version = "1.33" description = "Apply JSON-Patches (RFC 6902)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -522,7 +510,6 @@ jsonpointer = ">=1.9" name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -532,23 +519,22 @@ files = [ [[package]] name = "langchain" -version = "0.1.1" +version = "0.1.3" description = "Building applications with LLMs through composability" -category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain-0.1.1-py3-none-any.whl", hash = "sha256:3f1dcf458bbd603447e93ece99fe6611b1fafa16dc67464b1c8091dd475242f9"}, - {file = "langchain-0.1.1.tar.gz", hash = "sha256:a9616544b78ccf1a5b286fae7926e00beea6dc5b8fda983e5180313fefd3dfab"}, + {file = "langchain-0.1.3-py3-none-any.whl", hash = "sha256:9802bc17a068518295fd282eb833543793c11d222caf9a0b1c7058fe59b951f3"}, + {file = "langchain-0.1.3.tar.gz", hash = "sha256:7cdebf1bb14181aa614a21b373232c423b0fef06bb97a488a833e11c01524d4a"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" jsonpatch = ">=1.33,<2.0" -langchain-community = ">=0.0.13,<0.1" -langchain-core = ">=0.1.9,<0.2" -langsmith = ">=0.0.77,<0.1.0" +langchain-community = ">=0.0.14,<0.1" +langchain-core = ">=0.1.14,<0.2" +langsmith = ">=0.0.83,<0.1" numpy = ">=1,<2" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -572,21 +558,20 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"] [[package]] name = "langchain-community" -version = "0.0.13" +version = "0.0.15" description = "Community contributed LangChain integrations." -category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_community-0.0.13-py3-none-any.whl", hash = "sha256:655196e446e7f37f4882221b6f3f791d6add28ea596d521ccf6f4507386b9a13"}, - {file = "langchain_community-0.0.13.tar.gz", hash = "sha256:cf66c6ff7fcbeb582f5e88ee00decea7fdeca5ccddda725046f28efc697c41a7"}, + {file = "langchain_community-0.0.15-py3-none-any.whl", hash = "sha256:2b830c79366e192aed2a997a11a69b62505fb2ee8d08a85f3df7bd3ab62473f1"}, + {file = "langchain_community-0.0.15.tar.gz", hash = "sha256:b027d7765661300edced958228e78077780d96332efe63c5949bc5e435cc7c2b"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" -langchain-core = ">=0.1.9,<0.2" -langsmith = ">=0.0.63,<0.1.0" +langchain-core = ">=0.1.14,<0.2" +langsmith = ">=0.0.83,<0.1" numpy = ">=1,<2" PyYAML = ">=5.3" requests = ">=2,<3" @@ -595,24 +580,23 @@ tenacity = ">=8.1.0,<9.0.0" [package.extras] cli = ["typer (>=0.9.0,<0.10.0)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<5)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)", "zhipuai (>=1.0.7,<2.0.0)"] [[package]] name = "langchain-core" -version = "0.1.12" +version = "0.1.15" description = "Building applications with LLMs through composability" -category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain_core-0.1.12-py3-none-any.whl", hash = "sha256:d11c6262f7a9deff7de8fdf14498b8a951020dfed3a80f2358ab731ad04abef0"}, - {file = "langchain_core-0.1.12.tar.gz", hash = "sha256:f18e9300e9a07589b3e280e51befbc5a4513f535949406e55eb7a2dc40c3ce66"}, + {file = "langchain_core-0.1.15-py3-none-any.whl", hash = "sha256:eb2e6a81400a98e8d27638ede158c0dc4245ca8e69578138f5ef4c913258ff95"}, + {file = "langchain_core-0.1.15.tar.gz", hash = "sha256:129c0e92ef9a31971766496a9fade429217bd56d16bb6d0024d27925f26187d6"}, ] [package.dependencies] anyio = ">=3,<5" jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.0.63,<0.1.0" +langsmith = ">=0.0.83,<0.1" packaging = ">=23.2,<24.0" pydantic = ">=1,<3" PyYAML = ">=5.3" @@ -624,14 +608,13 @@ extended-testing = ["jinja2 (>=3,<4)"] [[package]] name = "langsmith" -version = "0.0.82" +version = "0.0.83" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langsmith-0.0.82-py3-none-any.whl", hash = "sha256:b17eed8351bcb375e46ce6d47e1b9bdd41826d0cc167eb96cbe0989ca966ce57"}, - {file = "langsmith-0.0.82.tar.gz", hash = "sha256:0645e0315e836f36a7b5de34efb765fcf3abda1ac79bcaf7a66d40c40e942ae4"}, + {file = "langsmith-0.0.83-py3-none-any.whl", hash = "sha256:a5bb7ac58c19a415a9d5f51db56dd32ee2cd7343a00825bbc2018312eb3d122a"}, + {file = "langsmith-0.0.83.tar.gz", hash = "sha256:94427846b334ad9bdbec3266fee12903fe9f5448f628667689d0412012aaf392"}, ] [package.dependencies] @@ -642,7 +625,6 @@ requests = ">=2,<3" name = "marshmallow" version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -663,7 +645,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -747,7 +728,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -759,7 +739,6 @@ files = [ name = "numpy" version = "1.26.3" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -805,7 +784,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -817,7 +795,6 @@ files = [ name = "py-cord" version = "2.4.1" description = "A Python wrapper for the Discord API" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -837,7 +814,6 @@ voice = ["PyNaCl (>=1.3.0,<1.6)"] name = "pydantic" version = "2.5.3" description = "Data validation using Python type hints" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -857,7 +833,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.6" description = "" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -973,14 +948,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" 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"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -990,7 +964,6 @@ cli = ["click (>=5.0)"] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1040,7 +1013,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1062,7 +1034,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1074,7 +1045,6 @@ files = [ name = "sqlalchemy" version = "2.0.25" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1162,7 +1132,6 @@ sqlcipher = ["sqlcipher3_binary"] name = "tenacity" version = "8.2.3" description = "Retry code until it succeeds" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1177,7 +1146,6 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1189,7 +1157,6 @@ files = [ name = "typing-inspect" version = "0.9.0" description = "Runtime inspection utilities for typing module." -category = "main" optional = false python-versions = "*" files = [ @@ -1205,7 +1172,6 @@ typing-extensions = ">=3.7.4" name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1222,7 +1188,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "yarl" version = "1.9.4" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1325,4 +1290,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "016422ea0dc2d14a79ff54345c9d1c5eaa7ae60aafd389ccd57d6c69c4a18188" +content-hash = "d4e13c95585011ab77ad4cd6a22f4d43282bc253452c8b327099b5a85cb53c03" diff --git a/example/discord/pyproject.toml b/example/discord/pyproject.toml index 552af017..20c83957 100644 --- a/example/discord/pyproject.toml +++ b/example/discord/pyproject.toml @@ -9,9 +9,9 @@ packages = [{include = "honcho_discord_example"}] [tool.poetry.dependencies] python = "^3.11" langchain = "^0.1.0" -honcho-ai = "^0.0.0.dev1" py-cord = "^2.4.1" python-dotenv = "^1.0.0" +honcho-ai = {path = "../../sdk"} [build-system] diff --git a/sdk/honcho/client.py b/sdk/honcho/client.py index 3aee9fe9..f39de5f6 100644 --- a/sdk/honcho/client.py +++ b/sdk/honcho/client.py @@ -31,23 +31,9 @@ class Client: session_data=data["session_data"], ) - def get_sessions(self, user_id: str): + def get_sessions(self, user_id: str, location_id: str | None = None): """Return sessions associated with a user - Args: - user_id (str): The User ID representing the user, managed by the user - - Returns: - list[Dict]: List of Session objects - - """ - url = f"{self.base_url}/users/{user_id}/sessions" - response = requests.get(url) - return [Session(self, **session) for session in response.json()] - - def get_sessions_by_location(self, user_id: str, location_id: str): - """Get all sessions for a user for a specific location - Args: user_id (str): The User ID representing the user, managed by the user location_id (str, optional): Optional Location ID representing the location of a session @@ -56,9 +42,21 @@ class Client: list[Dict]: List of Session objects """ - url = f"{self.base_url}/users/{user_id}/sessions?location_id={location_id}" + url = f"{self.base_url}/users/{user_id}/sessions" + ( + f"?location_id={location_id}" if location_id else "" + ) response = requests.get(url) - return [Session(self, **session) for session in response.json()] + return [ + Session( + client=self, + id=session["id"], + user_id=session["user_id"], + location_id=session["location_id"], + is_active=session["is_active"], + session_data=session["session_data"], + ) + for session in response.json() + ] def create_session( self, user_id: str, location_id: str = "default", session_data: Dict = {} @@ -87,39 +85,6 @@ class Client: is_active=data["is_active"], ) - # def update_session(self, user_id: str, session_id: int, session_data: Dict): - # """Update the metadata of a session - - # Args: - # user_id (str): The User ID representing the user, managed by the user - # session_id (int): The ID of the Session to update - # session_data (Dict): The Session object containing any new metadata - - # Returns: - # Dict: The Session object of the updated Session - - # """ - # data = {"session_data": session_data} - # url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" - # response = requests.put(url, json=data) - # return Session(self, **response.json()) - - # def delete_session(self, user_id: str, session_id: int): - # """Delete a session by marking it as inactive - - # Args: - # user_id (str): The User ID representing the user, managed by the user - # session_id (int): The ID of the Session to delete - - # Returns: - # Bool: Whether the session was successfully deleted - - # """ - # url = f"{self.base_url}/users/{user_id}/sessions/{session_id}" - # response = requests.delete(url) - # success = response.status_code != 404 - # return success - class Session: def __init__( diff --git a/sdk/tests/test_honcho.py b/sdk/tests/test_honcho.py index 4720b60a..ff94522a 100644 --- a/sdk/tests/test_honcho.py +++ b/sdk/tests/test_honcho.py @@ -1,4 +1,5 @@ from honcho import Client +from uuid import uuid1 def test_session_creation_retrieval(): @@ -11,6 +12,17 @@ def test_session_creation_retrieval(): assert retrieved_session.session_data == {} +def test_session_multiple_retrieval(): + client = Client("http://localhost:8000") + user = str(uuid1()) + created_session_1 = client.create_session(user) + created_session_2 = client.create_session(user) + retrieved_sessions = client.get_sessions(user) + assert len(retrieved_sessions) == 2 + assert retrieved_sessions[0].id == created_session_1.id + assert retrieved_sessions[1].id == created_session_2.id + + def test_session_update(): client = Client("http://localhost:8000") created_session = client.create_session("ayush")