khoj 1.42.8.dev4__py3-none-any.whl → 1.42.9.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/database/adapters/__init__.py +20 -0
- khoj/interface/compiled/404/index.html +2 -2
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-2e626327abfbe612.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-d6acbba22ccac0ff.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/{page-802dedbf1d9d5e1e.js → page-9967631715682f3c.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/{page-85b9b416898738f7.js → page-6e91caf9bc0c8aba.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/layout-94c76c3a41db42a2.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-95998f0bdc22bb13.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-c062269e6906ef22.js → page-8c8c175f7f212b03.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/{webpack-88659b10d39e393f.js → webpack-4bf3eab7681a1206.js} +1 -1
- khoj/interface/compiled/_next/static/css/1e9b757ee2a2b34b.css +1 -0
- khoj/interface/compiled/_next/static/css/440ae0f0f650dc35.css +1 -0
- khoj/interface/compiled/_next/static/css/bd2071cad2ecf293.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 +1 -1
- 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 +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 +11 -2
- khoj/processor/conversation/anthropic/utils.py +90 -103
- khoj/processor/conversation/google/gemini_chat.py +4 -1
- khoj/processor/conversation/google/utils.py +84 -19
- khoj/processor/conversation/offline/chat_model.py +3 -3
- khoj/processor/conversation/openai/gpt.py +13 -38
- khoj/processor/conversation/openai/utils.py +113 -12
- khoj/processor/conversation/prompts.py +17 -35
- khoj/processor/conversation/utils.py +128 -57
- khoj/processor/operator/grounding_agent.py +1 -1
- khoj/processor/operator/operator_agent_binary.py +4 -3
- khoj/processor/tools/online_search.py +18 -0
- khoj/processor/tools/run_code.py +1 -1
- khoj/routers/api_chat.py +1 -1
- khoj/routers/helpers.py +293 -26
- khoj/routers/research.py +169 -155
- khoj/utils/helpers.py +284 -8
- {khoj-1.42.8.dev4.dist-info → khoj-1.42.9.dev16.dist-info}/METADATA +1 -1
- {khoj-1.42.8.dev4.dist-info → khoj-1.42.9.dev16.dist-info}/RECORD +51 -51
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-d5ae861e1ade9d08.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/search/layout-f5881c7ae3ba0795.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-b3f7ae1ef8871d30.js +0 -1
- khoj/interface/compiled/_next/static/css/02f60900b0d89ec7.css +0 -1
- khoj/interface/compiled/_next/static/css/76c658ee459140a9.css +0 -1
- khoj/interface/compiled/_next/static/css/fbacbdfd5e7f3f0e.css +0 -1
- /khoj/interface/compiled/_next/static/{8Wx2kDD5oC-v77JDu6vKI → w19FJJa9p2AFJB6DEektd}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{8Wx2kDD5oC-v77JDu6vKI → w19FJJa9p2AFJB6DEektd}/_ssgManifest.js +0 -0
- {khoj-1.42.8.dev4.dist-info → khoj-1.42.9.dev16.dist-info}/WHEEL +0 -0
- {khoj-1.42.8.dev4.dist-info → khoj-1.42.9.dev16.dist-info}/entry_points.txt +0 -0
- {khoj-1.42.8.dev4.dist-info → khoj-1.42.9.dev16.dist-info}/licenses/LICENSE +0 -0
khoj/routers/research.py
CHANGED
@@ -3,11 +3,9 @@ import logging
|
|
3
3
|
import os
|
4
4
|
from copy import deepcopy
|
5
5
|
from datetime import datetime
|
6
|
-
from
|
7
|
-
from typing import Callable, Dict, List, Optional, Type
|
6
|
+
from typing import Callable, Dict, List, Optional
|
8
7
|
|
9
8
|
import yaml
|
10
|
-
from pydantic import BaseModel, Field
|
11
9
|
|
12
10
|
from khoj.database.adapters import AgentAdapters, EntryAdapters
|
13
11
|
from khoj.database.models import Agent, ChatMessageModel, KhojUser
|
@@ -15,25 +13,31 @@ from khoj.processor.conversation import prompts
|
|
15
13
|
from khoj.processor.conversation.utils import (
|
16
14
|
OperatorRun,
|
17
15
|
ResearchIteration,
|
16
|
+
ToolCall,
|
18
17
|
construct_iteration_history,
|
19
18
|
construct_tool_chat_history,
|
20
19
|
load_complex_json,
|
21
20
|
)
|
22
21
|
from khoj.processor.operator import operate_environment
|
23
|
-
from khoj.processor.tools.online_search import
|
22
|
+
from khoj.processor.tools.online_search import read_webpages_content, search_online
|
24
23
|
from khoj.processor.tools.run_code import run_code
|
25
24
|
from khoj.routers.helpers import (
|
26
25
|
ChatEvent,
|
27
26
|
generate_summary_from_files,
|
27
|
+
grep_files,
|
28
|
+
list_files,
|
28
29
|
search_documents,
|
29
30
|
send_message_to_model_wrapper,
|
31
|
+
view_file_content,
|
30
32
|
)
|
31
33
|
from khoj.utils.helpers import (
|
32
34
|
ConversationCommand,
|
35
|
+
ToolDefinition,
|
36
|
+
dict_to_tuple,
|
33
37
|
is_none_or_empty,
|
34
38
|
is_operator_enabled,
|
35
39
|
timer,
|
36
|
-
|
40
|
+
tools_for_research_llm,
|
37
41
|
truncate_code_context,
|
38
42
|
)
|
39
43
|
from khoj.utils.rawconfig import LocationData
|
@@ -41,47 +45,6 @@ from khoj.utils.rawconfig import LocationData
|
|
41
45
|
logger = logging.getLogger(__name__)
|
42
46
|
|
43
47
|
|
44
|
-
class PlanningResponse(BaseModel):
|
45
|
-
"""
|
46
|
-
Schema for the response from planning agent when deciding the next tool to pick.
|
47
|
-
"""
|
48
|
-
|
49
|
-
scratchpad: str = Field(..., description="Scratchpad to reason about which tool to use next")
|
50
|
-
|
51
|
-
class Config:
|
52
|
-
arbitrary_types_allowed = True
|
53
|
-
|
54
|
-
@classmethod
|
55
|
-
def create_model_with_enum(cls: Type["PlanningResponse"], tool_options: dict) -> Type["PlanningResponse"]:
|
56
|
-
"""
|
57
|
-
Factory method that creates a customized PlanningResponse model
|
58
|
-
with a properly typed tool field based on available tools.
|
59
|
-
|
60
|
-
The tool field is dynamically generated based on available tools.
|
61
|
-
The query field should be filled by the model after the tool field for a more logical reasoning flow.
|
62
|
-
|
63
|
-
Args:
|
64
|
-
tool_options: Dictionary mapping tool names to values
|
65
|
-
|
66
|
-
Returns:
|
67
|
-
A customized PlanningResponse class
|
68
|
-
"""
|
69
|
-
# Create dynamic enum from tool options
|
70
|
-
tool_enum = Enum("ToolEnum", tool_options) # type: ignore
|
71
|
-
|
72
|
-
# Create and return a customized response model with the enum
|
73
|
-
class PlanningResponseWithTool(PlanningResponse):
|
74
|
-
"""
|
75
|
-
Use the scratchpad to reason about which tool to use next and the query to send to the tool.
|
76
|
-
Pick tool from provided options and your query to send to the tool.
|
77
|
-
"""
|
78
|
-
|
79
|
-
tool: tool_enum = Field(..., description="Name of the tool to use")
|
80
|
-
query: str = Field(..., description="Detailed query for the selected tool")
|
81
|
-
|
82
|
-
return PlanningResponseWithTool
|
83
|
-
|
84
|
-
|
85
48
|
async def apick_next_tool(
|
86
49
|
query: str,
|
87
50
|
conversation_history: List[ChatMessageModel],
|
@@ -104,12 +67,13 @@ async def apick_next_tool(
|
|
104
67
|
# Continue with previous iteration if a multi-step tool use is in progress
|
105
68
|
if (
|
106
69
|
previous_iterations
|
107
|
-
and previous_iterations[-1].
|
70
|
+
and previous_iterations[-1].query
|
71
|
+
and isinstance(previous_iterations[-1].query, ToolCall)
|
72
|
+
and previous_iterations[-1].query.name == ConversationCommand.Operator
|
108
73
|
and not previous_iterations[-1].summarizedResult
|
109
74
|
):
|
110
75
|
previous_iteration = previous_iterations[-1]
|
111
76
|
yield ResearchIteration(
|
112
|
-
tool=previous_iteration.tool,
|
113
77
|
query=query,
|
114
78
|
context=previous_iteration.context,
|
115
79
|
onlineContext=previous_iteration.onlineContext,
|
@@ -120,30 +84,40 @@ async def apick_next_tool(
|
|
120
84
|
return
|
121
85
|
|
122
86
|
# Construct tool options for the agent to choose from
|
123
|
-
|
87
|
+
tools = []
|
124
88
|
tool_options_str = ""
|
125
89
|
agent_tools = agent.input_tools if agent else []
|
126
90
|
user_has_entries = await EntryAdapters.auser_has_entries(user)
|
127
|
-
for tool,
|
91
|
+
for tool, tool_data in tools_for_research_llm.items():
|
128
92
|
# Skip showing operator tool as an option if not enabled
|
129
93
|
if tool == ConversationCommand.Operator and not is_operator_enabled():
|
130
94
|
continue
|
131
|
-
# Skip showing
|
132
|
-
if
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
95
|
+
# Skip showing document related tools if user has no documents
|
96
|
+
if (
|
97
|
+
tool == ConversationCommand.SemanticSearchFiles
|
98
|
+
or tool == ConversationCommand.RegexSearchFiles
|
99
|
+
or tool == ConversationCommand.ViewFile
|
100
|
+
or tool == ConversationCommand.ListFiles
|
101
|
+
) and not user_has_entries:
|
102
|
+
continue
|
103
|
+
if tool == ConversationCommand.SemanticSearchFiles:
|
104
|
+
description = tool_data.description.format(max_search_queries=max_document_searches)
|
105
|
+
elif tool == ConversationCommand.Webpage:
|
106
|
+
description = tool_data.description.format(max_webpages_to_read=max_webpages_to_read)
|
107
|
+
elif tool == ConversationCommand.Online:
|
108
|
+
description = tool_data.description.format(max_search_queries=max_online_searches)
|
109
|
+
else:
|
110
|
+
description = tool_data.description
|
140
111
|
# Add tool if agent does not have any tools defined or the tool is supported by the agent.
|
141
112
|
if len(agent_tools) == 0 or tool.value in agent_tools:
|
142
|
-
tool_options[tool.name] = tool.value
|
143
113
|
tool_options_str += f'- "{tool.value}": "{description}"\n'
|
144
|
-
|
145
|
-
|
146
|
-
|
114
|
+
tools.append(
|
115
|
+
ToolDefinition(
|
116
|
+
name=tool.value,
|
117
|
+
description=description,
|
118
|
+
schema=tool_data.schema,
|
119
|
+
)
|
120
|
+
)
|
147
121
|
|
148
122
|
today = datetime.today()
|
149
123
|
location_data = f"{location}" if location else "Unknown"
|
@@ -162,24 +136,17 @@ async def apick_next_tool(
|
|
162
136
|
max_iterations=max_iterations,
|
163
137
|
)
|
164
138
|
|
165
|
-
if query_images:
|
166
|
-
query = f"[placeholder for user attached images]\n{query}"
|
167
|
-
|
168
139
|
# Construct chat history with user and iteration history with researcher agent for context
|
169
|
-
iteration_chat_history = construct_iteration_history(previous_iterations,
|
140
|
+
iteration_chat_history = construct_iteration_history(previous_iterations, query, query_images, query_files)
|
170
141
|
chat_and_research_history = conversation_history + iteration_chat_history
|
171
142
|
|
172
|
-
# Plan function execution for the next tool
|
173
|
-
query = prompts.plan_function_execution_next_tool.format(query=query) if iteration_chat_history else query
|
174
|
-
|
175
143
|
try:
|
176
144
|
with timer("Chat actor: Infer information sources to refer", logger):
|
177
145
|
response = await send_message_to_model_wrapper(
|
178
|
-
query=
|
146
|
+
query="",
|
179
147
|
system_message=function_planning_prompt,
|
180
148
|
chat_history=chat_and_research_history,
|
181
|
-
|
182
|
-
response_schema=planning_response_model,
|
149
|
+
tools=tools,
|
183
150
|
deepthought=True,
|
184
151
|
user=user,
|
185
152
|
query_images=query_images,
|
@@ -190,48 +157,38 @@ async def apick_next_tool(
|
|
190
157
|
except Exception as e:
|
191
158
|
logger.error(f"Failed to infer information sources to refer: {e}", exc_info=True)
|
192
159
|
yield ResearchIteration(
|
193
|
-
tool=None,
|
194
160
|
query=None,
|
195
161
|
warning="Failed to infer information sources to refer. Skipping iteration. Try again.",
|
196
162
|
)
|
197
163
|
return
|
198
164
|
|
199
165
|
try:
|
200
|
-
response
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
generated_query = response.get("query", None)
|
205
|
-
scratchpad = response.get("scratchpad", None)
|
206
|
-
warning = None
|
207
|
-
logger.info(f"Response for determining relevant tools: {response}")
|
208
|
-
|
209
|
-
# Detect selection of previously used query, tool combination.
|
210
|
-
previous_tool_query_combinations = {(i.tool, i.query) for i in previous_iterations if i.warning is None}
|
211
|
-
if (selected_tool, generated_query) in previous_tool_query_combinations:
|
212
|
-
warning = f"Repeated tool, query combination detected. Skipping iteration. Try something different."
|
213
|
-
# Only send client status updates if we'll execute this iteration
|
214
|
-
elif send_status_func:
|
215
|
-
determined_tool_message = "**Determined Tool**: "
|
216
|
-
determined_tool_message += (
|
217
|
-
f"{selected_tool}({generated_query})." if selected_tool != ConversationCommand.Text else "respond."
|
218
|
-
)
|
219
|
-
determined_tool_message += f"\nReason: {scratchpad}" if scratchpad else ""
|
220
|
-
async for event in send_status_func(f"{scratchpad}"):
|
221
|
-
yield {ChatEvent.STATUS: event}
|
222
|
-
|
223
|
-
yield ResearchIteration(
|
224
|
-
tool=selected_tool,
|
225
|
-
query=generated_query,
|
226
|
-
warning=warning,
|
227
|
-
)
|
166
|
+
# Try parse the response as function call response to infer next tool to use.
|
167
|
+
# TODO: Handle multiple tool calls.
|
168
|
+
response_text = response.text
|
169
|
+
parsed_response = [ToolCall(**item) for item in load_complex_json(response_text)][0]
|
228
170
|
except Exception as e:
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
171
|
+
# Otherwise assume the model has decided to end the research run and respond to the user.
|
172
|
+
parsed_response = ToolCall(name=ConversationCommand.Text, args={"response": response_text}, id=None)
|
173
|
+
|
174
|
+
# If we have a valid response, extract the tool and query.
|
175
|
+
warning = None
|
176
|
+
logger.info(f"Response for determining relevant tools: {parsed_response.name}({parsed_response.args})")
|
177
|
+
|
178
|
+
# Detect selection of previously used query, tool combination.
|
179
|
+
previous_tool_query_combinations = {
|
180
|
+
(i.query.name, dict_to_tuple(i.query.args))
|
181
|
+
for i in previous_iterations
|
182
|
+
if i.warning is None and isinstance(i.query, ToolCall)
|
183
|
+
}
|
184
|
+
if (parsed_response.name, dict_to_tuple(parsed_response.args)) in previous_tool_query_combinations:
|
185
|
+
warning = f"Repeated tool, query combination detected. Skipping iteration. Try something different."
|
186
|
+
# Only send client status updates if we'll execute this iteration and model has thoughts to share.
|
187
|
+
elif send_status_func and not is_none_or_empty(response.thought):
|
188
|
+
async for event in send_status_func(response.thought):
|
189
|
+
yield {ChatEvent.STATUS: event}
|
190
|
+
|
191
|
+
yield ResearchIteration(query=parsed_response, warning=warning, raw_response=response.raw_content)
|
235
192
|
|
236
193
|
|
237
194
|
async def research(
|
@@ -257,10 +214,10 @@ async def research(
|
|
257
214
|
MAX_ITERATIONS = int(os.getenv("KHOJ_RESEARCH_ITERATIONS", 5))
|
258
215
|
|
259
216
|
# Incorporate previous partial research into current research chat history
|
260
|
-
research_conversation_history = deepcopy(conversation_history)
|
217
|
+
research_conversation_history = [chat for chat in deepcopy(conversation_history) if chat.message]
|
261
218
|
if current_iteration := len(previous_iterations) > 0:
|
262
219
|
logger.info(f"Continuing research with the previous {len(previous_iterations)} iteration results.")
|
263
|
-
previous_iterations_history = construct_iteration_history(previous_iterations
|
220
|
+
previous_iterations_history = construct_iteration_history(previous_iterations)
|
264
221
|
research_conversation_history += previous_iterations_history
|
265
222
|
|
266
223
|
while current_iteration < MAX_ITERATIONS:
|
@@ -273,7 +230,7 @@ async def research(
|
|
273
230
|
code_results: Dict = dict()
|
274
231
|
document_results: List[Dict[str, str]] = []
|
275
232
|
operator_results: OperatorRun = None
|
276
|
-
this_iteration = ResearchIteration(
|
233
|
+
this_iteration = ResearchIteration(query=query)
|
277
234
|
|
278
235
|
async for result in apick_next_tool(
|
279
236
|
query,
|
@@ -303,26 +260,30 @@ async def research(
|
|
303
260
|
logger.warning(f"Research mode: {this_iteration.warning}.")
|
304
261
|
|
305
262
|
# Terminate research if selected text tool or query, tool not set for next iteration
|
306
|
-
elif
|
263
|
+
elif (
|
264
|
+
not this_iteration.query
|
265
|
+
or isinstance(this_iteration.query, str)
|
266
|
+
or this_iteration.query.name == ConversationCommand.Text
|
267
|
+
):
|
307
268
|
current_iteration = MAX_ITERATIONS
|
308
269
|
|
309
|
-
elif this_iteration.
|
270
|
+
elif this_iteration.query.name == ConversationCommand.SemanticSearchFiles:
|
310
271
|
this_iteration.context = []
|
311
272
|
document_results = []
|
312
273
|
previous_inferred_queries = {
|
313
274
|
c["query"] for iteration in previous_iterations if iteration.context for c in iteration.context
|
314
275
|
}
|
315
276
|
async for result in search_documents(
|
316
|
-
this_iteration.query,
|
317
|
-
max_document_searches,
|
318
|
-
None,
|
319
|
-
user,
|
320
|
-
construct_tool_chat_history(previous_iterations, ConversationCommand.
|
321
|
-
conversation_id,
|
322
|
-
[ConversationCommand.Default],
|
323
|
-
location,
|
324
|
-
send_status_func,
|
325
|
-
query_images,
|
277
|
+
**this_iteration.query.args,
|
278
|
+
n=max_document_searches,
|
279
|
+
d=None,
|
280
|
+
user=user,
|
281
|
+
chat_history=construct_tool_chat_history(previous_iterations, ConversationCommand.SemanticSearchFiles),
|
282
|
+
conversation_id=conversation_id,
|
283
|
+
conversation_commands=[ConversationCommand.Default],
|
284
|
+
location_data=location,
|
285
|
+
send_status_func=send_status_func,
|
286
|
+
query_images=query_images,
|
326
287
|
previous_inferred_queries=previous_inferred_queries,
|
327
288
|
agent=agent,
|
328
289
|
tracer=tracer,
|
@@ -350,7 +311,7 @@ async def research(
|
|
350
311
|
else:
|
351
312
|
this_iteration.warning = "No matching document references found"
|
352
313
|
|
353
|
-
elif this_iteration.
|
314
|
+
elif this_iteration.query.name == ConversationCommand.SearchWeb:
|
354
315
|
previous_subqueries = {
|
355
316
|
subquery
|
356
317
|
for iteration in previous_iterations
|
@@ -359,12 +320,12 @@ async def research(
|
|
359
320
|
}
|
360
321
|
try:
|
361
322
|
async for result in search_online(
|
362
|
-
this_iteration.query,
|
363
|
-
construct_tool_chat_history(previous_iterations, ConversationCommand.Online),
|
364
|
-
location,
|
365
|
-
user,
|
366
|
-
send_status_func,
|
367
|
-
[],
|
323
|
+
**this_iteration.query.args,
|
324
|
+
conversation_history=construct_tool_chat_history(previous_iterations, ConversationCommand.Online),
|
325
|
+
location=location,
|
326
|
+
user=user,
|
327
|
+
send_status_func=send_status_func,
|
328
|
+
custom_filters=[],
|
368
329
|
max_online_searches=max_online_searches,
|
369
330
|
max_webpages_to_read=0,
|
370
331
|
query_images=query_images,
|
@@ -383,19 +344,15 @@ async def research(
|
|
383
344
|
this_iteration.warning = f"Error searching online: {e}"
|
384
345
|
logger.error(this_iteration.warning, exc_info=True)
|
385
346
|
|
386
|
-
elif this_iteration.
|
347
|
+
elif this_iteration.query.name == ConversationCommand.ReadWebpage:
|
387
348
|
try:
|
388
|
-
async for result in
|
389
|
-
this_iteration.query,
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
send_status_func,
|
394
|
-
max_webpages_to_read=max_webpages_to_read,
|
395
|
-
query_images=query_images,
|
349
|
+
async for result in read_webpages_content(
|
350
|
+
**this_iteration.query.args,
|
351
|
+
user=user,
|
352
|
+
send_status_func=send_status_func,
|
353
|
+
# max_webpages_to_read=max_webpages_to_read,
|
396
354
|
agent=agent,
|
397
355
|
tracer=tracer,
|
398
|
-
query_files=query_files,
|
399
356
|
):
|
400
357
|
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
401
358
|
yield result[ChatEvent.STATUS]
|
@@ -416,15 +373,15 @@ async def research(
|
|
416
373
|
this_iteration.warning = f"Error reading webpages: {e}"
|
417
374
|
logger.error(this_iteration.warning, exc_info=True)
|
418
375
|
|
419
|
-
elif this_iteration.
|
376
|
+
elif this_iteration.query.name == ConversationCommand.RunCode:
|
420
377
|
try:
|
421
378
|
async for result in run_code(
|
422
|
-
this_iteration.query,
|
423
|
-
construct_tool_chat_history(previous_iterations, ConversationCommand.Code),
|
424
|
-
"",
|
425
|
-
location,
|
426
|
-
user,
|
427
|
-
send_status_func,
|
379
|
+
**this_iteration.query.args,
|
380
|
+
conversation_history=construct_tool_chat_history(previous_iterations, ConversationCommand.Code),
|
381
|
+
context="",
|
382
|
+
location_data=location,
|
383
|
+
user=user,
|
384
|
+
send_status_func=send_status_func,
|
428
385
|
query_images=query_images,
|
429
386
|
agent=agent,
|
430
387
|
query_files=query_files,
|
@@ -441,14 +398,14 @@ async def research(
|
|
441
398
|
this_iteration.warning = f"Error running code: {e}"
|
442
399
|
logger.warning(this_iteration.warning, exc_info=True)
|
443
400
|
|
444
|
-
elif this_iteration.
|
401
|
+
elif this_iteration.query.name == ConversationCommand.OperateComputer:
|
445
402
|
try:
|
446
403
|
async for result in operate_environment(
|
447
|
-
this_iteration.query,
|
448
|
-
user,
|
449
|
-
construct_tool_chat_history(previous_iterations, ConversationCommand.Operator),
|
450
|
-
location,
|
451
|
-
previous_iterations[-1].operatorContext if previous_iterations else None,
|
404
|
+
**this_iteration.query.args,
|
405
|
+
user=user,
|
406
|
+
conversation_log=construct_tool_chat_history(previous_iterations, ConversationCommand.Operator),
|
407
|
+
location_data=location,
|
408
|
+
previous_trajectory=previous_iterations[-1].operatorContext if previous_iterations else None,
|
452
409
|
send_status_func=send_status_func,
|
453
410
|
query_images=query_images,
|
454
411
|
agent=agent,
|
@@ -474,6 +431,63 @@ async def research(
|
|
474
431
|
this_iteration.warning = f"Error operating browser: {e}"
|
475
432
|
logger.error(this_iteration.warning, exc_info=True)
|
476
433
|
|
434
|
+
elif this_iteration.query.name == ConversationCommand.ViewFile:
|
435
|
+
try:
|
436
|
+
async for result in view_file_content(
|
437
|
+
**this_iteration.query.args,
|
438
|
+
user=user,
|
439
|
+
):
|
440
|
+
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
441
|
+
yield result[ChatEvent.STATUS]
|
442
|
+
else:
|
443
|
+
if this_iteration.context is None:
|
444
|
+
this_iteration.context = []
|
445
|
+
document_results: List[Dict[str, str]] = result # type: ignore
|
446
|
+
this_iteration.context += document_results
|
447
|
+
async for result in send_status_func(f"**Viewed file**: {this_iteration.query.args['path']}"):
|
448
|
+
yield result
|
449
|
+
except Exception as e:
|
450
|
+
this_iteration.warning = f"Error viewing file: {e}"
|
451
|
+
logger.error(this_iteration.warning, exc_info=True)
|
452
|
+
|
453
|
+
elif this_iteration.query.name == ConversationCommand.ListFiles:
|
454
|
+
try:
|
455
|
+
async for result in list_files(
|
456
|
+
**this_iteration.query.args,
|
457
|
+
user=user,
|
458
|
+
):
|
459
|
+
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
460
|
+
yield result[ChatEvent.STATUS]
|
461
|
+
else:
|
462
|
+
if this_iteration.context is None:
|
463
|
+
this_iteration.context = []
|
464
|
+
document_results: List[Dict[str, str]] = [result] # type: ignore
|
465
|
+
this_iteration.context += document_results
|
466
|
+
async for result in send_status_func(result["query"]):
|
467
|
+
yield result
|
468
|
+
except Exception as e:
|
469
|
+
this_iteration.warning = f"Error listing files: {e}"
|
470
|
+
logger.error(this_iteration.warning, exc_info=True)
|
471
|
+
|
472
|
+
elif this_iteration.query.name == ConversationCommand.RegexSearchFiles:
|
473
|
+
try:
|
474
|
+
async for result in grep_files(
|
475
|
+
**this_iteration.query.args,
|
476
|
+
user=user,
|
477
|
+
):
|
478
|
+
if isinstance(result, dict) and ChatEvent.STATUS in result:
|
479
|
+
yield result[ChatEvent.STATUS]
|
480
|
+
else:
|
481
|
+
if this_iteration.context is None:
|
482
|
+
this_iteration.context = []
|
483
|
+
document_results: List[Dict[str, str]] = [result] # type: ignore
|
484
|
+
this_iteration.context += document_results
|
485
|
+
async for result in send_status_func(result["query"]):
|
486
|
+
yield result
|
487
|
+
except Exception as e:
|
488
|
+
this_iteration.warning = f"Error searching with regex: {e}"
|
489
|
+
logger.error(this_iteration.warning, exc_info=True)
|
490
|
+
|
477
491
|
else:
|
478
492
|
# No valid tools. This is our exit condition.
|
479
493
|
current_iteration = MAX_ITERATIONS
|
@@ -481,7 +495,7 @@ async def research(
|
|
481
495
|
current_iteration += 1
|
482
496
|
|
483
497
|
if document_results or online_results or code_results or operator_results or this_iteration.warning:
|
484
|
-
results_data = f"\n<
|
498
|
+
results_data = f"\n<iteration_{current_iteration}_results>"
|
485
499
|
if document_results:
|
486
500
|
results_data += f"\n<document_references>\n{yaml.dump(document_results, allow_unicode=True, sort_keys=False, default_flow_style=False)}\n</document_references>"
|
487
501
|
if online_results:
|
@@ -494,7 +508,7 @@ async def research(
|
|
494
508
|
)
|
495
509
|
if this_iteration.warning:
|
496
510
|
results_data += f"\n<warning>\n{this_iteration.warning}\n</warning>"
|
497
|
-
results_data += "\n</results>\n</
|
511
|
+
results_data += f"\n</results>\n</iteration_{current_iteration}_results>"
|
498
512
|
|
499
513
|
# intermediate_result = await extract_relevant_info(this_iteration.query, results_data, agent)
|
500
514
|
this_iteration.summarizedResult = results_data
|