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,
}
pn.extension()
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[type(widget_cls.param[key])]
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": content}
def _generate_response(messages: list, response_model: Type[BaseModel]):
return client.chat.completions.create(
model="gpt-4", response_model=response_model, messages=messages
)
class FieldWidgetName(BaseModel):
label: str
widget_name: Literal[pn.widgets.__all__]
class BestMatches(BaseModel):
field_widget: list[FieldWidgetName] = Field(
description=(
"The most suitable widgets to use to collect "
"user input in a form based on the query."
)
)
def respond(query: str, user: str, instance: pn.chat.ChatInterface):
messages = [_format_message(query)]
best_matches = _generate_response(messages, BestMatches)
widgets = []
for best_match in best_matches.field_widget:
widget_cls = getattr(pn.widgets, best_match.widget_name)
widget_label = best_match.label
widget_model = _create_model_from_widget(widget_cls)
messages += [
_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))
widgets.append(widget)
return pn.Column(*widgets)
client = instructor.patch(OpenAI())
chat = pn.chat.ChatInterface(
callback=respond, help_text="Helps generate a form based on a query."
)
chat.show()