alonsosilva commited on
Commit
d774d12
·
1 Parent(s): 75425d3
Files changed (6) hide show
  1. Dockerfile +31 -0
  2. LICENSE +21 -0
  3. README.md +1 -0
  4. app.py +173 -0
  5. packages.txt +1 -0
  6. 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