Skip to content

Commit

Permalink
Release/1.0.0 (Chainlit#587)
Browse files Browse the repository at this point in the history
* init

* base data layer

* add step to data layer

* add queue until user message

* remove data_persistence from config

* upload askfilemessage response as file element

* step context

* step context

* llama index integration + step elements

* haystack integration + step error

* langchain integration + error handling

* feedback

* feedback

* refactor AppUser to User

* migrate react-client

* migrate react-components

* migrate main ui

* fix mypy

* fix type import

* fix step issues + langchain issues

* token count

* remove IMessage and MessageDict

* wip fix tests

* fix existing tests

* add data layer test

* action toast

* remove seconds from message time

* add support for action interruption

* rename appuser to user

* toast style

* fix update thread

* use http for file uploads

* remove useless create_task

* wip data layer

* rename client step type

* fix chainlit hello

* wip data layer

* fix test

* wip data layer

* add root param to step

* fix llama index callback handler

* add step show input

* fix final answer streaming

* update readme

* step type lower case

* chainlit_client

* debug ci

* debug ci

* bump sdk version
  • Loading branch information
willydouhard authored Dec 12, 2023
1 parent 21ad163 commit 3a4a7ce
Show file tree
Hide file tree
Showing 230 changed files with 4,688 additions and 4,065 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ dist

.env

*.files

poetry.lock

venv
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

Nothing is unreleased!

## [1.0.0rc0] - 2023-12-12

### Added

- cl.Step

### Changed

- File upload uses HTTP instead of WS and no longer has size limitation
- `cl.AppUser` becomes `cl.User`
- `Prompt` has been split in `ChatGeneration` and `CompletionGeneration`
- `Action` now display a toaster in the UI while running

## [0.7.700] - 2023-11-28

### Added
Expand Down
42 changes: 10 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,7 @@

Chainlit is an open-source async Python framework that makes it incredibly fast to build Chat GPT like applications with your **own business logic and data**.

Contact us here for **Enterprise Support** and to get early access to the **Monitoring** product: https://forms.gle/BX3UNBLmTF75KgZVA

Key features:

- [💬 Multi Modal chats](https://docs.chainlit.io/chat-experience/elements)
- [💭 Chain of Thought visualisation](https://docs.chainlit.io/observability-iteration/chain-of-thought)
- [💾 Data persistence + human feedback](https://docs.chainlit.io/chat-data/overview)
- [🛝 In context Prompt Playground](https://docs.chainlit.io/observability-iteration/prompt-playground/overview)
- [👤 Authentication](https://docs.chainlit.io/authentication/overview)
Contact us [here](https://forms.gle/BX3UNBLmTF75KgZVA) for **Enterprise Support** and to get early access to the **Analytics & Observability** product.

https://github.com/Chainlit/chainlit/assets/13104895/8882af90-fdfa-4b24-8200-1ee96c6c7490

Expand Down Expand Up @@ -49,11 +41,16 @@ Create a new file `demo.py` with the following code:
import chainlit as cl


@cl.step
def tool():
return "Response from the tool!"


@cl.on_message # this function will be called every time a user inputs a message in the UI
async def main(message: cl.Message):
"""
This function is called every time a user inputs a message in the UI.
It sends back an intermediate response from Tool 1, followed by the final answer.
It sends back an intermediate response from the tool, followed by the final answer.
Args:
message: The user's message.
Expand All @@ -62,15 +59,11 @@ async def main(message: cl.Message):
None.
"""

# Send an intermediate response from Tool 1.
await cl.Message(
author="Tool 1",
content=f"Response from tool1",
parent_id=message.id,
).send()
# Call the tool
tool()

# Send the final answer.
await cl.Message(content=f"This is the final answer").send()
await cl.Message(content="This is the final answer").send()
```

Now run it!
Expand All @@ -90,7 +83,6 @@ Chainlit is compatible with all Python programs and libraries. That being said,
- [OpenAI Assistant](https://github.com/Chainlit/cookbook/tree/main/openai-assistant)
- [Llama Index](https://docs.chainlit.io/integrations/llama-index)
- [Haystack](https://docs.chainlit.io/integrations/haystack)
- [Langflow](https://docs.chainlit.io/integrations/langflow)

## 🎨 Custom Frontend

Expand All @@ -109,22 +101,8 @@ To build and connect your own frontend, check out our [Custom Frontend Cookbook]

You can find various examples of Chainlit apps [here](https://github.com/Chainlit/cookbook) that leverage tools and services such as OpenAI, Anthropiс, LangChain, LlamaIndex, ChromaDB, Pinecone and more.

## 🛣 Roadmap

- [x] Selectable chat profiles (at the beginning of a chat)
- [ ] One click chat sharing
- New clients:
- [x] Custom React app
- [ ] Slack
- [ ] Discord
- [ ] Website embbed

Tell us what you would like to see added in Chainlit using the Github issues or on [Discord](https://discord.gg/k73SQ3FyUh).

## 🏢 Enterprise support

For entreprise grade features and self hosting, please visit this [page](https://docs.chainlit.io/cloud/persistence/enterprise) and fill the form.

## 💁 Contributing

As an open-source initiative in a rapidly evolving domain, we welcome contributions, be it through the addition of new features or the improvement of documentation.
Expand Down
55 changes: 32 additions & 23 deletions backend/chainlit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
env_found = load_dotenv(dotenv_path=os.path.join(os.getcwd(), ".env"))

import asyncio
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

from starlette.datastructures import Headers

Expand All @@ -21,8 +21,8 @@
from chainlit.action import Action
from chainlit.cache import cache
from chainlit.chat_settings import ChatSettings
from chainlit.client.base import AppUser, ConversationDict, PersistedAppUser
from chainlit.config import config
from chainlit.context import context
from chainlit.element import (
Audio,
Avatar,
Expand All @@ -46,51 +46,54 @@
Message,
)
from chainlit.oauth_providers import get_configured_oauth_providers
from chainlit.step import Step, step
from chainlit.sync import make_async, run_sync
from chainlit.telemetry import trace
from chainlit.types import ChatProfile, FileSpec
from chainlit.types import ChatProfile, ThreadDict
from chainlit.user import PersistedUser, User
from chainlit.user_session import user_session
from chainlit.utils import make_module_getattr, wrap_user_function
from chainlit.version import __version__
from chainlit_client import ChatGeneration, CompletionGeneration, GenerationMessage

if env_found:
logger.info("Loaded .env file")


@trace
def password_auth_callback(func: Callable[[str, str], Optional[AppUser]]) -> Callable:
def password_auth_callback(func: Callable[[str, str], Optional[User]]) -> Callable:
"""
Framework agnostic decorator to authenticate the user.
Args:
func (Callable[[str, str], Optional[AppUser]]): The authentication callback to execute. Takes the email and password as parameters.
func (Callable[[str, str], Optional[User]]): The authentication callback to execute. Takes the email and password as parameters.
Example:
@cl.password_auth_callback
async def password_auth_callback(username: str, password: str) -> Optional[AppUser]:
async def password_auth_callback(username: str, password: str) -> Optional[User]:
Returns:
Callable[[str, str], Optional[AppUser]]: The decorated authentication callback.
Callable[[str, str], Optional[User]]: The decorated authentication callback.
"""

config.code.password_auth_callback = wrap_user_function(func)
return func


@trace
def header_auth_callback(func: Callable[[Headers], Optional[AppUser]]) -> Callable:
def header_auth_callback(func: Callable[[Headers], Optional[User]]) -> Callable:
"""
Framework agnostic decorator to authenticate the user via a header
Args:
func (Callable[[Headers], Optional[AppUser]]): The authentication callback to execute.
func (Callable[[Headers], Optional[User]]): The authentication callback to execute.
Example:
@cl.header_auth_callback
async def header_auth_callback(headers: Headers) -> Optional[AppUser]:
async def header_auth_callback(headers: Headers) -> Optional[User]:
Returns:
Callable[[Headers], Optional[AppUser]]: The decorated authentication callback.
Callable[[Headers], Optional[User]]: The decorated authentication callback.
"""

config.code.header_auth_callback = wrap_user_function(func)
Expand All @@ -99,20 +102,20 @@ async def header_auth_callback(headers: Headers) -> Optional[AppUser]:

@trace
def oauth_callback(
func: Callable[[str, str, Dict[str, str], AppUser], Optional[AppUser]]
func: Callable[[str, str, Dict[str, str], User], Optional[User]]
) -> Callable:
"""
Framework agnostic decorator to authenticate the user via oauth
Args:
func (Callable[[str, str, Dict[str, str], AppUser], Optional[AppUser]]): The authentication callback to execute.
func (Callable[[str, str, Dict[str, str], User], Optional[User]]): The authentication callback to execute.
Example:
@cl.oauth_callback
async def oauth_callback(provider_id: str, token: str, raw_user_data: Dict[str, str], default_app_user: AppUser) -> Optional[AppUser]:
async def oauth_callback(provider_id: str, token: str, raw_user_data: Dict[str, str], default_app_user: User) -> Optional[User]:
Returns:
Callable[[str, str, Dict[str, str], AppUser], Optional[AppUser]]: The decorated authentication callback.
Callable[[str, str, Dict[str, str], User], Optional[User]]: The decorated authentication callback.
"""

if len(get_configured_oauth_providers()) == 0:
Expand Down Expand Up @@ -158,7 +161,7 @@ def on_chat_start(func: Callable) -> Callable:


@trace
def on_chat_resume(func: Callable[[ConversationDict], Any]) -> Callable:
def on_chat_resume(func: Callable[[ThreadDict], Any]) -> Callable:
"""
Hook to react to resume websocket connection event.
Expand All @@ -175,16 +178,16 @@ def on_chat_resume(func: Callable[[ConversationDict], Any]) -> Callable:

@trace
def set_chat_profiles(
func: Callable[[Optional["AppUser"]], List["ChatProfile"]]
func: Callable[[Optional["User"]], List["ChatProfile"]]
) -> Callable:
"""
Programmatic declaration of the available chat profiles (can depend on the AppUser from the session if authentication is setup).
Programmatic declaration of the available chat profiles (can depend on the User from the session if authentication is setup).
Args:
func (Callable[[Optional["AppUser"]], List["ChatProfile"]]): The function declaring the chat profiles.
func (Callable[[Optional["User"]], List["ChatProfile"]]): The function declaring the chat profiles.
Returns:
Callable[[Optional["AppUser"]], List["ChatProfile"]]: The decorated function.
Callable[[Optional["User"]], List["ChatProfile"]]: The decorated function.
"""

config.code.set_chat_profiles = wrap_user_function(func)
Expand Down Expand Up @@ -225,7 +228,7 @@ def author_rename(func: Callable[[str], str]) -> Callable[[str], str]:
@trace
def on_stop(func: Callable) -> Callable:
"""
Hook to react to the user stopping a conversation.
Hook to react to the user stopping a thread.
Args:
func (Callable[[], Any]): The stop hook to execute.
Expand Down Expand Up @@ -291,8 +294,8 @@ def sleep(duration: int):
__all__ = [
"user_session",
"Action",
"AppUser",
"PersistedAppUser",
"User",
"PersistedUser",
"Audio",
"Pdf",
"Plotly",
Expand All @@ -312,6 +315,11 @@ def sleep(duration: int):
"AskUserMessage",
"AskActionMessage",
"AskFileMessage",
"Step",
"step",
"ChatGeneration",
"CompletionGeneration",
"GenerationMessage",
"on_chat_start",
"on_chat_end",
"on_chat_resume",
Expand All @@ -325,6 +333,7 @@ def sleep(duration: int):
"run_sync",
"make_async",
"cache",
"context",
"LangchainCallbackHandler",
"AsyncLangchainCallbackHandler",
"LlamaIndexCallbackHandler",
Expand Down
19 changes: 9 additions & 10 deletions backend/chainlit/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from typing import Any, Dict

import jwt
from chainlit.client.cloud import AppUser
from chainlit.config import config
from chainlit.data import chainlit_client
from chainlit.data import get_data_layer
from chainlit.oauth_providers import get_configured_oauth_providers
from chainlit.user import User
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer

Expand Down Expand Up @@ -47,7 +47,7 @@ def get_configuration():
}


def create_jwt(data: AppUser) -> str:
def create_jwt(data: User) -> str:
to_encode = data.to_dict() # type: Dict[str, Any]
to_encode.update(
{
Expand All @@ -67,21 +67,20 @@ async def authenticate_user(token: str = Depends(reuseable_oauth)):
options={"verify_signature": True},
)
del dict["exp"]
app_user = AppUser(**dict)
user = User(**dict)
except Exception as e:
raise HTTPException(status_code=401, detail="Invalid authentication token")

if chainlit_client:
if data_layer := get_data_layer():
try:
persisted_app_user = await chainlit_client.get_app_user(app_user.username)
persisted_user = await data_layer.get_user(user.identifier)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if persisted_app_user == None:
if persisted_user == None:
raise HTTPException(status_code=401, detail="User does not exist")

return persisted_app_user
return persisted_user
else:
return app_user
return user


async def get_current_user(token: str = Depends(reuseable_oauth)):
Expand Down
3 changes: 1 addition & 2 deletions backend/chainlit/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from chainlit.logger import logger
from chainlit.markdown import init_markdown
from chainlit.secret import random_secret
from chainlit.server import app, max_message_size, register_wildcard_route_handler
from chainlit.server import app, register_wildcard_route_handler
from chainlit.telemetry import trace_event


Expand Down Expand Up @@ -73,7 +73,6 @@ async def start():
host=host,
port=port,
log_level=log_level,
ws_max_size=max_message_size,
ws_per_message_deflate=ws_per_message_deflate,
)
server = uvicorn.Server(config)
Expand Down
Loading

0 comments on commit 3a4a7ce

Please sign in to comment.