turbo-chat
Idiomatic way to build chatgpt apps using async generators in python

About
The ChatGPT API uses a new input format called chatml. In openai's python client, the format is used something like this:
messages = [
{"role": "system", "content": "Greet the user!"},
{"role": "user", "content": "Hello world!"},
]
The idea here is to incrementally build the messages using an async generator and then use that to generate completions. Async generators are incredibly versatile and simple abstraction for doing this kind of stuff. They can also be composed together very easily.
async def example():
yield System(content="Greet the user!")
yield User(content="Hello World!")
output = yield Generate()
print(output.content)
See more detailed example below.
Installation
pip install turbo-chat
Example
from typing import AsyncGenerator, Union
from turbo_chat import (
turbo,
System,
User,
Assistant,
GetInput,
Generate,
run,
)
async def get_user(id):
return {"zodiac": "pisces"}
async def set_user_zodiac(user_id: int):
user_data: dict = await get_user(user_id)
zodiac: str = user_data["zodiac"]
yield User(content=f"My zodiac sign is {zodiac}")
@turbo(temperature=0.0)
async def horoscope(user_id: int):
yield System(content="You are a fortune teller")
async for output in set_user_zodiac(user_id):
yield output
input = yield GetInput(message="What do you want to know?")
yield User(content=input)
value = yield Generate(temperature=0.9)
app: AsyncGenerator[Union[Assistant, GetInput], str] = horoscope({"user_id": 1})
_input = None
while not (result := await (app.run(_input)).done:
if result.needs_input:
_input = input(result.content)
continue
print(result.content)
Custom memory
You can also customize how the messages are persisted in-between the executions.
from turbo_chat import turbo, BaseMemory
class RedisMemory(BaseMemory):
"""Implement BaseMemory methods here"""
async def setup(self, **kwargs) -> None:
...
async def append(self, item) -> None:
...
async def clear(self) -> None:
...
@turbo(memory_class=RedisMemory)
async def app():
...
Get access to memory object directly (just declare an additional param)
@turbo()
async def app(some_param: Any, memory: BaseMemory):
messages = await memory.get()
...
Generate a response to use internally but don't yield downstream
@turbo()
async def example():
yield System(content="You are a good guy named John")
yield User(content="What is your name?")
result = yield Generate(forward=False)
yield User(content="How are you doing?")
result = yield Generate()
b = example()
results = [output async for output in b]
assert len(results) == 1
Add a simple in-memory cache
You can also subclass the BaseCache
class to create a custom cache.
cache = SimpleCache()
@turbo(cache=cache)
async def example():
yield System(content="You are a good guy named John")
yield User(content="What is your name?")
result = yield Generate()
b = example()
results = [output async for output in b]
assert len(cache.cache) == 1
Latest Changes