File size: 4,888 Bytes
920a9a0
 
ab13803
920a9a0
5d7b030
 
 
 
 
 
 
 
 
 
1e333df
920a9a0
 
5d7b030
 
 
 
 
 
 
 
 
 
 
 
920a9a0
 
 
 
 
 
 
 
 
 
 
 
5d7b030
 
 
920a9a0
5d7b030
 
920a9a0
 
 
5d7b030
 
 
 
 
0b2a850
5d7b030
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b2a850
5d7b030
 
 
 
920a9a0
5d7b030
 
 
 
920a9a0
 
 
 
ab13803
 
5d7b030
 
 
 
 
 
ab13803
920a9a0
5d7b030
 
 
 
 
 
a4b0766
 
 
5d7b030
 
 
 
920a9a0
 
 
5d7b030
 
 
 
 
 
920a9a0
 
 
 
 
 
 
 
 
0b2a850
920a9a0
 
5d7b030
 
920a9a0
 
 
5d7b030
9567c67
920a9a0
 
 
a4b0766
5d7b030
a4b0766
5d7b030
a4b0766
920a9a0
 
5d7b030
920a9a0
 
ab13803
0b2a850
920a9a0
 
 
 
 
 
 
 
8196738
920a9a0
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import re
import os
import panel as pn
from panel.io.mime_render import exec_with_return
from llama_index import (
    VectorStoreIndex,
    SimpleDirectoryReader,
    ServiceContext,
    StorageContext,
    load_index_from_storage,
)
from llama_index.chat_engine import ContextChatEngine
from llama_index.embeddings import OpenAIEmbedding
from llama_index.llms import OpenAI

pn.extension("codeeditor", sizing_mode="stretch_width")

SYSTEM_PROMPT = (
    "You are a renowned data visualization expert "
    "with a strong background in hvplot and holoviews. "
    "Note, hvplot is built on top of holoviews; so "
    "anything you can do with holoviews, you can do "
    "with hvplot, but prioritize hvplot kwargs "
    "first as its simpler. Your primary goal is "
    "to assist the user in edit the code based on user request "
    "using best practices. Simply provide code "
    "in code fences (```python). You absolutely "
    "must have `hvplot_obj` as the last line of code. FYI,"
    "Data columns: ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']"
)

USER_CONTENT_FORMAT = """
Request:
{content}

Code:
```python
{code}
```
""".strip()

DEFAULT_HVPLOT = """
import hvplot.pandas
from bokeh.sampledata.iris import flowers

hvplot_obj = flowers.hvplot(x='petal_length', y='petal_width', by='species', kind='scatter')
hvplot_obj
""".strip()


def init_llm(event):
    api_key = event.new
    if not api_key:
        api_key = os.environ.get("OPENAI_API_KEY")
    if not api_key:
        return
    pn.state.cache["llm"] = OpenAI(api_key=api_key)


def create_chat_engine(llm):
    try:
        storage_context = StorageContext.from_defaults(persist_dir="persisted/")
        index = load_index_from_storage(storage_context=storage_context)
    except Exception as exc:
        embed_model = OpenAIEmbedding()
        service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)
        documents = SimpleDirectoryReader(
            input_dir="hvplot_docs", required_exts=[".md"], recursive=True
        ).load_data()
        index = VectorStoreIndex.from_documents(
            documents, service_context=service_context, show_progress=True
        )
        index.storage_context.persist("persisted/")

    retriever = index.as_retriever()
    chat_engine = ContextChatEngine.from_defaults(
        system_prompt=SYSTEM_PROMPT,
        retriever=retriever,
        verbose=True,
    )
    return chat_engine


def callback(content: str, user: str, instance: pn.chat.ChatInterface):
    if "llm" not in pn.state.cache:
        yield "Need to set OpenAI API key first"
        return

    if "engine" not in pn.state.cache:
        engine = pn.state.cache["engine"] = create_chat_engine(pn.state.cache["llm"])
    else:
        engine = pn.state.cache["engine"]

    # new user contents
    user_content = USER_CONTENT_FORMAT.format(
        content=content, code=code_editor.value
    )

    # send user content to chat engine
    agent_response = engine.stream_chat(user_content)

    message = None
    for chunk in agent_response.response_gen:
        message = instance.stream(chunk, message=message, user="OpenAI")

    # extract code
    llm_matches = re.findall(r"```python\n(.*)\n```", message.object, re.DOTALL)
    if llm_matches:
        llm_code = llm_matches[0]
        if llm_code.splitlines()[-1].strip() != "hvplot_obj":
            llm_code += "\nhvplot_obj"
        code_editor.value = llm_code


def update_plot(event):
    try:
        hvplot_pane.object = exec_with_return(event.new)
    except Exception as exc:
        chat_interface.send(f"Fix this error: {exc}")


# instantiate widgets and panes
api_key_input = pn.widgets.PasswordInput(
    placeholder=(
        "Currently subsidized by Andrew, "
        "but you can also pass your own OpenAI API Key"
    )
)
chat_interface = pn.chat.ChatInterface(
    callback=callback,
    show_clear=False,
    show_undo=False,
    show_button_name=False,
    message_params=dict(
        show_reaction_icons=False,
        show_copy_icon=False,
    ),
    height=650,
    callback_exception="verbose",
)
hvplot_pane = pn.pane.HoloViews(
    exec_with_return(DEFAULT_HVPLOT),
    sizing_mode="stretch_both",
)
code_editor = pn.widgets.CodeEditor(
    value=DEFAULT_HVPLOT,
    language="python",
    sizing_mode="stretch_both",
)

# watch for code changes
api_key_input.param.watch(init_llm, "value")
code_editor.param.watch(update_plot, "value")
api_key_input.param.trigger("value")

# lay them out
tabs = pn.Tabs(
    ("Plot", hvplot_pane),
    ("Code", code_editor),
)

sidebar = [api_key_input, chat_interface]
main = [tabs]
template = pn.template.FastListTemplate(
    sidebar=sidebar,
    main=main,
    sidebar_width=600,
    main_layout=None,
    accent_base_color="#fd7000",
    header_background="#fd7000",
    title="Chat with Plot"
)
template.servable()