khoj 1.42.2.dev1__py3-none-any.whl → 1.42.2.dev16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- khoj/configure.py +2 -0
- khoj/database/adapters/__init__.py +6 -6
- khoj/interface/compiled/404/index.html +2 -2
- khoj/interface/compiled/_next/static/chunks/{2327-f03b2a77f67b8f8c.js → 2327-aa22697ed9c8d54a.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/7127-79a3af5138960272.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{5138-81457f7f59956b56.js → 7211-7fedd2ee3655239c.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-4e2a134ec26aa606.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/page-ef89ac958e78aa81.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-ad4d1792ab1a4108.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/page-db0fbea54ccea62f.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-9a167dc9b5fcd464.js → page-da90c78180a86040.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/{webpack-1c900156837baf90.js → webpack-0f15e6b51732b337.js} +1 -1
- khoj/interface/compiled/_next/static/css/{c34713c98384ee87.css → 2945c4a857922f3b.css} +1 -1
- khoj/interface/compiled/_next/static/css/{9c223d337a984468.css → 7017ee76c2f2cd87.css} +1 -1
- khoj/interface/compiled/_next/static/css/9a460202d29476e5.css +1 -0
- khoj/interface/compiled/agents/index.html +2 -2
- khoj/interface/compiled/agents/index.txt +2 -2
- khoj/interface/compiled/automations/index.html +2 -2
- khoj/interface/compiled/automations/index.txt +3 -3
- khoj/interface/compiled/chat/index.html +2 -2
- khoj/interface/compiled/chat/index.txt +2 -2
- khoj/interface/compiled/index.html +2 -2
- khoj/interface/compiled/index.txt +2 -2
- khoj/interface/compiled/search/index.html +2 -2
- khoj/interface/compiled/search/index.txt +2 -2
- khoj/interface/compiled/settings/index.html +2 -2
- khoj/interface/compiled/settings/index.txt +4 -4
- khoj/interface/compiled/share/chat/index.html +2 -2
- khoj/interface/compiled/share/chat/index.txt +2 -2
- khoj/processor/conversation/anthropic/anthropic_chat.py +17 -132
- khoj/processor/conversation/anthropic/utils.py +1 -1
- khoj/processor/conversation/google/gemini_chat.py +18 -139
- khoj/processor/conversation/offline/chat_model.py +21 -151
- khoj/processor/conversation/openai/gpt.py +12 -126
- khoj/processor/conversation/prompts.py +2 -63
- khoj/routers/api.py +5 -533
- khoj/routers/api_automation.py +243 -0
- khoj/routers/api_chat.py +35 -116
- khoj/routers/helpers.py +329 -80
- khoj/routers/research.py +3 -33
- khoj/utils/helpers.py +0 -6
- {khoj-1.42.2.dev1.dist-info → khoj-1.42.2.dev16.dist-info}/METADATA +1 -1
- {khoj-1.42.2.dev1.dist-info → khoj-1.42.2.dev16.dist-info}/RECORD +54 -53
- khoj/interface/compiled/_next/static/chunks/7127-d3199617463d45f0.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/page-465741d9149dfd48.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-33934fc2d6ae6838.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-1726184cf1c1b86e.js +0 -1
- khoj/interface/compiled/_next/static/css/fca983d49c3dd1a3.css +0 -1
- /khoj/interface/compiled/_next/static/{Dzg_ViqMwQEjqMgetZPRc → OTsOjbrtuaYMukpuJS4sy}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{Dzg_ViqMwQEjqMgetZPRc → OTsOjbrtuaYMukpuJS4sy}/_ssgManifest.js +0 -0
- /khoj/interface/compiled/_next/static/chunks/{1915-ab4353eaca76f690.js → 1915-1943ee8a628b893c.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{2117-1c18aa2098982bf9.js → 2117-5a41630a2bd2eae8.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4363-4efaf12abe696251.js → 4363-e6ac2203564d1a3b.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{4447-5d44807c40355b1a.js → 4447-e038b251d626c340.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{8667-adbe6017a66cef10.js → 8667-8136f74e9a086fca.js} +0 -0
- /khoj/interface/compiled/_next/static/chunks/{9259-d8bcd9da9e80c81e.js → 9259-640fdd77408475df.js} +0 -0
- {khoj-1.42.2.dev1.dist-info → khoj-1.42.2.dev16.dist-info}/WHEEL +0 -0
- {khoj-1.42.2.dev1.dist-info → khoj-1.42.2.dev16.dist-info}/entry_points.txt +0 -0
- {khoj-1.42.2.dev1.dist-info → khoj-1.42.2.dev16.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
import threading
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
import cron_descriptor
|
7
|
+
import pytz
|
8
|
+
from apscheduler.job import Job
|
9
|
+
from apscheduler.triggers.cron import CronTrigger
|
10
|
+
from fastapi import APIRouter, Request
|
11
|
+
from fastapi.responses import Response
|
12
|
+
from starlette.authentication import requires
|
13
|
+
|
14
|
+
from khoj.database.adapters import AutomationAdapters, ConversationAdapters
|
15
|
+
from khoj.database.models import KhojUser
|
16
|
+
from khoj.processor.conversation.utils import clean_json
|
17
|
+
from khoj.routers.helpers import schedule_automation, schedule_query
|
18
|
+
from khoj.utils.helpers import is_none_or_empty
|
19
|
+
|
20
|
+
# Initialize Router
|
21
|
+
api_automation = APIRouter()
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
|
25
|
+
@api_automation.get("", response_class=Response)
|
26
|
+
@requires(["authenticated"])
|
27
|
+
def get_automations(request: Request) -> Response:
|
28
|
+
user: KhojUser = request.user.object
|
29
|
+
|
30
|
+
# Collate all automations created by user that are still active
|
31
|
+
automations_info = [automation_info for automation_info in AutomationAdapters.get_automations_metadata(user)]
|
32
|
+
|
33
|
+
# Return tasks information as a JSON response
|
34
|
+
return Response(content=json.dumps(automations_info), media_type="application/json", status_code=200)
|
35
|
+
|
36
|
+
|
37
|
+
@api_automation.delete("", response_class=Response)
|
38
|
+
@requires(["authenticated"])
|
39
|
+
def delete_automation(request: Request, automation_id: str) -> Response:
|
40
|
+
user: KhojUser = request.user.object
|
41
|
+
|
42
|
+
try:
|
43
|
+
automation_info = AutomationAdapters.delete_automation(user, automation_id)
|
44
|
+
except ValueError:
|
45
|
+
return Response(status_code=204)
|
46
|
+
|
47
|
+
# Return deleted automation information as a JSON response
|
48
|
+
return Response(content=json.dumps(automation_info), media_type="application/json", status_code=200)
|
49
|
+
|
50
|
+
|
51
|
+
@api_automation.post("", response_class=Response)
|
52
|
+
@requires(["authenticated"])
|
53
|
+
def post_automation(
|
54
|
+
request: Request,
|
55
|
+
q: str,
|
56
|
+
crontime: str,
|
57
|
+
subject: Optional[str] = None,
|
58
|
+
city: Optional[str] = None,
|
59
|
+
region: Optional[str] = None,
|
60
|
+
country: Optional[str] = None,
|
61
|
+
timezone: Optional[str] = None,
|
62
|
+
) -> Response:
|
63
|
+
user: KhojUser = request.user.object
|
64
|
+
|
65
|
+
# Perform validation checks
|
66
|
+
if is_none_or_empty(q) or is_none_or_empty(crontime):
|
67
|
+
return Response(content="A query and crontime is required", status_code=400)
|
68
|
+
if not cron_descriptor.get_description(crontime):
|
69
|
+
return Response(content="Invalid crontime", status_code=400)
|
70
|
+
|
71
|
+
# Infer subject, query to run
|
72
|
+
_, query_to_run, generated_subject = schedule_query(q, chat_history=[], user=user)
|
73
|
+
subject = subject or generated_subject
|
74
|
+
|
75
|
+
# Normalize query parameters
|
76
|
+
# Add /automated_task prefix to query if not present
|
77
|
+
query_to_run = query_to_run.strip()
|
78
|
+
if not query_to_run.startswith("/automated_task"):
|
79
|
+
query_to_run = f"/automated_task {query_to_run}"
|
80
|
+
|
81
|
+
# Normalize crontime for AP Scheduler CronTrigger
|
82
|
+
crontime = crontime.strip()
|
83
|
+
if len(crontime.split(" ")) > 5:
|
84
|
+
# Truncate crontime to 5 fields
|
85
|
+
crontime = " ".join(crontime.split(" ")[:5])
|
86
|
+
|
87
|
+
# Convert crontime to standard unix crontime
|
88
|
+
crontime = crontime.replace("?", "*")
|
89
|
+
|
90
|
+
# Disallow minute level automation recurrence
|
91
|
+
minute_value = crontime.split(" ")[0]
|
92
|
+
if not minute_value.isdigit():
|
93
|
+
return Response(
|
94
|
+
content="Minute level recurrence is unsupported. Please create a less frequent schedule.",
|
95
|
+
status_code=400,
|
96
|
+
)
|
97
|
+
|
98
|
+
# Create new Conversation Session associated with this new task
|
99
|
+
title = f"Automation: {subject}"
|
100
|
+
conversation = ConversationAdapters.create_conversation_session(user, request.user.client_app, title=title)
|
101
|
+
|
102
|
+
# Schedule automation with query_to_run, timezone, subject directly provided by user
|
103
|
+
try:
|
104
|
+
# Use the query to run as the scheduling request if the scheduling request is unset
|
105
|
+
calling_url = request.url.replace(query=f"{request.url.query}")
|
106
|
+
automation = schedule_automation(
|
107
|
+
query_to_run, subject, crontime, timezone, q, user, calling_url, str(conversation.id)
|
108
|
+
)
|
109
|
+
except Exception as e:
|
110
|
+
logger.error(f"Error creating automation {q} for {user.email}: {e}", exc_info=True)
|
111
|
+
return Response(
|
112
|
+
content=f"Unable to create automation. Ensure the automation doesn't already exist.",
|
113
|
+
media_type="text/plain",
|
114
|
+
status_code=500,
|
115
|
+
)
|
116
|
+
|
117
|
+
# Collate info about the created user automation
|
118
|
+
automation_info = AutomationAdapters.get_automation_metadata(user, automation)
|
119
|
+
|
120
|
+
# Return information about the created automation as a JSON response
|
121
|
+
return Response(content=json.dumps(automation_info), media_type="application/json", status_code=200)
|
122
|
+
|
123
|
+
|
124
|
+
@api_automation.post("/trigger", response_class=Response)
|
125
|
+
@requires(["authenticated"])
|
126
|
+
def trigger_manual_job(
|
127
|
+
request: Request,
|
128
|
+
automation_id: str,
|
129
|
+
):
|
130
|
+
user: KhojUser = request.user.object
|
131
|
+
|
132
|
+
# Check, get automation to edit
|
133
|
+
try:
|
134
|
+
automation: Job = AutomationAdapters.get_automation(user, automation_id)
|
135
|
+
except ValueError as e:
|
136
|
+
logger.error(f"Error triggering automation {automation_id} for {user.email}: {e}", exc_info=True)
|
137
|
+
return Response(content="Invalid automation", status_code=403)
|
138
|
+
|
139
|
+
# Trigger the job without waiting for the result.
|
140
|
+
scheduled_chat_func = automation.func
|
141
|
+
|
142
|
+
# Run the function in a separate thread
|
143
|
+
thread = threading.Thread(target=scheduled_chat_func, args=automation.args, kwargs=automation.kwargs)
|
144
|
+
thread.start()
|
145
|
+
|
146
|
+
return Response(content="Automation triggered", status_code=200)
|
147
|
+
|
148
|
+
|
149
|
+
@api_automation.put("", response_class=Response)
|
150
|
+
@requires(["authenticated"])
|
151
|
+
def edit_job(
|
152
|
+
request: Request,
|
153
|
+
automation_id: str,
|
154
|
+
q: Optional[str],
|
155
|
+
subject: Optional[str],
|
156
|
+
crontime: Optional[str],
|
157
|
+
city: Optional[str] = None,
|
158
|
+
region: Optional[str] = None,
|
159
|
+
country: Optional[str] = None,
|
160
|
+
timezone: Optional[str] = None,
|
161
|
+
) -> Response:
|
162
|
+
user: KhojUser = request.user.object
|
163
|
+
|
164
|
+
# Perform validation checks
|
165
|
+
if is_none_or_empty(q) or is_none_or_empty(subject) or is_none_or_empty(crontime):
|
166
|
+
return Response(content="A query, subject and crontime is required", status_code=400)
|
167
|
+
if not cron_descriptor.get_description(crontime):
|
168
|
+
return Response(content="Invalid crontime", status_code=400)
|
169
|
+
|
170
|
+
# Check, get automation to edit
|
171
|
+
try:
|
172
|
+
automation: Job = AutomationAdapters.get_automation(user, automation_id)
|
173
|
+
except ValueError as e:
|
174
|
+
logger.error(f"Error editing automation {automation_id} for {user.email}: {e}", exc_info=True)
|
175
|
+
return Response(content="Invalid automation", status_code=403)
|
176
|
+
|
177
|
+
# Infer subject, query to run
|
178
|
+
_, query_to_run, _ = schedule_query(q, chat_history=[], user=user)
|
179
|
+
subject = subject
|
180
|
+
|
181
|
+
# Normalize query parameters
|
182
|
+
# Add /automated_task prefix to query if not present
|
183
|
+
query_to_run = query_to_run.strip()
|
184
|
+
if not query_to_run.startswith("/automated_task"):
|
185
|
+
query_to_run = f"/automated_task {query_to_run}"
|
186
|
+
# Normalize crontime for AP Scheduler CronTrigger
|
187
|
+
crontime = crontime.strip()
|
188
|
+
if len(crontime.split(" ")) > 5:
|
189
|
+
# Truncate crontime to 5 fields
|
190
|
+
crontime = " ".join(crontime.split(" ")[:5])
|
191
|
+
# Convert crontime to standard unix crontime
|
192
|
+
crontime = crontime.replace("?", "*")
|
193
|
+
|
194
|
+
# Disallow minute level automation recurrence
|
195
|
+
minute_value = crontime.split(" ")[0]
|
196
|
+
if not minute_value.isdigit():
|
197
|
+
return Response(
|
198
|
+
content="Recurrence of every X minutes is unsupported. Please create a less frequent schedule.",
|
199
|
+
status_code=400,
|
200
|
+
)
|
201
|
+
|
202
|
+
# Construct updated automation metadata
|
203
|
+
automation_metadata: dict[str, str] = json.loads(clean_json(automation.name))
|
204
|
+
automation_metadata["scheduling_request"] = q
|
205
|
+
automation_metadata["query_to_run"] = query_to_run
|
206
|
+
automation_metadata["subject"] = subject.strip()
|
207
|
+
automation_metadata["crontime"] = crontime
|
208
|
+
conversation_id = automation_metadata.get("conversation_id")
|
209
|
+
|
210
|
+
if not conversation_id:
|
211
|
+
title = f"Automation: {subject}"
|
212
|
+
|
213
|
+
# Create new Conversation Session associated with this new task
|
214
|
+
conversation = ConversationAdapters.create_conversation_session(user, request.user.client_app, title=title)
|
215
|
+
|
216
|
+
conversation_id = str(conversation.id)
|
217
|
+
automation_metadata["conversation_id"] = conversation_id
|
218
|
+
|
219
|
+
# Modify automation with updated query, subject
|
220
|
+
automation.modify(
|
221
|
+
name=json.dumps(automation_metadata),
|
222
|
+
kwargs={
|
223
|
+
"query_to_run": query_to_run,
|
224
|
+
"subject": subject,
|
225
|
+
"scheduling_request": q,
|
226
|
+
"user": user,
|
227
|
+
"calling_url": request.url,
|
228
|
+
"conversation_id": conversation_id,
|
229
|
+
},
|
230
|
+
)
|
231
|
+
|
232
|
+
# Reschedule automation if crontime updated
|
233
|
+
user_timezone = pytz.timezone(timezone)
|
234
|
+
trigger = CronTrigger.from_crontab(crontime, user_timezone)
|
235
|
+
if automation.trigger != trigger:
|
236
|
+
automation.reschedule(trigger=trigger)
|
237
|
+
|
238
|
+
# Collate info about the updated user automation
|
239
|
+
automation = AutomationAdapters.get_automation(user, automation.id)
|
240
|
+
automation_info = AutomationAdapters.get_automation_metadata(user, automation)
|
241
|
+
|
242
|
+
# Return modified automation information as a JSON response
|
243
|
+
return Response(content=json.dumps(automation_info), media_type="application/json", status_code=200)
|
khoj/routers/api_chat.py
CHANGED
@@ -40,7 +40,6 @@ from khoj.processor.tools.online_search import (
|
|
40
40
|
search_online,
|
41
41
|
)
|
42
42
|
from khoj.processor.tools.run_code import run_code
|
43
|
-
from khoj.routers.api import extract_references_and_questions
|
44
43
|
from khoj.routers.email import send_query_feedback
|
45
44
|
from khoj.routers.helpers import (
|
46
45
|
ApiImageRateLimiter,
|
@@ -63,6 +62,7 @@ from khoj.routers.helpers import (
|
|
63
62
|
is_query_empty,
|
64
63
|
is_ready_to_chat,
|
65
64
|
read_chat_stream,
|
65
|
+
search_documents,
|
66
66
|
update_telemetry_state,
|
67
67
|
validate_chat_model,
|
68
68
|
)
|
@@ -1055,113 +1055,11 @@ async def chat(
|
|
1055
1055
|
if state.verbose > 1:
|
1056
1056
|
logger.debug(f'Researched Results: {"".join(r.summarizedResult for r in research_results)}')
|
1057
1057
|
|
1058
|
-
used_slash_summarize = conversation_commands == [ConversationCommand.Summarize]
|
1059
|
-
# Skip trying to summarize if
|
1060
|
-
if (
|
1061
|
-
# summarization intent was inferred
|
1062
|
-
ConversationCommand.Summarize in conversation_commands
|
1063
|
-
# and not triggered via slash command
|
1064
|
-
and not used_slash_summarize
|
1065
|
-
# but we can't actually summarize
|
1066
|
-
and len(file_filters) == 0
|
1067
|
-
):
|
1068
|
-
conversation_commands.remove(ConversationCommand.Summarize)
|
1069
|
-
elif ConversationCommand.Summarize in conversation_commands:
|
1070
|
-
response_log = ""
|
1071
|
-
agent_has_entries = await EntryAdapters.aagent_has_entries(agent)
|
1072
|
-
if len(file_filters) == 0 and not agent_has_entries:
|
1073
|
-
response_log = "No files selected for summarization. Please add files using the section on the left."
|
1074
|
-
async for result in send_llm_response(response_log, tracer.get("usage")):
|
1075
|
-
yield result
|
1076
|
-
else:
|
1077
|
-
async for response in generate_summary_from_files(
|
1078
|
-
q=q,
|
1079
|
-
user=user,
|
1080
|
-
file_filters=file_filters,
|
1081
|
-
chat_history=conversation.messages,
|
1082
|
-
query_images=uploaded_images,
|
1083
|
-
agent=agent,
|
1084
|
-
send_status_func=partial(send_event, ChatEvent.STATUS),
|
1085
|
-
query_files=attached_file_context,
|
1086
|
-
tracer=tracer,
|
1087
|
-
):
|
1088
|
-
if isinstance(response, dict) and ChatEvent.STATUS in response:
|
1089
|
-
yield response[ChatEvent.STATUS]
|
1090
|
-
else:
|
1091
|
-
if isinstance(response, str):
|
1092
|
-
response_log = response
|
1093
|
-
async for result in send_llm_response(response, tracer.get("usage")):
|
1094
|
-
yield result
|
1095
|
-
|
1096
|
-
summarized_document = FileAttachment(
|
1097
|
-
name="Summarized Document",
|
1098
|
-
content=response_log,
|
1099
|
-
type="text/plain",
|
1100
|
-
size=len(response_log.encode("utf-8")),
|
1101
|
-
)
|
1102
|
-
|
1103
|
-
async for result in send_event(ChatEvent.GENERATED_ASSETS, {"files": [summarized_document.model_dump()]}):
|
1104
|
-
yield result
|
1105
|
-
|
1106
|
-
generated_files.append(summarized_document)
|
1107
|
-
|
1108
|
-
custom_filters = []
|
1109
|
-
if conversation_commands == [ConversationCommand.Help]:
|
1110
|
-
if not q:
|
1111
|
-
chat_model = await ConversationAdapters.aget_user_chat_model(user)
|
1112
|
-
if chat_model == None:
|
1113
|
-
chat_model = await ConversationAdapters.aget_default_chat_model(user)
|
1114
|
-
model_type = chat_model.model_type
|
1115
|
-
formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device())
|
1116
|
-
async for result in send_llm_response(formatted_help, tracer.get("usage")):
|
1117
|
-
yield result
|
1118
|
-
return
|
1119
|
-
# Adding specification to search online specifically on khoj.dev pages.
|
1120
|
-
custom_filters.append("site:khoj.dev")
|
1121
|
-
conversation_commands.append(ConversationCommand.Online)
|
1122
|
-
|
1123
|
-
if ConversationCommand.Automation in conversation_commands:
|
1124
|
-
try:
|
1125
|
-
automation, crontime, query_to_run, subject = await create_automation(
|
1126
|
-
q, timezone, user, request.url, chat_history, tracer=tracer
|
1127
|
-
)
|
1128
|
-
except Exception as e:
|
1129
|
-
logger.error(f"Error scheduling task {q} for {user.email}: {e}")
|
1130
|
-
error_message = f"Unable to create automation. Ensure the automation doesn't already exist."
|
1131
|
-
async for result in send_llm_response(error_message, tracer.get("usage")):
|
1132
|
-
yield result
|
1133
|
-
return
|
1134
|
-
|
1135
|
-
llm_response = construct_automation_created_message(automation, crontime, query_to_run, subject)
|
1136
|
-
# Trigger task to save conversation to DB
|
1137
|
-
asyncio.create_task(
|
1138
|
-
save_to_conversation_log(
|
1139
|
-
q,
|
1140
|
-
llm_response,
|
1141
|
-
user,
|
1142
|
-
chat_history,
|
1143
|
-
user_message_time,
|
1144
|
-
intent_type="automation",
|
1145
|
-
client_application=request.user.client_app,
|
1146
|
-
conversation_id=conversation_id,
|
1147
|
-
inferred_queries=[query_to_run],
|
1148
|
-
automation_id=automation.id,
|
1149
|
-
query_images=uploaded_images,
|
1150
|
-
train_of_thought=train_of_thought,
|
1151
|
-
raw_query_files=raw_query_files,
|
1152
|
-
tracer=tracer,
|
1153
|
-
)
|
1154
|
-
)
|
1155
|
-
# Send LLM Response
|
1156
|
-
async for result in send_llm_response(llm_response, tracer.get("usage")):
|
1157
|
-
yield result
|
1158
|
-
return
|
1159
|
-
|
1160
1058
|
# Gather Context
|
1161
1059
|
## Extract Document References
|
1162
1060
|
if not ConversationCommand.Research in conversation_commands:
|
1163
1061
|
try:
|
1164
|
-
async for result in
|
1062
|
+
async for result in search_documents(
|
1165
1063
|
user,
|
1166
1064
|
chat_history,
|
1167
1065
|
q,
|
@@ -1216,7 +1114,7 @@ async def chat(
|
|
1216
1114
|
location,
|
1217
1115
|
user,
|
1218
1116
|
partial(send_event, ChatEvent.STATUS),
|
1219
|
-
custom_filters,
|
1117
|
+
custom_filters=[],
|
1220
1118
|
max_online_searches=3,
|
1221
1119
|
query_images=uploaded_images,
|
1222
1120
|
query_files=attached_file_context,
|
@@ -1463,33 +1361,29 @@ async def chat(
|
|
1463
1361
|
code_results,
|
1464
1362
|
operator_results,
|
1465
1363
|
research_results,
|
1466
|
-
inferred_queries,
|
1467
|
-
conversation_commands,
|
1468
1364
|
user,
|
1469
|
-
request.user.client_app,
|
1470
1365
|
location,
|
1471
1366
|
user_name,
|
1472
1367
|
uploaded_images,
|
1473
|
-
train_of_thought,
|
1474
1368
|
attached_file_context,
|
1475
|
-
raw_query_files,
|
1476
|
-
generated_images,
|
1477
1369
|
generated_files,
|
1478
|
-
generated_mermaidjs_diagram,
|
1479
1370
|
program_execution_context,
|
1480
1371
|
generated_asset_results,
|
1481
1372
|
is_subscribed,
|
1482
1373
|
tracer,
|
1483
1374
|
)
|
1484
1375
|
|
1376
|
+
full_response = ""
|
1485
1377
|
async for item in llm_response:
|
1486
|
-
# Should not happen with async generator
|
1487
|
-
if item is None:
|
1378
|
+
# Should not happen with async generator. Skip.
|
1379
|
+
if item is None or not isinstance(item, ResponseWithThought):
|
1380
|
+
logger.warning(f"Unexpected item type in LLM response: {type(item)}. Skipping.")
|
1488
1381
|
continue
|
1489
1382
|
if cancellation_event.is_set():
|
1490
1383
|
break
|
1491
|
-
message = item.response
|
1492
|
-
if
|
1384
|
+
message = item.response
|
1385
|
+
full_response += message if message else ""
|
1386
|
+
if item.thought:
|
1493
1387
|
async for result in send_event(ChatEvent.THOUGHT, item.thought):
|
1494
1388
|
yield result
|
1495
1389
|
continue
|
@@ -1506,6 +1400,31 @@ async def chat(
|
|
1506
1400
|
logger.warning(f"Error during streaming. Stopping send: {e}")
|
1507
1401
|
break
|
1508
1402
|
|
1403
|
+
# Save conversation once finish streaming
|
1404
|
+
asyncio.create_task(
|
1405
|
+
save_to_conversation_log(
|
1406
|
+
q,
|
1407
|
+
chat_response=full_response,
|
1408
|
+
user=user,
|
1409
|
+
chat_history=chat_history,
|
1410
|
+
compiled_references=compiled_references,
|
1411
|
+
online_results=online_results,
|
1412
|
+
code_results=code_results,
|
1413
|
+
operator_results=operator_results,
|
1414
|
+
research_results=research_results,
|
1415
|
+
inferred_queries=inferred_queries,
|
1416
|
+
client_application=request.user.client_app,
|
1417
|
+
conversation_id=str(conversation.id),
|
1418
|
+
query_images=uploaded_images,
|
1419
|
+
train_of_thought=train_of_thought,
|
1420
|
+
raw_query_files=raw_query_files,
|
1421
|
+
generated_images=generated_images,
|
1422
|
+
raw_generated_files=generated_files,
|
1423
|
+
generated_mermaidjs_diagram=generated_mermaidjs_diagram,
|
1424
|
+
tracer=tracer,
|
1425
|
+
)
|
1426
|
+
)
|
1427
|
+
|
1509
1428
|
# Signal end of LLM response after the loop finishes
|
1510
1429
|
if not cancellation_event.is_set():
|
1511
1430
|
async for result in send_event(ChatEvent.END_LLM_RESPONSE, ""):
|