alita-sdk 0.3.176__py3-none-any.whl → 0.3.177__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.
- alita_sdk/community/__init__.py +7 -17
- alita_sdk/tools/carrier/api_wrapper.py +6 -0
- alita_sdk/tools/carrier/backend_tests_tool.py +308 -7
- alita_sdk/tools/carrier/carrier_sdk.py +18 -0
- alita_sdk/tools/carrier/tools.py +2 -1
- {alita_sdk-0.3.176.dist-info → alita_sdk-0.3.177.dist-info}/METADATA +1 -2
- {alita_sdk-0.3.176.dist-info → alita_sdk-0.3.177.dist-info}/RECORD +10 -41
- alita_sdk/community/browseruse/__init__.py +0 -73
- alita_sdk/community/browseruse/api_wrapper.py +0 -288
- alita_sdk/community/deep_researcher/__init__.py +0 -70
- alita_sdk/community/deep_researcher/agents/__init__.py +0 -1
- alita_sdk/community/deep_researcher/agents/baseclass.py +0 -182
- alita_sdk/community/deep_researcher/agents/knowledge_gap_agent.py +0 -74
- alita_sdk/community/deep_researcher/agents/long_writer_agent.py +0 -251
- alita_sdk/community/deep_researcher/agents/planner_agent.py +0 -124
- alita_sdk/community/deep_researcher/agents/proofreader_agent.py +0 -80
- alita_sdk/community/deep_researcher/agents/thinking_agent.py +0 -64
- alita_sdk/community/deep_researcher/agents/tool_agents/__init__.py +0 -20
- alita_sdk/community/deep_researcher/agents/tool_agents/crawl_agent.py +0 -87
- alita_sdk/community/deep_researcher/agents/tool_agents/search_agent.py +0 -96
- alita_sdk/community/deep_researcher/agents/tool_selector_agent.py +0 -83
- alita_sdk/community/deep_researcher/agents/utils/__init__.py +0 -0
- alita_sdk/community/deep_researcher/agents/utils/parse_output.py +0 -148
- alita_sdk/community/deep_researcher/agents/writer_agent.py +0 -63
- alita_sdk/community/deep_researcher/api_wrapper.py +0 -116
- alita_sdk/community/deep_researcher/deep_research.py +0 -185
- alita_sdk/community/deep_researcher/examples/deep_example.py +0 -30
- alita_sdk/community/deep_researcher/examples/iterative_example.py +0 -34
- alita_sdk/community/deep_researcher/examples/report_plan_example.py +0 -27
- alita_sdk/community/deep_researcher/iterative_research.py +0 -419
- alita_sdk/community/deep_researcher/llm_config.py +0 -87
- alita_sdk/community/deep_researcher/main.py +0 -67
- alita_sdk/community/deep_researcher/tools/__init__.py +0 -2
- alita_sdk/community/deep_researcher/tools/crawl_website.py +0 -109
- alita_sdk/community/deep_researcher/tools/web_search.py +0 -294
- alita_sdk/community/deep_researcher/utils/__init__.py +0 -0
- alita_sdk/community/deep_researcher/utils/md_to_pdf.py +0 -8
- alita_sdk/community/deep_researcher/utils/os.py +0 -21
- {alita_sdk-0.3.176.dist-info → alita_sdk-0.3.177.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.176.dist-info → alita_sdk-0.3.177.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.176.dist-info → alita_sdk-0.3.177.dist-info}/top_level.txt +0 -0
@@ -1,288 +0,0 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
from typing import Dict, List, Any, Optional, Type
|
3
|
-
from pydantic import BaseModel, Field
|
4
|
-
from browser_use import Agent, ActionResult, Browser, BrowserConfig, BrowserContextConfig
|
5
|
-
from browser_use.agent.views import AgentHistoryList
|
6
|
-
from playwright._impl._api_structures import ProxySettings
|
7
|
-
from alita_sdk.tools.elitea_base import BaseToolApiWrapper
|
8
|
-
from pydantic import create_model, Field, model_validator
|
9
|
-
from tempfile import TemporaryDirectory, NamedTemporaryFile
|
10
|
-
from browser_use.controller.service import Controller
|
11
|
-
from langchain_core.callbacks import dispatch_custom_event
|
12
|
-
from pyobjtojson import obj_to_json
|
13
|
-
import os
|
14
|
-
import asyncio
|
15
|
-
|
16
|
-
import socket
|
17
|
-
from browser_use.browser.utils.screen_resolution import get_screen_resolution, get_window_adjustments
|
18
|
-
from playwright.async_api import Playwright, Browser as PlaywrightBrowser
|
19
|
-
from browser_use.browser.chrome import (
|
20
|
-
CHROME_ARGS,
|
21
|
-
CHROME_DEBUG_PORT,
|
22
|
-
CHROME_DETERMINISTIC_RENDERING_ARGS,
|
23
|
-
CHROME_DISABLE_SECURITY_ARGS,
|
24
|
-
CHROME_DOCKER_ARGS,
|
25
|
-
CHROME_HEADLESS_ARGS,
|
26
|
-
)
|
27
|
-
IN_DOCKER = os.environ.get('IN_DOCKER', 'false').lower()[0] in 'ty1'
|
28
|
-
|
29
|
-
class BrowserEx(Browser):
|
30
|
-
def __init__(self, config: BrowserConfig):
|
31
|
-
super().__init__(config)
|
32
|
-
self.config = config
|
33
|
-
|
34
|
-
async def _setup_builtin_browser(self, playwright: Playwright) -> PlaywrightBrowser:
|
35
|
-
"""Sets up and returns a Playwright Browser instance with anti-detection measures."""
|
36
|
-
assert self.config.browser_binary_path is None, 'browser_binary_path should be None if trying to use the builtin browsers'
|
37
|
-
|
38
|
-
# Use the configured window size from new_context_config if available
|
39
|
-
if (
|
40
|
-
not self.config.headless
|
41
|
-
and hasattr(self.config, 'new_context_config')
|
42
|
-
and hasattr(self.config.new_context_config, 'browser_window_size')
|
43
|
-
):
|
44
|
-
screen_size = self.config.new_context_config.browser_window_size.model_dump()
|
45
|
-
offset_x, offset_y = get_window_adjustments()
|
46
|
-
elif self.config.headless:
|
47
|
-
screen_size = {'width': 1920, 'height': 1080}
|
48
|
-
offset_x, offset_y = 0, 0
|
49
|
-
else:
|
50
|
-
screen_size = get_screen_resolution()
|
51
|
-
offset_x, offset_y = get_window_adjustments()
|
52
|
-
|
53
|
-
chrome_args = {
|
54
|
-
f'--remote-debugging-port={self.config.chrome_remote_debugging_port}',
|
55
|
-
*CHROME_ARGS,
|
56
|
-
*(CHROME_DOCKER_ARGS if IN_DOCKER else []),
|
57
|
-
*(CHROME_HEADLESS_ARGS if self.config.headless else []),
|
58
|
-
*(CHROME_DISABLE_SECURITY_ARGS if self.config.disable_security else []),
|
59
|
-
*(CHROME_DETERMINISTIC_RENDERING_ARGS if self.config.deterministic_rendering else []),
|
60
|
-
f'--window-position={offset_x},{offset_y}',
|
61
|
-
f'--window-size={screen_size["width"]},{screen_size["height"]}',
|
62
|
-
*self.config.extra_browser_args,
|
63
|
-
}
|
64
|
-
|
65
|
-
# check if chrome remote debugging port is already taken,
|
66
|
-
# if so remove the remote-debugging-port arg to prevent conflicts
|
67
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
68
|
-
if s.connect_ex(('localhost', self.config.chrome_remote_debugging_port)) == 0:
|
69
|
-
chrome_args.remove(f'--remote-debugging-port={self.config.chrome_remote_debugging_port}')
|
70
|
-
|
71
|
-
browser_class = getattr(playwright, self.config.browser_class)
|
72
|
-
args = {
|
73
|
-
'chromium': list(chrome_args),
|
74
|
-
'firefox': [
|
75
|
-
*{
|
76
|
-
'-no-remote',
|
77
|
-
*self.config.extra_browser_args,
|
78
|
-
}
|
79
|
-
],
|
80
|
-
'webkit': [
|
81
|
-
*{
|
82
|
-
'--no-startup-window',
|
83
|
-
*self.config.extra_browser_args,
|
84
|
-
}
|
85
|
-
],
|
86
|
-
}
|
87
|
-
|
88
|
-
browser = await browser_class.launch(
|
89
|
-
headless=self.config.headless,
|
90
|
-
channel='chromium',
|
91
|
-
args=args[self.config.browser_class],
|
92
|
-
proxy=self.config.proxy.model_dump() if self.config.proxy else None,
|
93
|
-
handle_sigterm=False,
|
94
|
-
handle_sigint=False,
|
95
|
-
)
|
96
|
-
return browser
|
97
|
-
|
98
|
-
|
99
|
-
BrowserTask = create_model(
|
100
|
-
"BrowserTask",
|
101
|
-
task=(str, Field(description="Task to perform")),
|
102
|
-
max_steps=(Optional[int], Field(description="Maximum number of steps to perform")),
|
103
|
-
debug=(Optional[bool], Field(description="Whether debug mode is enabled")),
|
104
|
-
__config__=Field(description="Browser Use API Wrapper")
|
105
|
-
)
|
106
|
-
|
107
|
-
BrowserTasks = create_model(
|
108
|
-
"BrowserTasks",
|
109
|
-
tasks=(List[str], Field(description="List of tasks to perform")),
|
110
|
-
max_steps=(Optional[int], Field(description="Maximum number of steps to perform")),
|
111
|
-
debug=(Optional[bool], Field(description="Whether debug mode is enabled")),
|
112
|
-
__config__=Field(description="Browser Use API Wrapper")
|
113
|
-
)
|
114
|
-
|
115
|
-
async def thinking_processor(agent):
|
116
|
-
"""Hook to be called after each step."""
|
117
|
-
if hasattr(agent, "state"):
|
118
|
-
history = agent.state.history
|
119
|
-
else:
|
120
|
-
history = None
|
121
|
-
return
|
122
|
-
|
123
|
-
# Process model thoughts
|
124
|
-
model_thoughts = obj_to_json(
|
125
|
-
obj=history.model_thoughts(),
|
126
|
-
check_circular=False
|
127
|
-
)
|
128
|
-
if len(model_thoughts) > 0:
|
129
|
-
model_thoughts_last_elem = model_thoughts[-1]
|
130
|
-
evalualtion = model_thoughts_last_elem.get('evaluation_previous_goal')
|
131
|
-
memory = model_thoughts_last_elem.get('memory')
|
132
|
-
next_goal = model_thoughts_last_elem.get('next_goal')
|
133
|
-
dispatch_custom_event(
|
134
|
-
name="thinking_step",
|
135
|
-
data={
|
136
|
-
"message": f"**Memory** : \n\n{memory}\n\n**Evaluation goal**:\n\n{evalualtion}\n\n**Next goal**:\n\n{next_goal}",
|
137
|
-
"tool_name": "task",
|
138
|
-
"toolkit": "browser_use"
|
139
|
-
}
|
140
|
-
)
|
141
|
-
|
142
|
-
|
143
|
-
class DoneResult(BaseModel):
|
144
|
-
title: str
|
145
|
-
comments: str
|
146
|
-
hours_since_start: int
|
147
|
-
|
148
|
-
gif_default_location = './agent_history.gif'
|
149
|
-
default_bucket = 'browseruse'
|
150
|
-
|
151
|
-
class BrowserUseAPIWrapper(BaseToolApiWrapper):
|
152
|
-
"""Wrapper for Browser Use API."""
|
153
|
-
headless: bool = True
|
154
|
-
width: int = 1280
|
155
|
-
height: int = 800
|
156
|
-
use_vision: bool = False
|
157
|
-
trace_actions: bool = False
|
158
|
-
trace_actions_path: Optional[str] = None
|
159
|
-
cookies: Optional[Dict[str, Any]] = None
|
160
|
-
disable_security: bool = True
|
161
|
-
proxy: Any = None
|
162
|
-
extra_chromium_args: List[str] = []
|
163
|
-
client: Any = None # AlitaClient
|
164
|
-
artifact: Any = None # Artifact
|
165
|
-
llm: Any = None # LLMLikeObject
|
166
|
-
bucket: str = None
|
167
|
-
proxy_settings: Any = None
|
168
|
-
validate_output: bool = False
|
169
|
-
planner_llm: Any = None
|
170
|
-
browser_window_size: Dict[str, int] = None
|
171
|
-
|
172
|
-
|
173
|
-
@model_validator(mode='before')
|
174
|
-
@classmethod
|
175
|
-
def validate_toolkit(cls, values):
|
176
|
-
"""Validate toolkit parameters."""
|
177
|
-
values['proxy'] = ProxySettings(**values['proxy']) if values.get('proxy') else None
|
178
|
-
values['extra_chromium_args'] = values.get('extra_chromium_args') or []
|
179
|
-
values['browser_window_size'] = {"width": values.get('width', 1280), "height": values.get('height', 800)}
|
180
|
-
values['artifact'] = values.get('client').artifact(values.get('bucket', default_bucket))
|
181
|
-
return values
|
182
|
-
|
183
|
-
|
184
|
-
def _create_browser(self):
|
185
|
-
cookies_file = None
|
186
|
-
if self.cookies:
|
187
|
-
cookies_file = NamedTemporaryFile(delete=False)
|
188
|
-
cookies_file.write(self.cookies)
|
189
|
-
cookies_file.close()
|
190
|
-
|
191
|
-
context_config = BrowserContextConfig(
|
192
|
-
cookies_file=cookies_file,
|
193
|
-
wait_for_network_idle_page_load_time=10.0, # TODO: Make this configurable
|
194
|
-
highlight_elements=True,
|
195
|
-
browser_window_size=self.browser_window_size
|
196
|
-
)
|
197
|
-
browser_config = BrowserConfig(
|
198
|
-
headless=self.headless,
|
199
|
-
browser_class='chromium', # TODO: Make this configurable
|
200
|
-
disable_security=self.disable_security,
|
201
|
-
extra_chromium_args=self.extra_chromium_args,
|
202
|
-
proxy=self.proxy,
|
203
|
-
new_context_config=context_config
|
204
|
-
)
|
205
|
-
return BrowserEx(config=browser_config)
|
206
|
-
|
207
|
-
def task(self, task: str, max_steps: Optional[int] = 20, debug: Optional[bool] = False):
|
208
|
-
"""Perform a task using the browser."""
|
209
|
-
return asyncio.run(self._tasks([task], max_steps, debug))
|
210
|
-
|
211
|
-
async def _tasks(self, tasks: List[str], max_steps: Optional[int] = 20, debug: Optional[bool] = False):
|
212
|
-
browser = self._create_browser()
|
213
|
-
context_config = BrowserContextConfig(
|
214
|
-
wait_for_network_idle_page_load_time=10.0, # TODO: Make this configurable
|
215
|
-
highlight_elements=True,
|
216
|
-
browser_window_size=self.browser_window_size
|
217
|
-
)
|
218
|
-
async with await browser.new_context(context_config) as browser:
|
219
|
-
start = tasks[0]
|
220
|
-
if len(tasks) == 1:
|
221
|
-
tasks = []
|
222
|
-
agent = Agent(
|
223
|
-
task=start,
|
224
|
-
llm=self.llm,
|
225
|
-
browser_context=browser,
|
226
|
-
max_actions_per_step=20,
|
227
|
-
use_vision=self.use_vision,
|
228
|
-
save_conversation_path=None,
|
229
|
-
generate_gif=True,
|
230
|
-
planner_llm=self.planner_llm,
|
231
|
-
controller=Controller(),
|
232
|
-
message_context = "Carefully check every step, and make sure to provide detailed feedback on the results.",
|
233
|
-
validate_output=self.validate_output
|
234
|
-
)
|
235
|
-
for task in tasks:
|
236
|
-
agent.add_new_task(task)
|
237
|
-
history: AgentHistoryList = await agent.run(
|
238
|
-
max_steps=max_steps,
|
239
|
-
on_step_end=thinking_processor
|
240
|
-
)
|
241
|
-
await browser.close()
|
242
|
-
files = self._save_execution(history.model_dump_json())
|
243
|
-
|
244
|
-
return {
|
245
|
-
"run_data": str(history.extracted_content()),
|
246
|
-
"files": files
|
247
|
-
}
|
248
|
-
|
249
|
-
def _save_execution(self, data_content: Any):
|
250
|
-
"""Saves tasks execution gif"""
|
251
|
-
|
252
|
-
try:
|
253
|
-
with open(gif_default_location, 'rb') as file:
|
254
|
-
artifact_data = file.read()
|
255
|
-
except FileNotFoundError:
|
256
|
-
artifact_data = None
|
257
|
-
|
258
|
-
filename = f"tasks_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
259
|
-
files = []
|
260
|
-
if data_content:
|
261
|
-
self.artifact.create(f'{filename}.json', data_content)
|
262
|
-
files.append(f'{filename}.json')
|
263
|
-
|
264
|
-
if artifact_data:
|
265
|
-
self.artifact.create(f'{filename}.gif', artifact_data)
|
266
|
-
files.append(f'{filename}.gif')
|
267
|
-
return files
|
268
|
-
|
269
|
-
|
270
|
-
def tasks(self, tasks: List[str], max_steps: Optional[int] = 20, debug: Optional[bool] = False):
|
271
|
-
"""Perform a list of tasks using the browser."""
|
272
|
-
return asyncio.run(self._tasks(tasks, max_steps, debug))
|
273
|
-
|
274
|
-
def get_available_tools(self):
|
275
|
-
return [
|
276
|
-
{
|
277
|
-
"name": "task",
|
278
|
-
"description": self.task.__doc__,
|
279
|
-
"args_schema": BrowserTask,
|
280
|
-
"ref": self.task
|
281
|
-
},
|
282
|
-
{
|
283
|
-
"name": "tasks",
|
284
|
-
"description": self.tasks.__doc__,
|
285
|
-
"args_schema": BrowserTasks,
|
286
|
-
"ref": self.tasks
|
287
|
-
}
|
288
|
-
]
|
@@ -1,70 +0,0 @@
|
|
1
|
-
from .deep_research import DeepResearcher
|
2
|
-
from .iterative_research import IterativeResearcher
|
3
|
-
from .agents.baseclass import ResearchRunner
|
4
|
-
from .llm_config import LLMConfig
|
5
|
-
|
6
|
-
__all__ = ["DeepResearcher", "IterativeResearcher", "ResearchRunner", "LLMConfig"]
|
7
|
-
|
8
|
-
from typing import Any, List, Literal, Optional
|
9
|
-
|
10
|
-
from langchain_core.tools import BaseToolkit, BaseTool
|
11
|
-
from pydantic import BaseModel, ConfigDict, create_model, Field
|
12
|
-
|
13
|
-
from .api_wrapper import DeepResearcherWrapper
|
14
|
-
from ..base.tool import BaseAction
|
15
|
-
from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
|
16
|
-
|
17
|
-
name = "deep_researcher"
|
18
|
-
|
19
|
-
def get_tools(tool):
|
20
|
-
return DeepResearcherToolkit().get_toolkit(
|
21
|
-
selected_tools=tool['settings'].get('selected_tools', []),
|
22
|
-
max_iterations=tool['settings'].get('max_iterations', 5),
|
23
|
-
max_time_minutes=tool['settings'].get('max_time_minutes', 10),
|
24
|
-
verbose=tool['settings'].get('verbose', False),
|
25
|
-
tracing=tool['settings'].get('tracing', False),
|
26
|
-
alita=tool['settings'].get('alita', None),
|
27
|
-
llm=tool['settings'].get('llm', None),
|
28
|
-
toolkit_name=tool.get('toolkit_name')
|
29
|
-
).get_tools()
|
30
|
-
|
31
|
-
|
32
|
-
class DeepResearcherToolkit(BaseToolkit):
|
33
|
-
tools: List[BaseTool] = []
|
34
|
-
toolkit_max_length: int = 0
|
35
|
-
|
36
|
-
@staticmethod
|
37
|
-
def toolkit_config_schema() -> BaseModel:
|
38
|
-
selected_tools = {x['name']: x['args_schema'].schema() for x in DeepResearcherWrapper.model_construct().get_available_tools()}
|
39
|
-
DeepResearcherToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
|
40
|
-
return create_model(
|
41
|
-
name,
|
42
|
-
max_iterations=(int, Field(default=5, title="Max iterations", description="Maximum number of iterations for research", json_schema_extra={'toolkit_name': True, 'max_toolkit_length': DeepResearcherToolkit.toolkit_max_length})),
|
43
|
-
max_time_minutes=(int, Field(default=10, title="Max time (minutes)", description="Maximum time in minutes for research")),
|
44
|
-
verbose=(bool, Field(default=False, title="Verbose", description="Print status updates to the console")),
|
45
|
-
tracing=(bool, Field(default=False, title="Tracing", description="Enable tracing (only for OpenAI models)")),
|
46
|
-
selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
|
47
|
-
__config__=ConfigDict(json_schema_extra={'metadata': {"label": "Deep Researcher", "icon_url": "research-icon.svg"}})
|
48
|
-
)
|
49
|
-
|
50
|
-
@classmethod
|
51
|
-
def get_toolkit(cls, selected_tools: list[str] | None = None, toolkit_name: Optional[str] = None, **kwargs):
|
52
|
-
if selected_tools is None:
|
53
|
-
selected_tools = []
|
54
|
-
deep_researcher_api_wrapper = DeepResearcherWrapper(**kwargs)
|
55
|
-
prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
|
56
|
-
available_tools = deep_researcher_api_wrapper.get_available_tools()
|
57
|
-
tools = []
|
58
|
-
for tool in available_tools:
|
59
|
-
if selected_tools and tool["name"] not in selected_tools:
|
60
|
-
continue
|
61
|
-
tools.append(BaseAction(
|
62
|
-
api_wrapper=deep_researcher_api_wrapper,
|
63
|
-
name=prefix + tool["name"],
|
64
|
-
description=tool["description"],
|
65
|
-
args_schema=tool["args_schema"]
|
66
|
-
))
|
67
|
-
return cls(tools=tools)
|
68
|
-
|
69
|
-
def get_tools(self):
|
70
|
-
return self.tools
|
@@ -1 +0,0 @@
|
|
1
|
-
from .baseclass import ResearchAgent, ResearchRunner
|
@@ -1,182 +0,0 @@
|
|
1
|
-
from typing import Any, Callable, Optional, List, Dict, Union, TypeVar, Generic, Type
|
2
|
-
from pydantic import BaseModel
|
3
|
-
import asyncio
|
4
|
-
import json
|
5
|
-
|
6
|
-
# LangChain imports
|
7
|
-
from langchain_core.tools import BaseTool
|
8
|
-
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, BaseMessage
|
9
|
-
from langchain_core.prompts import ChatPromptTemplate
|
10
|
-
from langchain.agents import AgentExecutor
|
11
|
-
from langchain_core.runnables import RunnablePassthrough
|
12
|
-
from langchain.agents.format_scratchpad import format_to_openai_functions
|
13
|
-
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
|
14
|
-
from langchain_core.runnables.base import RunnableSerializable
|
15
|
-
|
16
|
-
# Type variable for parameterizing the output type
|
17
|
-
TContext = TypeVar("TContext")
|
18
|
-
|
19
|
-
class ResearchRunner:
|
20
|
-
"""
|
21
|
-
LangChain-based runner for research agents that supports both structured output
|
22
|
-
and custom output parsing.
|
23
|
-
"""
|
24
|
-
|
25
|
-
@classmethod
|
26
|
-
async def run(cls, agent, user_message: str, **kwargs) -> 'RunResult':
|
27
|
-
"""
|
28
|
-
Run the agent with the given user message and return the result.
|
29
|
-
|
30
|
-
Args:
|
31
|
-
agent: The agent to run
|
32
|
-
user_message: The user message to send to the agent
|
33
|
-
|
34
|
-
Returns:
|
35
|
-
A RunResult containing the final output
|
36
|
-
"""
|
37
|
-
if not isinstance(agent, ResearchAgent):
|
38
|
-
raise TypeError("Agent must be a ResearchAgent")
|
39
|
-
|
40
|
-
result = await agent.arun(user_message)
|
41
|
-
return RunResult(final_output=result)
|
42
|
-
|
43
|
-
class RunResult:
|
44
|
-
"""
|
45
|
-
A simple class to maintain compatibility with the previous API
|
46
|
-
while using LangChain agents under the hood.
|
47
|
-
"""
|
48
|
-
|
49
|
-
def __init__(self, final_output: Any):
|
50
|
-
self.final_output = final_output
|
51
|
-
|
52
|
-
def final_output_as(self, output_type: Type[Any]) -> Any:
|
53
|
-
"""
|
54
|
-
Convert the final output to the specified type.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
output_type: The type to convert to
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
An instance of output_type
|
61
|
-
"""
|
62
|
-
if isinstance(self.final_output, output_type):
|
63
|
-
return self.final_output
|
64
|
-
|
65
|
-
if isinstance(self.final_output, str):
|
66
|
-
try:
|
67
|
-
# Try to parse as JSON if it's a string
|
68
|
-
parsed = json.loads(self.final_output)
|
69
|
-
return output_type(**parsed)
|
70
|
-
except Exception:
|
71
|
-
# If that fails, try to parse the string for JSON
|
72
|
-
try:
|
73
|
-
# Look for JSON-like content in the string
|
74
|
-
import re
|
75
|
-
json_match = re.search(r'```json\n(.*?)\n```', self.final_output, re.DOTALL)
|
76
|
-
if json_match:
|
77
|
-
json_str = json_match.group(1)
|
78
|
-
parsed = json.loads(json_str)
|
79
|
-
return output_type(**parsed)
|
80
|
-
except Exception:
|
81
|
-
pass
|
82
|
-
|
83
|
-
# If all else fails, try to initialize with the entire output as a string
|
84
|
-
try:
|
85
|
-
if hasattr(output_type, "model_validate"):
|
86
|
-
return output_type.model_validate({"output": self.final_output})
|
87
|
-
else:
|
88
|
-
return output_type(output=self.final_output)
|
89
|
-
except Exception as e:
|
90
|
-
raise ValueError(f"Could not convert output to {output_type.__name__}: {e}")
|
91
|
-
|
92
|
-
class ResearchAgent(Generic[TContext]):
|
93
|
-
"""
|
94
|
-
LangChain-based agent for research tasks that supports both structured output
|
95
|
-
and custom output parsing.
|
96
|
-
"""
|
97
|
-
|
98
|
-
def __init__(
|
99
|
-
self,
|
100
|
-
name: str,
|
101
|
-
instructions: str,
|
102
|
-
tools: List[BaseTool],
|
103
|
-
model: Any,
|
104
|
-
output_type: Optional[Type[BaseModel]] = None,
|
105
|
-
output_parser: Optional[Callable[[str], Any]] = None
|
106
|
-
):
|
107
|
-
self.name = name
|
108
|
-
self.instructions = instructions
|
109
|
-
self.tools = tools
|
110
|
-
self.model = model
|
111
|
-
self.output_type = output_type
|
112
|
-
self.output_parser = output_parser
|
113
|
-
|
114
|
-
# Create the LangChain agent
|
115
|
-
self.agent = self._create_agent()
|
116
|
-
|
117
|
-
def _create_agent(self) -> RunnableSerializable:
|
118
|
-
"""
|
119
|
-
Create a LangChain agent with the specified configuration.
|
120
|
-
"""
|
121
|
-
# Create the system prompt
|
122
|
-
system_prompt = self.instructions
|
123
|
-
|
124
|
-
# Create the prompt template
|
125
|
-
prompt = ChatPromptTemplate.from_messages([
|
126
|
-
("system", system_prompt),
|
127
|
-
("human", "{input}"),
|
128
|
-
("ai", "{agent_scratchpad}")
|
129
|
-
])
|
130
|
-
|
131
|
-
# Create the LangChain agent
|
132
|
-
agent = (
|
133
|
-
{
|
134
|
-
"input": RunnablePassthrough(),
|
135
|
-
"agent_scratchpad": lambda x: format_to_openai_functions(x["intermediate_steps"])
|
136
|
-
}
|
137
|
-
| prompt
|
138
|
-
| self.model
|
139
|
-
| OpenAIFunctionsAgentOutputParser()
|
140
|
-
)
|
141
|
-
|
142
|
-
# Create the agent executor
|
143
|
-
return AgentExecutor(
|
144
|
-
agent=agent,
|
145
|
-
tools=self.tools,
|
146
|
-
verbose=True,
|
147
|
-
handle_parsing_errors=True
|
148
|
-
)
|
149
|
-
|
150
|
-
async def arun(self, user_input: str) -> Any:
|
151
|
-
"""
|
152
|
-
Run the agent asynchronously with the given user input.
|
153
|
-
|
154
|
-
Args:
|
155
|
-
user_input: The user input to send to the agent
|
156
|
-
|
157
|
-
Returns:
|
158
|
-
The agent's output
|
159
|
-
"""
|
160
|
-
try:
|
161
|
-
# Run the agent
|
162
|
-
result = await self.agent.ainvoke({"input": user_input, "intermediate_steps": []})
|
163
|
-
output = result.get("output", "")
|
164
|
-
|
165
|
-
# Apply output parser if specified
|
166
|
-
if self.output_parser is not None:
|
167
|
-
return self.output_parser(output)
|
168
|
-
|
169
|
-
# Try to convert to output_type if specified
|
170
|
-
if self.output_type is not None:
|
171
|
-
try:
|
172
|
-
return self.output_type.model_validate_json(output)
|
173
|
-
except Exception:
|
174
|
-
try:
|
175
|
-
return self.output_type.model_validate({"output": output})
|
176
|
-
except Exception:
|
177
|
-
pass
|
178
|
-
|
179
|
-
# Otherwise return the raw output
|
180
|
-
return output
|
181
|
-
except Exception as e:
|
182
|
-
return f"Error: {str(e)}"
|
@@ -1,74 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Agent used to evaluate the state of the research report (typically done in a loop) and identify knowledge gaps that still
|
3
|
-
need to be addressed.
|
4
|
-
|
5
|
-
The Agent takes as input a string in the following format:
|
6
|
-
===========================================================
|
7
|
-
ORIGINAL QUERY: <original user query>
|
8
|
-
|
9
|
-
HISTORY OF ACTIONS, FINDINGS AND THOUGHTS: <breakdown of activities and findings carried out so far>
|
10
|
-
===========================================================
|
11
|
-
|
12
|
-
The Agent then:
|
13
|
-
1. Carefully reviews the current draft and assesses its completeness in answering the original query
|
14
|
-
2. Identifies specific knowledge gaps that still exist and need to be filled
|
15
|
-
3. Returns a KnowledgeGapOutput object
|
16
|
-
"""
|
17
|
-
|
18
|
-
from pydantic import BaseModel, Field
|
19
|
-
from typing import List, Optional, Any
|
20
|
-
from langchain_core.tools import BaseTool
|
21
|
-
from .baseclass import ResearchAgent
|
22
|
-
from ..llm_config import LLMConfig, model_supports_structured_output
|
23
|
-
from datetime import datetime
|
24
|
-
from .utils.parse_output import create_type_parser
|
25
|
-
|
26
|
-
class KnowledgeGapOutput(BaseModel):
|
27
|
-
"""Output from the Knowledge Gap Agent"""
|
28
|
-
research_complete: bool = Field(description="Whether the research and findings are complete enough to end the research loop")
|
29
|
-
outstanding_gaps: List[str] = Field(description="List of knowledge gaps that still need to be addressed")
|
30
|
-
|
31
|
-
|
32
|
-
INSTRUCTIONS = f"""
|
33
|
-
You are a Research State Evaluator. Today's date is {datetime.now().strftime("%Y-%m-%d")}.
|
34
|
-
Your job is to critically analyze the current state of a research report,
|
35
|
-
identify what knowledge gaps still exist and determine the best next step to take.
|
36
|
-
|
37
|
-
You will be given:
|
38
|
-
1. The original user query and any relevant background context to the query
|
39
|
-
2. A full history of the tasks, actions, findings and thoughts you've made up until this point in the research process
|
40
|
-
|
41
|
-
Your task is to:
|
42
|
-
1. Carefully review the findings and thoughts, particularly from the latest iteration, and assess their completeness in answering the original query
|
43
|
-
2. Determine if the findings are sufficiently complete to end the research loop
|
44
|
-
3. If not, identify up to 3 knowledge gaps that need to be addressed in sequence in order to continue with research - these should be relevant to the original query
|
45
|
-
|
46
|
-
Be specific in the gaps you identify and include relevant information as this will be passed onto another agent to process without additional context.
|
47
|
-
|
48
|
-
Only output JSON. Follow the JSON schema below. Do not output anything else. I will be parsing this with Pydantic so output valid JSON only:
|
49
|
-
{KnowledgeGapOutput.model_json_schema()}
|
50
|
-
"""
|
51
|
-
|
52
|
-
def init_knowledge_gap_agent(config: LLMConfig) -> ResearchAgent:
|
53
|
-
"""
|
54
|
-
Initialize the knowledge gap agent.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
config: The LLM configuration to use
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
A ResearchAgent that can evaluate knowledge gaps
|
61
|
-
"""
|
62
|
-
selected_model = config.fast_model
|
63
|
-
|
64
|
-
# Determine whether to use structured output based on if we have a Langchain LLM
|
65
|
-
use_output_parser = not hasattr(selected_model, 'langchain_llm')
|
66
|
-
|
67
|
-
return ResearchAgent(
|
68
|
-
name="KnowledgeGapAgent",
|
69
|
-
instructions=INSTRUCTIONS,
|
70
|
-
tools=[], # No tools needed for this agent
|
71
|
-
model=selected_model.langchain_llm if hasattr(selected_model, 'langchain_llm') else selected_model,
|
72
|
-
output_type=KnowledgeGapOutput if not use_output_parser else None,
|
73
|
-
output_parser=create_type_parser(KnowledgeGapOutput) if use_output_parser else None
|
74
|
-
)
|