Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto create forms based on input #133

Open
ahuang11 opened this issue Mar 5, 2024 · 0 comments
Open

Auto create forms based on input #133

ahuang11 opened this issue Mar 5, 2024 · 0 comments

Comments

@ahuang11
Copy link
Collaborator

ahuang11 commented Mar 5, 2024

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()
image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant