khoj 1.42.1.dev10__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 +9 -7
- khoj/database/models/__init__.py +9 -9
- khoj/interface/compiled/404/index.html +2 -2
- khoj/interface/compiled/_next/static/chunks/7127-79a3af5138960272.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{5138-2cce449fd2454abf.js → 7211-7fedd2ee3655239c.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/page-ef89ac958e78aa81.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-964e8ed3380daff1.js → webpack-0f15e6b51732b337.js} +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 +1 -1
- khoj/interface/compiled/automations/index.html +2 -2
- khoj/interface/compiled/automations/index.txt +2 -2
- 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 +1 -1
- khoj/interface/compiled/search/index.html +2 -2
- khoj/interface/compiled/search/index.txt +1 -1
- khoj/interface/compiled/settings/index.html +2 -2
- khoj/interface/compiled/settings/index.txt +1 -1
- 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 +19 -134
- khoj/processor/conversation/anthropic/utils.py +1 -1
- khoj/processor/conversation/google/gemini_chat.py +20 -141
- khoj/processor/conversation/offline/chat_model.py +23 -153
- khoj/processor/conversation/openai/gpt.py +14 -128
- khoj/processor/conversation/prompts.py +2 -63
- khoj/processor/conversation/utils.py +94 -89
- khoj/processor/image/generate.py +16 -11
- khoj/processor/operator/__init__.py +2 -3
- khoj/processor/operator/operator_agent_binary.py +11 -11
- khoj/processor/tools/online_search.py +9 -3
- khoj/processor/tools/run_code.py +5 -5
- khoj/routers/api.py +5 -527
- khoj/routers/api_automation.py +243 -0
- khoj/routers/api_chat.py +48 -129
- khoj/routers/helpers.py +371 -121
- khoj/routers/research.py +11 -43
- khoj/utils/helpers.py +0 -6
- {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dev16.dist-info}/METADATA +1 -1
- {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dev16.dist-info}/RECORD +51 -50
- khoj/interface/compiled/_next/static/chunks/7127-d3199617463d45f0.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/page-465741d9149dfd48.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-898079bcea5376f4.js +0 -1
- khoj/interface/compiled/_next/static/css/fca983d49c3dd1a3.css +0 -1
- /khoj/interface/compiled/_next/static/{2niR8lV9_OpGs1vdb2yMp → OTsOjbrtuaYMukpuJS4sy}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{2niR8lV9_OpGs1vdb2yMp → OTsOjbrtuaYMukpuJS4sy}/_ssgManifest.js +0 -0
- {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dev16.dist-info}/WHEEL +0 -0
- {khoj-1.42.1.dev10.dist-info → khoj-1.42.2.dev16.dist-info}/entry_points.txt +0 -0
- {khoj-1.42.1.dev10.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
|
)
|
@@ -752,7 +752,7 @@ async def chat(
|
|
752
752
|
q,
|
753
753
|
chat_response="",
|
754
754
|
user=user,
|
755
|
-
|
755
|
+
chat_history=chat_history,
|
756
756
|
compiled_references=compiled_references,
|
757
757
|
online_results=online_results,
|
758
758
|
code_results=code_results,
|
@@ -918,7 +918,7 @@ async def chat(
|
|
918
918
|
if city or region or country or country_code:
|
919
919
|
location = LocationData(city=city, region=region, country=country, country_code=country_code)
|
920
920
|
user_message_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
921
|
-
|
921
|
+
chat_history = conversation.messages
|
922
922
|
|
923
923
|
# If interrupt flag is set, wait for the previous turn to be saved before proceeding
|
924
924
|
if interrupt_flag:
|
@@ -964,14 +964,14 @@ async def chat(
|
|
964
964
|
operator_results = [OperatorRun(**iter_dict) for iter_dict in last_message.operatorContext or []]
|
965
965
|
train_of_thought = [thought.model_dump() for thought in last_message.trainOfThought or []]
|
966
966
|
# Drop the interrupted message from conversation history
|
967
|
-
|
967
|
+
chat_history.pop()
|
968
968
|
logger.info(f"Loaded interrupted partial context from conversation {conversation_id}.")
|
969
969
|
|
970
970
|
if conversation_commands == [ConversationCommand.Default]:
|
971
971
|
try:
|
972
972
|
chosen_io = await aget_data_sources_and_output_format(
|
973
973
|
q,
|
974
|
-
|
974
|
+
chat_history,
|
975
975
|
is_automated_task,
|
976
976
|
user=user,
|
977
977
|
query_images=uploaded_images,
|
@@ -1011,7 +1011,7 @@ async def chat(
|
|
1011
1011
|
user=user,
|
1012
1012
|
query=defiltered_query,
|
1013
1013
|
conversation_id=conversation_id,
|
1014
|
-
conversation_history=
|
1014
|
+
conversation_history=conversation.messages,
|
1015
1015
|
previous_iterations=list(research_results),
|
1016
1016
|
query_images=uploaded_images,
|
1017
1017
|
agent=agent,
|
@@ -1055,115 +1055,13 @@ 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
|
-
meta_log=meta_log,
|
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, meta_log, 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
|
-
meta_log,
|
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,
|
1168
1066
|
(n or 7),
|
1169
1067
|
d,
|
@@ -1212,11 +1110,11 @@ async def chat(
|
|
1212
1110
|
try:
|
1213
1111
|
async for result in search_online(
|
1214
1112
|
defiltered_query,
|
1215
|
-
|
1113
|
+
chat_history,
|
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,
|
@@ -1240,7 +1138,7 @@ async def chat(
|
|
1240
1138
|
try:
|
1241
1139
|
async for result in read_webpages(
|
1242
1140
|
defiltered_query,
|
1243
|
-
|
1141
|
+
chat_history,
|
1244
1142
|
location,
|
1245
1143
|
user,
|
1246
1144
|
partial(send_event, ChatEvent.STATUS),
|
@@ -1281,7 +1179,7 @@ async def chat(
|
|
1281
1179
|
context = f"# Iteration 1:\n#---\nNotes:\n{compiled_references}\n\nOnline Results:{online_results}"
|
1282
1180
|
async for result in run_code(
|
1283
1181
|
defiltered_query,
|
1284
|
-
|
1182
|
+
chat_history,
|
1285
1183
|
context,
|
1286
1184
|
location,
|
1287
1185
|
user,
|
@@ -1306,7 +1204,7 @@ async def chat(
|
|
1306
1204
|
async for result in operate_environment(
|
1307
1205
|
defiltered_query,
|
1308
1206
|
user,
|
1309
|
-
|
1207
|
+
chat_history,
|
1310
1208
|
location,
|
1311
1209
|
list(operator_results)[-1] if operator_results else None,
|
1312
1210
|
query_images=uploaded_images,
|
@@ -1356,7 +1254,7 @@ async def chat(
|
|
1356
1254
|
async for result in text_to_image(
|
1357
1255
|
defiltered_query,
|
1358
1256
|
user,
|
1359
|
-
|
1257
|
+
chat_history,
|
1360
1258
|
location_data=location,
|
1361
1259
|
references=compiled_references,
|
1362
1260
|
online_results=online_results,
|
@@ -1400,7 +1298,7 @@ async def chat(
|
|
1400
1298
|
|
1401
1299
|
async for result in generate_mermaidjs_diagram(
|
1402
1300
|
q=defiltered_query,
|
1403
|
-
|
1301
|
+
chat_history=chat_history,
|
1404
1302
|
location_data=location,
|
1405
1303
|
note_references=compiled_references,
|
1406
1304
|
online_results=online_results,
|
@@ -1456,40 +1354,36 @@ async def chat(
|
|
1456
1354
|
|
1457
1355
|
llm_response, chat_metadata = await agenerate_chat_response(
|
1458
1356
|
defiltered_query,
|
1459
|
-
|
1357
|
+
chat_history,
|
1460
1358
|
conversation,
|
1461
1359
|
compiled_references,
|
1462
1360
|
online_results,
|
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, ""):
|