Spaces:
Sleeping
Sleeping
Commit
·
d774d12
1
Parent(s):
75425d3
Add app
Browse files- Dockerfile +31 -0
- LICENSE +21 -0
- README.md +1 -0
- app.py +173 -0
- packages.txt +1 -0
- requirements.txt +4 -0
Dockerfile
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11
|
2 |
+
|
3 |
+
RUN apt-get update
|
4 |
+
RUN apt-get --yes install graphviz
|
5 |
+
|
6 |
+
# Set up a new user named "user" with user ID 1000
|
7 |
+
RUN useradd -m -u 1000 user
|
8 |
+
|
9 |
+
# Switch to the "user" user
|
10 |
+
USER user
|
11 |
+
|
12 |
+
# Set home to the user's home directory
|
13 |
+
ENV HOME=/home/user \
|
14 |
+
PATH=/home/user/.local/bin:$PATH
|
15 |
+
|
16 |
+
# Set the working directory to the user's home directory
|
17 |
+
WORKDIR $HOME/app
|
18 |
+
|
19 |
+
# Try and run pip command after setting the user with `USER user` to avoid permission issues with Python
|
20 |
+
RUN pip install --no-cache-dir --upgrade pip
|
21 |
+
|
22 |
+
# Copy the current directory contents into the container at $HOME/app setting the owner to the user
|
23 |
+
COPY --chown=user . $HOME/app
|
24 |
+
|
25 |
+
COPY --chown=user requirements.txt .
|
26 |
+
|
27 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
28 |
+
|
29 |
+
COPY --chown=user app.py app.py
|
30 |
+
|
31 |
+
ENTRYPOINT ["solara", "run", "app.py", "--host=0.0.0.0", "--port", "7860"]
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2024 Alonso Silva Allende
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
CHANGED
@@ -6,6 +6,7 @@ colorTo: gray
|
|
6 |
sdk: docker
|
7 |
pinned: false
|
8 |
license: mit
|
|
|
9 |
---
|
10 |
|
11 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
6 |
sdk: docker
|
7 |
pinned: false
|
8 |
license: mit
|
9 |
+
app_port: 7860
|
10 |
---
|
11 |
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import solara
|
2 |
+
from typing import Any, Callable, Optional, TypeVar, Union, cast, overload, List
|
3 |
+
from typing_extensions import TypedDict
|
4 |
+
import time
|
5 |
+
import ipyvue
|
6 |
+
import reacton
|
7 |
+
from solara.alias import rv as v
|
8 |
+
import os
|
9 |
+
import openai
|
10 |
+
from openai import OpenAI
|
11 |
+
import instructor
|
12 |
+
from pydantic import BaseModel, Field
|
13 |
+
from graphviz import Digraph
|
14 |
+
|
15 |
+
|
16 |
+
# NEEDED FOR INPUT TEXT AREA INSTEAD OF INPUT TEXT
|
17 |
+
def use_change(el: reacton.core.Element, on_value: Callable[[Any], Any], enabled=True):
|
18 |
+
"""Trigger a callback when a blur events occurs or the enter key is pressed."""
|
19 |
+
on_value_ref = solara.use_ref(on_value)
|
20 |
+
on_value_ref.current = on_value
|
21 |
+
def add_events():
|
22 |
+
def on_change(widget, event, data):
|
23 |
+
if enabled:
|
24 |
+
on_value_ref.current(widget.v_model)
|
25 |
+
widget = cast(ipyvue.VueWidget, solara.get_widget(el))
|
26 |
+
if enabled:
|
27 |
+
widget.on_event("blur", on_change)
|
28 |
+
widget.on_event("keyup.enter", on_change)
|
29 |
+
def cleanup():
|
30 |
+
if enabled:
|
31 |
+
widget.on_event("blur", on_change, remove=True)
|
32 |
+
widget.on_event("keyup.enter", on_change, remove=True)
|
33 |
+
return cleanup
|
34 |
+
solara.use_effect(add_events, [enabled])
|
35 |
+
|
36 |
+
|
37 |
+
@solara.component
|
38 |
+
def InputTextarea(
|
39 |
+
label: str,
|
40 |
+
value: Union[str, solara.Reactive[str]] = "",
|
41 |
+
on_value: Callable[[str], None] = None,
|
42 |
+
disabled: bool = False,
|
43 |
+
password: bool = False,
|
44 |
+
continuous_update: bool = False,
|
45 |
+
error: Union[bool, str] = False,
|
46 |
+
message: Optional[str] = None,
|
47 |
+
):
|
48 |
+
reactive_value = solara.use_reactive(value, on_value)
|
49 |
+
del value, on_value
|
50 |
+
def set_value_cast(value):
|
51 |
+
reactive_value.value = str(value)
|
52 |
+
def on_v_model(value):
|
53 |
+
if continuous_update:
|
54 |
+
set_value_cast(value)
|
55 |
+
messages = []
|
56 |
+
if error and isinstance(error, str):
|
57 |
+
messages.append(error)
|
58 |
+
elif message:
|
59 |
+
messages.append(message)
|
60 |
+
text_area = v.Textarea(
|
61 |
+
v_model=reactive_value.value,
|
62 |
+
on_v_model=on_v_model,
|
63 |
+
label=label,
|
64 |
+
disabled=disabled,
|
65 |
+
type="password" if password else None,
|
66 |
+
error=bool(error),
|
67 |
+
messages=messages,
|
68 |
+
solo=True,
|
69 |
+
hide_details=True,
|
70 |
+
outlined=True,
|
71 |
+
rows=1,
|
72 |
+
auto_grow=True,
|
73 |
+
)
|
74 |
+
use_change(text_area, set_value_cast, enabled=not continuous_update)
|
75 |
+
return text_area
|
76 |
+
|
77 |
+
# EXTRACTION
|
78 |
+
openai.api_key = os.environ['OPENAI_API_KEY']
|
79 |
+
client = instructor.from_openai(OpenAI())
|
80 |
+
|
81 |
+
class Node(BaseModel):
|
82 |
+
id: int
|
83 |
+
label: str
|
84 |
+
color: str
|
85 |
+
|
86 |
+
class Edge(BaseModel):
|
87 |
+
source: int
|
88 |
+
target: int
|
89 |
+
label: str
|
90 |
+
color: str = "black"
|
91 |
+
|
92 |
+
class KnowledgeGraph(BaseModel):
|
93 |
+
nodes: List[Node] = Field(description="Nodes in the knowledge graph")
|
94 |
+
edges: List[Edge] = Field(description="Edges in the knowledge graph")
|
95 |
+
|
96 |
+
class MessageDict(TypedDict):
|
97 |
+
role: str
|
98 |
+
content: str
|
99 |
+
|
100 |
+
def add_chunk_to_ai_message(chunk: str):
|
101 |
+
messages.value = [
|
102 |
+
*messages.value[:-1],
|
103 |
+
{
|
104 |
+
"role": "assistant",
|
105 |
+
"content": chunk,
|
106 |
+
},
|
107 |
+
]
|
108 |
+
|
109 |
+
import ast
|
110 |
+
|
111 |
+
# DISPLAYED OUTPUT
|
112 |
+
@solara.component
|
113 |
+
def ChatInterface():
|
114 |
+
with solara.lab.ChatBox():
|
115 |
+
if len(messages.value)>0:
|
116 |
+
if messages.value[-1]["role"] != "user":
|
117 |
+
obj = messages.value[-1]["content"]
|
118 |
+
if f"{obj}" != "":
|
119 |
+
obj = ast.literal_eval(f"{obj}")
|
120 |
+
dot = Digraph(comment="Knowledge Graph")
|
121 |
+
if obj['nodes'] not in [None, []]:
|
122 |
+
if obj['nodes'][0]['label'] not in [None, '']:
|
123 |
+
for i, node in enumerate(obj['nodes']):
|
124 |
+
if obj['nodes'][i]['label'] not in [None, '']:
|
125 |
+
dot.node(name=str(obj['nodes'][i]['id']), label=obj['nodes'][i]['label'], color=obj['nodes'][i]['color'])
|
126 |
+
if obj['edges'] not in [None, []]:
|
127 |
+
if obj['edges'][0]['label'] not in [None, '']:
|
128 |
+
for i, edge in enumerate(obj['edges']):
|
129 |
+
if obj['edges'][i]['source'] not in [None,''] and obj['edges'][i]['target'] not in [None,''] and obj['edges'][i]['label'] not in [None,'']:
|
130 |
+
dot.edge(str(obj['edges'][i]['source']), str(obj['edges'][i]['target']), label=obj['edges'][i]['label'], color=obj['edges'][i]['color'])
|
131 |
+
solara.display(dot)
|
132 |
+
|
133 |
+
messages: solara.Reactive[List[MessageDict]] = solara.reactive([])
|
134 |
+
aux = solara.reactive("")
|
135 |
+
text_block = solara.reactive("Alice loves Bob while Charles hates both Alice and Bob.")
|
136 |
+
@solara.component
|
137 |
+
def Page():
|
138 |
+
title = "Knowledge Graph Generator"
|
139 |
+
with solara.Head():
|
140 |
+
solara.Title(f"{title}")
|
141 |
+
with solara.Column(style={"width": "70%", "padding": "50px"}):
|
142 |
+
solara.Markdown(f"#{title}")
|
143 |
+
solara.Markdown("Enter some text and the language model will try to describe it as a knowledge graph. Done with :heart: by [alonsosilva](https://twitter.com/alonsosilva)")
|
144 |
+
extraction_stream = client.chat.completions.create_partial(
|
145 |
+
model="gpt-3.5-turbo",
|
146 |
+
response_model=KnowledgeGraph,
|
147 |
+
messages=[
|
148 |
+
{
|
149 |
+
"role": "user",
|
150 |
+
"content": f"Help me understand the following by describing it as small knowledge graph: {text_block}",
|
151 |
+
},
|
152 |
+
],
|
153 |
+
temperature=0,
|
154 |
+
stream=True,
|
155 |
+
)
|
156 |
+
|
157 |
+
user_message_count = len([m for m in messages.value if m["role"] == "user"])
|
158 |
+
def send():
|
159 |
+
messages.value = [*messages.value, {"role": "user", "content": "Hello"}]
|
160 |
+
def response(message):
|
161 |
+
for extraction in extraction_stream:
|
162 |
+
obj = extraction.model_dump()
|
163 |
+
if f"{obj}" != aux.value:
|
164 |
+
add_chunk_to_ai_message(f"{obj}")
|
165 |
+
aux.value = f"{obj}"
|
166 |
+
def result():
|
167 |
+
if messages.value != []:
|
168 |
+
response(messages.value[-1]["content"])
|
169 |
+
result = solara.lab.use_task(result, dependencies=[user_message_count])
|
170 |
+
InputTextarea("Enter text:", value=text_block, continuous_update=True)
|
171 |
+
solara.Button(label="Generate Knowledge Graph", on_click=send)
|
172 |
+
ChatInterface()
|
173 |
+
Page()
|
packages.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
graphviz
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
solara==1.31.0
|
2 |
+
openai==1.17.0
|
3 |
+
instructor==1.1.0
|
4 |
+
graphviz==0.20.3
|