david-oplatka commited on
Commit
8dd11f1
1 Parent(s): 4222bab

Add App Files

Browse files
Files changed (4) hide show
  1. Dockerfile +30 -0
  2. agent.py +235 -0
  3. app.py +713 -0
  4. requirements.txt +10 -0
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG VECTARA_CUSTOMER_ID
2
+ ARG VECTARA_API_KEY
3
+ ARG VECTARA_CORPUS_ID
4
+ ARG VECTARA_AGENT_TYPE
5
+ ARG VECTARA_AGENT_MAIN_LLM_PROVIDER
6
+ ARG VECTARA_AGENT_TOOL_LLM_PROVIDE
7
+ ARG OPENAI_API_KEY
8
+ ARG QUERY_EXAMPLES
9
+ ARG DEMO_NAME
10
+ ARG SHORT_DESCRIPTION
11
+ ARG EXTRA_INFO
12
+
13
+ FROM python:3.10
14
+
15
+ ENV PYTHONDONTWRITEBYTECODE 1
16
+ ENV PYTHONUNBUFFERED 1
17
+
18
+ RUN useradd -m -u 1000 user
19
+ USER user
20
+ ENV PATH="/home/user/.local/bin:$PATH"
21
+
22
+ WORKDIR /app
23
+
24
+ COPY --chown=user ./requirements.txt requirements.txt
25
+ RUN pip3 install --no-cache-dir -r requirements.txt
26
+
27
+ EXPOSE 8000
28
+
29
+ COPY --chown=user . /app
30
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
agent.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import requests
4
+ import json
5
+ from typing import Tuple, List
6
+
7
+ from omegaconf import OmegaConf
8
+
9
+ from typing import Optional
10
+ from pydantic import Field, BaseModel
11
+
12
+ from vectara_agentic.agent import Agent
13
+ from vectara_agentic.tools import ToolsFactory, VectaraToolFactory
14
+ from vectara_agentic.tools_catalog import summarize_text
15
+
16
+ from dotenv import load_dotenv
17
+ load_dotenv(override=True)
18
+
19
+ citation_description = '''
20
+ The citation for a particular case.
21
+ Citation must include the volume number, reporter, and first page. For example: 253 P.2d 136.
22
+ '''
23
+
24
+ def extract_components_from_citation(citation: str) -> Tuple[int, str, int]:
25
+ citation_components = citation.split(' ')
26
+ volume_num = citation_components[0]
27
+ reporter = '-'.join(citation_components[1:-1]).replace('.', '').lower()
28
+ first_page = citation_components[-1]
29
+
30
+ if not volume_num.isdigit():
31
+ raise ValueError("volume number must be a number.")
32
+ if not first_page.isdigit():
33
+ raise ValueError("first page number must be a number.")
34
+
35
+ return int(volume_num), reporter, int(first_page)
36
+
37
+ def create_assistant_tools(cfg):
38
+
39
+ def get_opinion_text(
40
+ case_citation = Field(description = citation_description),
41
+ summarize: Optional[bool] = False
42
+ ) -> str:
43
+ """
44
+ Given case citation, returns the full opinion/ruling text of the case.
45
+ if summarize is True, the text is summarized.
46
+ If there is more than one opinion for the case, the type of each opinion is returned with the text,
47
+ and the opinions (or their summaries) are separated by semicolons (;)
48
+ """
49
+ volume_num, reporter, first_page = extract_components_from_citation(case_citation)
50
+ response = requests.get(f"https://static.case.law/{reporter}/{volume_num}/cases/{first_page:04d}-01.json")
51
+ if response.status_code != 200:
52
+ return "Case not found; please check the citation."
53
+ res = json.loads(response.text)
54
+
55
+ if len(res["casebody"]["opinions"]) == 1:
56
+ text = res["casebody"]["opinions"][0]["text"]
57
+ output = text if not summarize else summarize_text(text, "law")
58
+ else:
59
+ output = ""
60
+ for opinion in res["casebody"]["opinions"]:
61
+ text = opinion["text"] if not summarize else summarize_text(opinion["text"], "law")
62
+ output += f"Opinion type: {opinion['type']}, text: {text};"
63
+
64
+ return output
65
+
66
+ def get_case_document_pdf(
67
+ case_citation = Field(description = citation_description)
68
+ ) -> str:
69
+ """
70
+ Given a case citation, returns a valid web url to a pdf of the case record
71
+ """
72
+ volume_num, reporter, first_page = extract_components_from_citation(case_citation)
73
+ response = requests.get(f"https://static.case.law/{reporter}/{volume_num}/cases/{first_page:04d}-01.json")
74
+ if response.status_code != 200:
75
+ return "Case not found; please check the citation."
76
+ res = json.loads(response.text)
77
+ page_number = res["first_page_order"]
78
+ return f"https://static.case.law/{reporter}/{volume_num}.pdf#page={page_number}"
79
+
80
+ def get_case_document_page(
81
+ case_citation = Field(description = citation_description)
82
+ ) -> str:
83
+ """
84
+ Given a case citation, returns a valid web url to a page with information about the case.
85
+ """
86
+ volume_num, reporter, first_page = extract_components_from_citation(case_citation)
87
+ url = f"https://case.law/caselaw/?reporter={reporter}&volume={volume_num}&case={first_page:04d}-01"
88
+ response = requests.get(url)
89
+ if response.status_code != 200:
90
+ return "Case not found; please check the citation."
91
+ return url
92
+
93
+ def get_case_name(
94
+ case_citation = Field(description = citation_description)
95
+ ) -> Tuple[str, str]:
96
+ """
97
+ Given a case citation, returns its name and name abbreviation.
98
+ """
99
+ volume_num, reporter, first_page = extract_components_from_citation(case_citation)
100
+ response = requests.get(f"https://static.case.law/{reporter}/{volume_num}/cases/{first_page:04d}-01.json")
101
+ if response.status_code != 200:
102
+ return "Case not found", "Case not found"
103
+ res = json.loads(response.text)
104
+ return res["name"], res["name_abbreviation"]
105
+
106
+ def get_cited_cases(
107
+ case_citation = Field(description = citation_description)
108
+ ) -> List[dict]:
109
+ """
110
+ Given a case citation, returns a list of cases that are cited by the opinion of this case.
111
+ The output is a list of cases, each a dict with the citation, name and name_abbreviation of the case.
112
+ """
113
+ volume_num, reporter, first_page = extract_components_from_citation(case_citation)
114
+ response = requests.get(f"https://static.case.law/{reporter}/{volume_num}/cases/{first_page:04d}-01.json")
115
+ if response.status_code != 200:
116
+ return "Case not found; please check the citation."
117
+ res = json.loads(response.text)
118
+ citations = res["cites_to"]
119
+ res = []
120
+ for citation in citations[:10]:
121
+ name, name_abbreviation = get_case_name(citation["cite"])
122
+ res.append({
123
+ "citation": citation["cite"],
124
+ "name": name,
125
+ "name_abbreviation": name_abbreviation
126
+ })
127
+ return res
128
+
129
+ def validate_url(
130
+ url = Field(description = "A web url pointing to case-law document")
131
+ ) -> str:
132
+ """
133
+ Given a link, returns whether or not the link is valid.
134
+ If it is not valid, it should not be used in any output.
135
+ """
136
+ pdf_pattern = re.compile(r'^https://static.case.law/.*')
137
+ document_pattern = re.compile(r'^https://case.law/caselaw/?reporter=.*')
138
+ return "URL is valid" if bool(pdf_pattern.match(url)) | bool(document_pattern.match(url)) else "URL is bad"
139
+
140
+ class QueryCaselawArgs(BaseModel):
141
+ query: str = Field(..., description="The user query.")
142
+ citations: Optional[str] = Field(default = None,
143
+ description = "The citation of the case. Optional.",
144
+ examples = ['253 P.2d 136', '10 Alaska 11', '6 C.M.A. 3'])
145
+
146
+ vec_factory = VectaraToolFactory(vectara_api_key=cfg.api_key,
147
+ vectara_customer_id=cfg.customer_id,
148
+ vectara_corpus_id=cfg.corpus_id)
149
+ tools_factory = ToolsFactory()
150
+
151
+ ask_caselaw = vec_factory.create_rag_tool(
152
+ tool_name = "ask_caselaw",
153
+ tool_description = """
154
+ Returns a response (str) to the user query base on case law in the state of Alaska.
155
+ If 'citations' is provided, filters the response based on information from that case.
156
+ The response includes metadata about the case such as title/name the ruling, the court,
157
+ the decision date, the judges, and the case citation.
158
+ You can use case citations from the metadata as input to other tools.
159
+ Use this tool for general case law queries.
160
+ """,
161
+ tool_args_schema = QueryCaselawArgs,
162
+ reranker = "multilingual_reranker_v1", rerank_k = 100,
163
+ n_sentences_before = 2, n_sentences_after = 2, lambda_val = 0.0,
164
+ summary_num_results = 10,
165
+ vectara_summarizer = 'vectara-summary-ext-24-05-med-omni',
166
+ include_citations = False,
167
+ )
168
+
169
+ return (
170
+ [tools_factory.create_tool(tool) for tool in [
171
+ get_opinion_text,
172
+ get_case_document_pdf,
173
+ get_case_document_page,
174
+ get_cited_cases,
175
+ get_case_name,
176
+ validate_url
177
+ ]] +
178
+ tools_factory.standard_tools() +
179
+ tools_factory.legal_tools() +
180
+ tools_factory.guardrail_tools() +
181
+ [ask_caselaw]
182
+ )
183
+
184
+ def get_agent_config() -> OmegaConf:
185
+ cfg = OmegaConf.create({
186
+ 'customer_id': str(os.environ['VECTARA_CUSTOMER_ID']),
187
+ 'corpus_id': str(os.environ['VECTARA_CORPUS_ID']),
188
+ 'api_key': str(os.environ['VECTARA_API_KEY']),
189
+ 'examples': os.environ.get('QUERY_EXAMPLES', None),
190
+ 'demo_name': str(os.environ['DEMO_NAME']),
191
+ 'short_description': str(os.environ['SHORT_DESCRIPTION']),
192
+ 'extra_info': str(os.environ['EXTRA_INFO'])
193
+ })
194
+ return cfg
195
+
196
+ def initialize_agent(_cfg, update_func=None):
197
+
198
+ legal_assistant_instructions = """
199
+ - You are a helpful legal assistant, with expertise in case law for the state of Alaska.
200
+ - The ask_caselaw tool is your primary tools for finding information about cases.
201
+ Do not use your own knowledge to answer questions.
202
+ - For a query with multiple sub-questions, break down the query into the sub-questions,
203
+ and make separate calls to the ask_caselaw tool to answer each sub-question,
204
+ then combine the answers to provide a complete response.
205
+ - If the ask_caselaw tool responds that it does not have enough information to answer the query,
206
+ try to rephrase the query and call the tool again.
207
+ - When presenting the output from ask_caselaw tool,
208
+ Extract metadata from the tool's response, and respond in this format:
209
+ 'On <decision date>, the <court> ruled in <case name> that <judges ruling>. This opinion was authored by <judges>'.
210
+ - Citations include 3 components: volume number, reporter, and first page.
211
+ Here are some examples: '253 P.2d 136', '10 Alaska 11', '6 C.M.A. 3'
212
+ Never use your internal knowledge to contruct or guess what the citation is.
213
+ - If two cases have conflicting rulings, assume that the case with the more current ruling date is correct.
214
+ - If the response is based on cases that are older than 5 years, make sure to inform the user that the information may be outdated,
215
+ since some case opinions may no longer apply in law.
216
+ - To summarize the case, use the get_opinion_text with summarize set to True.
217
+ - If a user wants to learn more about a case, you can call the get_case_document_pdf tool with the citation to get a valid URL.
218
+ If this is unsuccessful, call the get_case_document_page tool instead.
219
+ The text displayed with this URL should be the name_abbreviation of the case (DON'T just say the info can be found here).
220
+ Don't call the get_case_document_page tool until after you have tried the get_case_document_pdf tool.
221
+ Don't provide URLs from any other tools. Do not generate URLs yourself.
222
+ - When presenting a URL in your response, use the validate_url tool.
223
+ - If a user wants to test their argument, use the ask_caselaw tool to gather information about cases related to their argument
224
+ and the critique_as_judge tool to determine whether their argument is sound or has issues that must be corrected.
225
+ - Never discuss politics, and always respond politely.
226
+ """
227
+
228
+ agent = Agent(
229
+ tools=create_assistant_tools(_cfg),
230
+ topic="Case law in Alaska",
231
+ custom_instructions=legal_assistant_instructions,
232
+ update_func=update_func
233
+ )
234
+
235
+ return agent
app.py ADDED
@@ -0,0 +1,713 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from markdown_it import MarkdownIt
3
+ from mdit_py_plugins import front_matter
4
+
5
+ from reactpy import component, html, hooks, use_callback, run, svg
6
+ from reactpy.backend.starlette import configure
7
+ from starlette.applications import Starlette
8
+ from vectara_agentic.agent import AgentStatusType
9
+ from agent import initialize_agent, get_agent_config
10
+
11
+ wait_message = "Please wait. Assistant at work..."
12
+
13
+ @component
14
+ def App():
15
+
16
+ # Used for showing/hiding logs
17
+ show_logs, set_show_logs = hooks.use_state(False)
18
+
19
+ # Determining when the "Show Logs" button should appear
20
+ show_logs_button, set_show_logs_button = hooks.use_state(False)
21
+
22
+ # Used for resetting chat bot with "Start Over" button.
23
+ first_turn, set_first_turn = hooks.use_state(True)
24
+
25
+ # Tracks the state of whether the header is collapsed or exapnded
26
+ more_info, set_more_info = hooks.use_state(False)
27
+
28
+ # Tracks the state of whether the footer is collapsed or expanded
29
+ collapsed, set_collapse = hooks.use_state(False)
30
+
31
+ # Record of Chat Messages
32
+ messages, set_messages = hooks.use_state([])
33
+
34
+ message, set_message = hooks.use_state("")
35
+
36
+ def use_agent_logger():
37
+ agent_log_entries, set_agent_log_entries = hooks.use_state([])
38
+
39
+ def reset_log_entries():
40
+ set_agent_log_entries([])
41
+
42
+ def add_log_entry(new_log_entry):
43
+ set_agent_log_entries(lambda previous_entries: previous_entries + [new_log_entry])
44
+
45
+ return agent_log_entries, add_log_entry, reset_log_entries
46
+
47
+ log_entries, add_log_entry, reset_log_entries = use_agent_logger()
48
+
49
+ def update_func(status_type: AgentStatusType, msg: str):
50
+ if status_type != AgentStatusType.AGENT_UPDATE:
51
+ output = f"{status_type.value} - {msg}"
52
+ add_log_entry(output)
53
+
54
+ cfg, _ = hooks.use_state(get_agent_config())
55
+ agent, _ = hooks.use_state(initialize_agent(cfg, update_func))
56
+
57
+ def toggle_header(event=None):
58
+ set_more_info(not more_info)
59
+
60
+ def toggle_footer(event=None):
61
+ set_collapse(not collapsed)
62
+
63
+ # Clears all messages and resets chat interface
64
+ def start_over(event=None):
65
+ set_messages([])
66
+ set_first_turn(True)
67
+
68
+ def display_message(new_messages):
69
+ if first_turn:
70
+ set_first_turn(False)
71
+
72
+ set_messages(messages + list(new_messages))
73
+
74
+ async def chat_response(user_message):
75
+ response = await asyncio.to_thread(agent.chat, user_message)
76
+ return response
77
+
78
+ async def send_message_async(sent_message):
79
+ response = await chat_response(sent_message)
80
+ set_messages(messages[:-1])
81
+ display_message(
82
+ [
83
+ {"user": "human", "message": sent_message},
84
+ {"user": "bot", "message": response},
85
+ ]
86
+ )
87
+ set_show_logs_button(True)
88
+
89
+ def send_message(event=None):
90
+ if message.strip():
91
+ sent_message = message
92
+ set_message("")
93
+ set_show_logs_button(False)
94
+ set_show_logs(False)
95
+ reset_log_entries()
96
+ display_message([{"user": "human", "message": sent_message}])
97
+ display_message([{"user": "bot", "message": wait_message}])
98
+
99
+ asyncio.create_task(send_message_async(sent_message))
100
+
101
+ def send_example(ex_prompt):
102
+ if ex_prompt.strip():
103
+ sent_message = ex_prompt
104
+ set_message("")
105
+ reset_log_entries()
106
+ display_message([{"user": "human", "message": sent_message}])
107
+ display_message([{"user": "bot", "message": wait_message}])
108
+
109
+ asyncio.create_task(send_message_async(sent_message))
110
+
111
+
112
+ handle_key_down = use_callback(lambda event:
113
+ send_message() if event['key'] == 'Enter' else None,
114
+ [send_message])
115
+
116
+ handle_change = use_callback(lambda event:
117
+ set_message(event['target']['value']),
118
+ [])
119
+
120
+ @component
121
+ def Header(demo_name: str, short_description: str, extra_info: str):
122
+ return html.header(
123
+ {
124
+ "style": {
125
+ "backgroundColor": "#FFFFFF",
126
+ "display": "flex",
127
+ "justifyContent": "space-between",
128
+ "alignItems": "center",
129
+ }
130
+ },
131
+ html.div(
132
+ {
133
+ "style": {
134
+ "display": "flex",
135
+ "alignItems": "center",
136
+ "flex": 2,
137
+ "textAlign": "left",
138
+ "padding": "10px",
139
+ }
140
+ },
141
+ html.img(
142
+ {
143
+ "src": "https://avatars.githubusercontent.com/u/108304503?s=200&v=4",
144
+ "style": {
145
+ "height": "30px",
146
+ "marginRight": "15px",
147
+ "marginLeft": "5px",
148
+ "transform": "translateY(-2px)",
149
+ }
150
+ }
151
+ ),
152
+ html.p(
153
+ {
154
+ "style": {
155
+ "fontSize": "25px",
156
+ "fontFamily": "Georgia, 'Times New Roman', Times, serif",
157
+ }
158
+ },
159
+ f"{demo_name}"
160
+ ),
161
+ ),
162
+ html.div(
163
+ {
164
+ "style": {
165
+ "flex": 5,
166
+ "textAlign": "center",
167
+ "padding": "10px 0px 15px 0px",
168
+ "fontFamily": "Lato",
169
+ "position": "relative",
170
+ }
171
+ },
172
+ html.h3(f"Welcome to the {demo_name} Demo"),
173
+ html.p(
174
+ short_description,
175
+ html.button(
176
+ {
177
+ "style": {
178
+ "display": "inline" if not more_info else "none",
179
+ "backgroundColor": "#FFFFFF",
180
+ "color": "#757575",
181
+ "fontSize": "13px",
182
+ "cursor": "pointer",
183
+ "border": "none",
184
+ "padding": "0px 0px 0px 5px",
185
+ },
186
+ "type": "button",
187
+ "onClick": toggle_header,
188
+ },
189
+ html.u(
190
+ {
191
+ "style": {
192
+ "flex": 2,
193
+ "textAlign": "right",
194
+ "padding": "10px",
195
+ }
196
+ },
197
+ "Learn More"
198
+ )
199
+ ),
200
+ f" {extra_info}" if more_info else ""
201
+ ),
202
+ html.button(
203
+ {
204
+ "style": {
205
+ "display": "block" if more_info else "none",
206
+ "background": "none",
207
+ "border": "none",
208
+ "color": "#757575",
209
+ "cursor": "pointer",
210
+ "position": "absolute",
211
+ "left": "50%",
212
+ "transform": "translateX(-50%)",
213
+ "bottom": "1px",
214
+ },
215
+ "type": "button",
216
+ "on_click": toggle_header,
217
+ },
218
+ svg.svg(
219
+ {
220
+ "width": "20",
221
+ "height": "20",
222
+ "viewBox": "0 0 20 20",
223
+ "fill": "none",
224
+ "stroke": "black",
225
+ "strokeWidth": "2",
226
+ "strokeLinecap": "round",
227
+ "strokeLinejoin": "round",
228
+ },
229
+ svg.path(
230
+ {
231
+ "d": "M12 19V5M5 12l7-7 7 7",
232
+ "stroke": "currentColor",
233
+ }
234
+ )
235
+ )
236
+ )
237
+ ),
238
+ html.div(
239
+ {
240
+ "style": {
241
+ "flex": 2,
242
+ "textAlign": "right",
243
+ "padding": "10px",
244
+ }
245
+ },
246
+ html.button(
247
+ {
248
+ "style": {
249
+ "backgroundColor": "#FFFFFF",
250
+ "color": "#757575",
251
+ "fontSize": "14px",
252
+ "cursor": "pointer",
253
+ "border": "1px solid #e2dfdf",
254
+ "borderRadius": "5px",
255
+ "padding": "6px 20px",
256
+ "marginRight": "15px",
257
+ },
258
+ "type": "button",
259
+ "onClick": start_over,
260
+ },
261
+ "Start Over?"
262
+ )
263
+ )
264
+ )
265
+
266
+ def markdown_to_html(markdown_text):
267
+ md = (
268
+ MarkdownIt("commonmark", {"breaks": True, "html": True})
269
+ .use(front_matter.front_matter_plugin)
270
+ )
271
+ return md.render(markdown_text)
272
+
273
+ @component
274
+ def MarkdownRenderer(content):
275
+ html_content = markdown_to_html(content)
276
+ return html.div(
277
+ {
278
+ "style": {
279
+ "fontFamily": "Arial",
280
+ "color": "#49454F",
281
+ "fontSize": "14px",
282
+ },
283
+ "dangerouslySetInnerHTML": {"__html": html_content}
284
+ }
285
+ )
286
+
287
+ @component
288
+ def ExamplePrompts():
289
+ example_questions = [example.strip() for example in cfg['examples'].split(";")] if cfg.examples else []
290
+
291
+ def create_prompt_button(question):
292
+ return html.button(
293
+ {
294
+ "style": {
295
+ "backgroundColor": "#FFFFFF",
296
+ "borderWidth": "1px",
297
+ "borderColor": "#65558F",
298
+ "borderRadius": "20px",
299
+ "padding": "5px 7px",
300
+ "margin": "5px",
301
+ "cursor": "pointer",
302
+ "color": "#21005D",
303
+ "fontSize": "13px",
304
+ },
305
+ "type": "button",
306
+ "onClick": lambda _: send_example(question),
307
+ },
308
+ question
309
+ )
310
+
311
+ if first_turn:
312
+ return html.div(
313
+ {
314
+ "style": {
315
+ "display": "flex",
316
+ "transform": "translate(4%, 100%)",
317
+ "flexDirection": "column",
318
+ "justifyContent": "center",
319
+ "width": "90%",
320
+ }
321
+ },
322
+ html.p(
323
+ {
324
+ "style": {
325
+ "fontSize": "16px",
326
+ "fontFamily": "Arial",
327
+ "color": "#49454F",
328
+ "marginBottom": "5px",
329
+ "transform": "translateX(3%)",
330
+ "textAlign": "left",
331
+ }
332
+ },
333
+ "Queries to try:"
334
+ ),
335
+ html.div(
336
+ {
337
+ "style": {
338
+ "display": "flex",
339
+ "flexWrap": "wrap",
340
+ "justifyContent": "center",
341
+ }
342
+ },
343
+ *[create_prompt_button(q) for q in example_questions]
344
+ )
345
+ )
346
+
347
+ return None
348
+
349
+ @component
350
+ def ChatBox():
351
+ return html.div(
352
+ {
353
+ "style": {
354
+ "position": "fixed",
355
+ "bottom": "70px" if collapsed else "140px",
356
+ "left": "50%",
357
+ "transform": "translateX(-50%)",
358
+ "width": "80%",
359
+ "display": "flex",
360
+ "alignItems": "center",
361
+ "backgroundColor": "#FFFFFF",
362
+ "borderRadius": "50px",
363
+ "zIndex": "1000",
364
+ }
365
+ },
366
+ html.input(
367
+ {
368
+ "type": "text",
369
+ "value": message,
370
+ "placeholder": "Your Message",
371
+ "onChange": handle_change,
372
+ "onKeyDown": handle_key_down,
373
+ "style": {
374
+ "width": "100%",
375
+ "padding": "10px 50px 10px 20px",
376
+ "border": "none",
377
+ "borderRadius": "50px",
378
+ "color": "#65558F",
379
+ }
380
+ }
381
+ ),
382
+ html.button(
383
+ {
384
+ "type": "button",
385
+ "onClick": send_message,
386
+ "style": {
387
+ "position": "absolute",
388
+ "right": "8px",
389
+ "top": "50%",
390
+ "transform":"translateY(-50%)",
391
+ "background": "none",
392
+ "border": "none",
393
+ "cursor": "pointer",
394
+ "padding": "8px",
395
+ "display": "flex",
396
+ "alignItems": "center",
397
+ "justifyContent": "center",
398
+ }
399
+ },
400
+ svg.svg(
401
+ {
402
+ "xmlns": "http://www.w3.org/2000/svg",
403
+ "width": "20",
404
+ "height": "20",
405
+ "fill": "none",
406
+ "stroke": "currentColor",
407
+ "stroke-linecap": "round",
408
+ "stroke-linejoin": "round",
409
+ "stroke-width": "2",
410
+ "viewBox": "0 0 24 24",
411
+ "class": "feather feather-send",
412
+ },
413
+ svg.path({"d": "M22 2 11 13"}),
414
+ svg.path({"d": "M22 2 15 22 11 13 2 9z"})
415
+ )
416
+ )
417
+ )
418
+
419
+
420
+ @component
421
+ def ChatMessage(user, message):
422
+
423
+ return html.div(
424
+ {
425
+ "style": {
426
+ "display": "flex",
427
+ "width": "75%",
428
+ "transform": "translateX(20%)",
429
+ "justifyContent": "flex-end" if user == "human" else "flex-start",
430
+ "margin": "20px 0px 10px 0px"
431
+ }
432
+ },
433
+ html.div(
434
+ {
435
+ "style": {
436
+ "maxWidth": "50%",
437
+ "padding": "0px 15px 0px 10px",
438
+ "borderRadius": "15px",
439
+ "backgroundColor": "#E8DEF8" if user == "human" else "#ECE6F0",
440
+ "color": "#000000",
441
+ }
442
+ },
443
+ MarkdownRenderer(message)
444
+ )
445
+ )
446
+
447
+ @component
448
+ def Logs():
449
+ if (len(messages) > 0) and (len(log_entries) > 0) and (messages[-1]["message"] != wait_message) and (show_logs_button):
450
+ if not show_logs:
451
+ return html.div(
452
+ {
453
+ "style": {
454
+ "display": "flex",
455
+ "transform": "translateX(75%)",
456
+ "width": "20%",
457
+ "margin": "0px",
458
+ }
459
+ },
460
+ html.button(
461
+ {
462
+ "style": {
463
+ "position": "flex",
464
+ "background": "none",
465
+ "color": "#757575",
466
+ "border": "none",
467
+ "cursor": "pointer",
468
+ "fontFamily": "Arial",
469
+ "fontSize": "14px",
470
+ },
471
+ "type": "button",
472
+ "onClick": lambda _: set_show_logs(True)
473
+ },
474
+ "Show Logs"
475
+ )
476
+ )
477
+ else:
478
+ return html.div(
479
+ {
480
+ "style": {
481
+ "display": "flex",
482
+ "transform": "translateX(20%)",
483
+ "border": "2px solid #e2dfdf",
484
+ "borderRadius": "10px",
485
+ "width": "75%",
486
+ }
487
+ },
488
+ html.div(
489
+ [
490
+ html.p(
491
+ {
492
+ "style": {
493
+ "fontSize": "14px",
494
+ "marginLeft": "10px",
495
+ }
496
+ },
497
+ entry
498
+ ) for entry in log_entries
499
+ ],
500
+ html.button(
501
+ {
502
+ "style": {
503
+ "position": "flex",
504
+ "background": "none",
505
+ "color": "#757575",
506
+ "border": "none",
507
+ "cursor": "pointer",
508
+ "fontFamily": "Arial",
509
+ "fontSize": "14px",
510
+ "marginBottom": "10px",
511
+ "marginLeft": "5px",
512
+ },
513
+ "type": "button",
514
+ "onClick": lambda _: set_show_logs(False)
515
+ },
516
+ "Hide Logs"
517
+ )
518
+ )
519
+ )
520
+
521
+ return None
522
+
523
+ @component
524
+ def ChatUI():
525
+
526
+ def render_chat_content():
527
+ if first_turn:
528
+ return ExamplePrompts()
529
+ else:
530
+ return [ChatMessage(msg["user"], msg["message"]) for msg in messages]
531
+
532
+ return html.div(
533
+ {
534
+ "style": {
535
+ "display": "flex",
536
+ "flexDirection": "column",
537
+ "padding": "10px",
538
+ "backgroundColor": "#F4F1F4",
539
+ "overflowY": "auto",
540
+ "height": f"calc(100vh - {265 if collapsed else 335}px)",
541
+ "marginBottom": "15px",
542
+ "paddingBottom": "15px",
543
+ },
544
+ },
545
+ render_chat_content(),
546
+ Logs()
547
+ )
548
+
549
+
550
+ @component
551
+ def Footer():
552
+
553
+ if collapsed:
554
+ return html.footer(
555
+ {
556
+ "style": {
557
+ "backgroundColor": "#FFFFFF",
558
+ "position": "fixed",
559
+ "bottom": 0,
560
+ "width": "100%",
561
+ "height": "50px",
562
+ }
563
+ },
564
+ html.button(
565
+ {
566
+ "style": {
567
+ "background": "none",
568
+ "border": "none",
569
+ "color": "#757575",
570
+ "cursor": "pointer",
571
+ "position": "absolute",
572
+ "bottom": "5px",
573
+ "right": "10px",
574
+ },
575
+ "type": "button",
576
+ "on_click": toggle_footer,
577
+ },
578
+ svg.svg(
579
+ {
580
+ "xmlns": "http://www.w3.org/2000/svg",
581
+ "width": "24",
582
+ "height": "24",
583
+ "fill": "none",
584
+ "stroke": "currentColor",
585
+ "stroke-linecap": "round",
586
+ "stroke-linejoin": "round",
587
+ "stroke-width": "3",
588
+ "viewBox": "0 0 24 24",
589
+ },
590
+ svg.path({"d": "M19 14l-7-7-7 7"})
591
+ )
592
+ )
593
+ )
594
+
595
+ else:
596
+ return html.footer(
597
+ {
598
+ "style": {
599
+ "backgroundColor": "#FFFFFF",
600
+ "position": "fixed",
601
+ "bottom": 0,
602
+ "width": "100%",
603
+ }
604
+ },
605
+ html.div(
606
+ {
607
+ "style": {
608
+ "backgroundColor": "#FFFFFF",
609
+ "padding": "0px 20px 0px 50px",
610
+ "position": "relative",
611
+ "display": "block",
612
+ }
613
+ },
614
+ html.button(
615
+ {
616
+ "style": {
617
+ "position": "absolute",
618
+ "right": "10px",
619
+ "background": "none",
620
+ "border": "none",
621
+ "color": "#757575",
622
+ "cursor": "pointer",
623
+ },
624
+ "type": "button",
625
+ "on_click": toggle_footer,
626
+ },
627
+ svg.svg(
628
+ {
629
+ "xmlns": "http://www.w3.org/2000/svg",
630
+ "width": "24",
631
+ "height": "24",
632
+ "fill": "none",
633
+ "stroke": "currentColor",
634
+ "stroke-linecap": "round",
635
+ "stroke-linejoin": "round",
636
+ "stroke-width": "3",
637
+ "viewBox": "0 0 24 24",
638
+ },
639
+ svg.path({"d": "M18 6L6 18"}),
640
+ svg.path({"d": "M6 6l12 12"})
641
+ )
642
+ ),
643
+ html.p(
644
+ {
645
+ "style": {
646
+ "fontSize": "20px",
647
+ "color": "#4b4851"
648
+ }
649
+ },
650
+ "How this works?",
651
+ ),
652
+ html.p(
653
+ {
654
+ "style": {
655
+ "color": "#757575",
656
+ }
657
+ },
658
+ "This app was built with ",
659
+ html.a(
660
+ {
661
+ "href": "https://vectara.com/",
662
+ "target": "_blank",
663
+ },
664
+ "Vectara",
665
+ ),
666
+ html.br(),
667
+ "It demonstrates the use of Agentic-RAG functionality with Vectara",
668
+ )
669
+ )
670
+ )
671
+
672
+ return html.div(
673
+ {
674
+ "style": {
675
+ "backgroundColor": "#F4F1F4",
676
+ "margin": "0",
677
+ "padding": "0",
678
+ "minHeight": "100vh",
679
+ "display": "flex",
680
+ "boxSizing": "border-box",
681
+ "flexDirection": "column",
682
+ "overflowX": "hidden",
683
+ "position": "relative",
684
+ }
685
+ },
686
+ html.head(
687
+ html.style("""
688
+ body {
689
+ margin: 0;
690
+ padding: 0;
691
+ }
692
+ """)
693
+ ),
694
+ Header(
695
+ demo_name=cfg['demo_name'],
696
+ short_description=cfg['short_description'],
697
+ extra_info=cfg['extra_info']
698
+ ),
699
+ ChatUI(),
700
+ ChatBox(),
701
+ Footer(),
702
+ )
703
+
704
+ app = Starlette()
705
+
706
+ configure(app, App)
707
+
708
+ if __name__ == "__main__":
709
+ import uvicorn
710
+ uvicorn.run(app, host="127.0.0.1", port=8000)
711
+
712
+ # if __name__ == "__main__":
713
+ # run(App)
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ reactpy[starlette]==1.0.2
2
+ uvicorn==0.30.6
3
+ omegaconf==2.3.0
4
+ python-dotenv==1.0.1
5
+ uuid==1.30
6
+ langdetect==1.0.9
7
+ langcodes==3.4.0
8
+ vectara-agentic==0.1.6
9
+ markdown-it-py==3.0.0
10
+ mdit-py-plugins==0.4.1