Open ahuang11 opened 9 months ago
https://github.com/holoviz-topics/panel-chat-examples/assets/15331990/bec50f60-beb8-4fc9-b727-7a1a15c6ddad
import json import datetime from typing import Tuple, Dict, Type, Callable, Union from typing import Literal import param import instructor from openai import OpenAI from pydantic import BaseModel, Field, create_model as _create_model from pydantic.fields import FieldInfo import panel as pn DATE_TYPE = Union[datetime.datetime, datetime.date] PARAM_TYPE_MAPPING: Dict[param.Parameter, Type] = { param.String: str, param.Integer: int, param.Number: float, param.Boolean: bool, param.Event: bool, param.Date: DATE_TYPE, param.DateRange: Tuple[DATE_TYPE], param.CalendarDate: DATE_TYPE, param.CalendarDateRange: Tuple[DATE_TYPE], param.Parameter: object, param.Color: str, param.Callable: Callable, param.List: list, param.ObjectSelector: object, } pn.extension() class FieldWidgetName(BaseModel): label: str widget_name: Literal[pn.widgets.__all__] class BestMatch(BaseModel): response: str = Field(description="Be a helpful chatbot assistant.") requires_widget: bool = Field( description="Whether the query requires a widget to be created." ) field_widget: FieldWidgetName | None = Field( default=None, description=( "The most suitable widgets to use to collect " "user input in a form based on the query." ), ) def _create_model_from_widget(widget_cls: Type[pn.widgets.Widget]) -> Type[BaseModel]: param_fields = {} common_keys = pn.widgets.Widget.param.values().keys() for key in widget_cls.param.values().keys() - common_keys | {"name"}: type_ = PARAM_TYPE_MAPPING.get(type(widget_cls.param[key]), str) param_fields[key] = ( type_, FieldInfo( description=getattr(widget_cls.param, key).doc, default=None, required=False, ), ) doc = ( "Hydrate this based on the initial query. Ensure the `name` is human readable." ) return _create_model(widget_cls.__name__, __doc__=doc, **param_fields) def _hydrate_widget(widget_cls: Type[pn.widgets.Widget], **kwargs) -> pn.widgets.Widget: return widget_cls( **{key: value for key, value in kwargs.items() if value is not None} ) def _format_message(content: str, role: str = "user"): return {"role": role, "content": str(content)} def _generate_response(messages: list, response_model: Type[BaseModel]): return client.chat.completions.create( model="gpt-4", response_model=response_model, messages=messages ) def _create_widget(best_match): widget_cls = getattr(pn.widgets, best_match.widget_name) widget_label = best_match.label widget_model = _create_model_from_widget(widget_cls) messages.extend( [ _format_message( f"Creating {json.dumps(widget_model.model_json_schema())} for {widget_label}", role="assistant", ) ] ) kwargs = _generate_response(messages, widget_model) widget = _hydrate_widget(widget_cls, **dict(kwargs)) pn.bind( react_to_value, widget, name=widget.name, watch=True, ) return widget def react_to_value(value, name): content = f"Selected: {value} from {name=} widget." messages.append(_format_message(content)) chat.send(value) chat.widgets = [pn.chat.ChatAreaInput()] def respond(query: str, user: str, instance: pn.chat.ChatInterface): messages.append(_format_message(query)) best_match = _generate_response(messages, BestMatch) yield best_match.response messages.append(_format_message(best_match.response, role="assistant")) if best_match.requires_widget: widget = _create_widget(best_match.field_widget) instance.widgets = [widget] messages = [] client = instructor.patch(OpenAI()) chat = pn.chat.ChatInterface( callback=respond, auto_send_types=[], help_text="Help answer your questions using Panel widgets.", callback_exception="raise", ) chat.show()
https://github.com/holoviz-topics/panel-chat-examples/assets/15331990/bec50f60-beb8-4fc9-b727-7a1a15c6ddad