Skip to content

Commit e14dc10

Browse files
Merge branch 'main' into fix/logger
2 parents 7208d40 + 3352ee3 commit e14dc10

27 files changed

Lines changed: 490 additions & 171 deletions

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ body:
1212
id: version
1313
attributes:
1414
label: What version of eigent are you using?
15-
placeholder: E.g., 0.0.72
15+
placeholder: E.g., 0.0.73
1616
validations:
1717
required: true
1818

.github/workflows/codeql.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# For most projects, this workflow file will not need changing; you simply need
2+
# to commit it to your repository.
3+
#
4+
# You may wish to alter this file to override the set of languages analyzed,
5+
# or to provide custom queries or build logic.
6+
#
7+
# ******** NOTE ********
8+
# We have attempted to detect the languages in your repository. Please check
9+
# the `language` matrix defined below to confirm you have the correct set of
10+
# supported CodeQL languages.
11+
#
12+
name: "CodeQL Advanced"
13+
14+
on:
15+
push:
16+
branches: [ "main" ]
17+
pull_request:
18+
branches: [ "main" ]
19+
schedule:
20+
- cron: '42 22 * * 5'
21+
22+
jobs:
23+
analyze:
24+
name: Analyze (${{ matrix.language }})
25+
# Runner size impacts CodeQL analysis time. To learn more, please see:
26+
# - https://gh.io/recommended-hardware-resources-for-running-codeql
27+
# - https://gh.io/supported-runners-and-hardware-resources
28+
# - https://gh.io/using-larger-runners (GitHub.com only)
29+
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
30+
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31+
permissions:
32+
# required for all workflows
33+
security-events: write
34+
35+
# required to fetch internal or private CodeQL packs
36+
packages: read
37+
38+
# only required for workflows in private repositories
39+
actions: read
40+
contents: read
41+
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
include:
46+
- language: actions
47+
build-mode: none
48+
- language: javascript-typescript
49+
build-mode: none
50+
- language: python
51+
build-mode: none
52+
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
53+
# Use `c-cpp` to analyze code written in C, C++ or both
54+
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
55+
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
56+
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
57+
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
58+
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
59+
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
60+
steps:
61+
- name: Checkout repository
62+
uses: actions/checkout@v4
63+
64+
# Add any setup steps before running the `github/codeql-action/init` action.
65+
# This includes steps like installing compilers or runtimes (`actions/setup-node`
66+
# or others). This is typically only required for manual builds.
67+
# - name: Setup runtime (example)
68+
# uses: actions/setup-example@v1
69+
70+
# Initializes the CodeQL tools for scanning.
71+
- name: Initialize CodeQL
72+
uses: github/codeql-action/init@v4
73+
with:
74+
languages: ${{ matrix.language }}
75+
build-mode: ${{ matrix.build-mode }}
76+
# If you wish to specify custom queries, you can do so here or in a config file.
77+
# By default, queries listed here will override any specified in a config file.
78+
# Prefix the list here with "+" to use these queries and those in the config file.
79+
80+
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
81+
# queries: security-extended,security-and-quality
82+
83+
# If the analyze step fails for one of the languages you are analyzing with
84+
# "We were unable to automatically build your code", modify the matrix above
85+
# to set the build mode to "manual" for that language. Then modify this step
86+
# to build your code.
87+
# ℹ️ Command-line programs to run using the OS shell.
88+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
89+
- name: Run manual build steps
90+
if: matrix.build-mode == 'manual'
91+
shell: bash
92+
run: |
93+
echo 'If you are using a "manual" build mode for one or more of the' \
94+
'languages you are analyzing, replace this with the commands to build' \
95+
'your code, for example:'
96+
echo ' make bootstrap'
97+
echo ' make release'
98+
exit 1
99+
100+
- name: Perform CodeQL Analysis
101+
uses: github/codeql-action/analyze@v4
102+
with:
103+
category: "/language:${{matrix.language}}"

backend/app/component/model_validation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ def create_agent(
3737
system_message="You are a helpful assistant that must use the tool get_website_content to get the content of a website.",
3838
model=model,
3939
tools=[get_website_content],
40+
step_timeout=900,
4041
)
4142
return agent

backend/app/utils/agent.py

Lines changed: 101 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def __init__(
107107
pause_event: asyncio.Event | None = None,
108108
prune_tool_calls_from_memory: bool = False,
109109
enable_snapshot_clean: bool = False,
110+
step_timeout: float | None = 900,
110111
) -> None:
111112
super().__init__(
112113
system_message=system_message,
@@ -128,6 +129,7 @@ def __init__(
128129
pause_event=pause_event,
129130
prune_tool_calls_from_memory=prune_tool_calls_from_memory,
130131
enable_snapshot_clean=enable_snapshot_clean,
132+
step_timeout=step_timeout,
131133
)
132134
self.api_task_id = api_task_id
133135
self.agent_name = agent_name
@@ -287,19 +289,25 @@ def _execute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord
287289
if asyncio.iscoroutinefunction(tool.func):
288290
# For async functions, we need to use the async execution path
289291
return asyncio.run(self._aexecute_tool(tool_call_request))
290-
elif hasattr(tool.func, "__wrapped__"):
291-
with set_process_task(self.process_task_id):
292-
return super()._execute_tool(tool_call_request)
293-
else:
294-
args = tool_call_request.args
295-
tool_call_id = tool_call_request.tool_call_id
296-
try:
297-
task_lock = get_task_lock(self.api_task_id)
298-
299-
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
300-
traceroot_logger.debug(
301-
f"Agent {self.agent_name} executing tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
302-
)
292+
293+
# Handle all sync tools ourselves to maintain ContextVar context
294+
args = tool_call_request.args
295+
tool_call_id = tool_call_request.tool_call_id
296+
297+
# Check if tool is wrapped by @listen_toolkit decorator
298+
# If so, the decorator will handle activate/deactivate events
299+
has_listen_decorator = hasattr(tool.func, "__wrapped__")
300+
301+
try:
302+
task_lock = get_task_lock(self.api_task_id)
303+
304+
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
305+
traceroot_logger.debug(
306+
f"Agent {self.agent_name} executing tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
307+
)
308+
309+
# Only send activate event if tool is NOT wrapped by @listen_toolkit
310+
if not has_listen_decorator:
303311
asyncio.create_task(
304312
task_lock.put_queue(
305313
ActionActivateToolkitData(
@@ -313,29 +321,33 @@ def _execute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord
313321
)
314322
)
315323
)
324+
# Set process_task context for all tool executions
325+
with set_process_task(self.process_task_id):
316326
raw_result = tool(**args)
317-
traceroot_logger.debug(f"Tool {func_name} executed successfully")
318-
if self.mask_tool_output:
319-
self._secure_result_store[tool_call_id] = raw_result
320-
result = (
321-
"[The tool has been executed successfully, but the output"
322-
" from the tool is masked. You can move forward]"
323-
)
324-
mask_flag = True
325-
else:
326-
result = raw_result
327-
mask_flag = False
328-
# Prepare result message with truncation
329-
if isinstance(result, str):
330-
result_msg = result
327+
traceroot_logger.debug(f"Tool {func_name} executed successfully")
328+
if self.mask_tool_output:
329+
self._secure_result_store[tool_call_id] = raw_result
330+
result = (
331+
"[The tool has been executed successfully, but the output"
332+
" from the tool is masked. You can move forward]"
333+
)
334+
mask_flag = True
335+
else:
336+
result = raw_result
337+
mask_flag = False
338+
# Prepare result message with truncation
339+
if isinstance(result, str):
340+
result_msg = result
341+
else:
342+
result_str = repr(result)
343+
MAX_RESULT_LENGTH = 500
344+
if len(result_str) > MAX_RESULT_LENGTH:
345+
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
331346
else:
332-
result_str = repr(result)
333-
MAX_RESULT_LENGTH = 500
334-
if len(result_str) > MAX_RESULT_LENGTH:
335-
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
336-
else:
337-
result_msg = result_str
347+
result_msg = result_str
338348

349+
# Only send deactivate event if tool is NOT wrapped by @listen_toolkit
350+
if not has_listen_decorator:
339351
asyncio.create_task(
340352
task_lock.put_queue(
341353
ActionDeactivateToolkitData(
@@ -349,31 +361,40 @@ def _execute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord
349361
)
350362
)
351363
)
352-
except Exception as e:
353-
# Capture the error message to prevent framework crash
354-
error_msg = f"Error executing tool '{func_name}': {e!s}"
355-
result = f"Tool execution failed: {error_msg}"
356-
mask_flag = False
357-
traceroot_logger.error(f"Tool execution failed for {func_name}: {e}", exc_info=True)
358-
359-
return self._record_tool_calling(func_name, args, result, tool_call_id, mask_output=mask_flag)
364+
except Exception as e:
365+
# Capture the error message to prevent framework crash
366+
error_msg = f"Error executing tool '{func_name}': {e!s}"
367+
result = f"Tool execution failed: {error_msg}"
368+
mask_flag = False
369+
traceroot_logger.error(f"Tool execution failed for {func_name}: {e}", exc_info=True)
370+
371+
return self._record_tool_calling(
372+
func_name, args, result, tool_call_id,
373+
mask_output=mask_flag,
374+
extra_content=tool_call_request.extra_content,
375+
)
360376

361377
@traceroot.trace()
362378
async def _aexecute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord:
363379
func_name = tool_call_request.tool_name
364380
tool: FunctionTool = self._internal_tools[func_name]
365-
if hasattr(tool.func, "__wrapped__"):
366-
with set_process_task(self.process_task_id):
367-
return await super()._aexecute_tool(tool_call_request)
368-
else:
369-
args = tool_call_request.args
370-
tool_call_id = tool_call_request.tool_call_id
371-
task_lock = get_task_lock(self.api_task_id)
372381

373-
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
374-
traceroot_logger.info(
375-
f"Agent {self.agent_name} executing async tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
376-
)
382+
# Always handle tool execution ourselves to maintain ContextVar context
383+
args = tool_call_request.args
384+
tool_call_id = tool_call_request.tool_call_id
385+
task_lock = get_task_lock(self.api_task_id)
386+
387+
# Check if tool is wrapped by @listen_toolkit decorator
388+
# If so, the decorator will handle activate/deactivate events
389+
has_listen_decorator = hasattr(tool.func, "__wrapped__")
390+
391+
toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit"
392+
traceroot_logger.info(
393+
f"Agent {self.agent_name} executing async tool: {func_name} from toolkit: {toolkit_name} with args: {json.dumps(args, ensure_ascii=False)}"
394+
)
395+
396+
# Only send activate event if tool is NOT wrapped by @listen_toolkit
397+
if not has_listen_decorator:
377398
await task_lock.put_queue(
378399
ActionActivateToolkitData(
379400
data={
@@ -385,7 +406,9 @@ async def _aexecute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallin
385406
},
386407
)
387408
)
388-
try:
409+
try:
410+
# Set process_task context for all tool executions
411+
with set_process_task(self.process_task_id):
389412
# Try different invocation paths in order of preference
390413
if hasattr(tool, "func") and hasattr(tool.func, "async_call"):
391414
# Case: FunctionTool wrapping an MCP tool
@@ -404,29 +427,32 @@ async def _aexecute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallin
404427
result = await tool(**args)
405428

406429
else:
407-
# Fallback: synchronous call
430+
# Fallback: synchronous call - call directly in current context
431+
# DO NOT use run_in_executor to preserve ContextVar
408432
result = tool(**args)
409433
# Handle case where synchronous call returns a coroutine
410434
if asyncio.iscoroutine(result):
411435
result = await result
412436

413-
except Exception as e:
414-
# Capture the error message to prevent framework crash
415-
error_msg = f"Error executing async tool '{func_name}': {e!s}"
416-
result = {"error": error_msg}
417-
traceroot_logger.error(f"Async tool execution failed for {func_name}: {e}", exc_info=True)
418-
419-
# Prepare result message with truncation
420-
if isinstance(result, str):
421-
result_msg = result
437+
except Exception as e:
438+
# Capture the error message to prevent framework crash
439+
error_msg = f"Error executing async tool '{func_name}': {e!s}"
440+
result = {"error": error_msg}
441+
traceroot_logger.error(f"Async tool execution failed for {func_name}: {e}", exc_info=True)
442+
443+
# Prepare result message with truncation
444+
if isinstance(result, str):
445+
result_msg = result
446+
else:
447+
result_str = repr(result)
448+
MAX_RESULT_LENGTH = 500
449+
if len(result_str) > MAX_RESULT_LENGTH:
450+
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
422451
else:
423-
result_str = repr(result)
424-
MAX_RESULT_LENGTH = 500
425-
if len(result_str) > MAX_RESULT_LENGTH:
426-
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
427-
else:
428-
result_msg = result_str
452+
result_msg = result_str
429453

454+
# Only send deactivate event if tool is NOT wrapped by @listen_toolkit
455+
if not has_listen_decorator:
430456
await task_lock.put_queue(
431457
ActionDeactivateToolkitData(
432458
data={
@@ -438,7 +464,10 @@ async def _aexecute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallin
438464
},
439465
)
440466
)
441-
return self._record_tool_calling(func_name, args, result, tool_call_id)
467+
return self._record_tool_calling(
468+
func_name, args, result, tool_call_id,
469+
extra_content=tool_call_request.extra_content,
470+
)
442471

443472
@traceroot.trace()
444473
def clone(self, with_memory: bool = False) -> ChatAgent:
@@ -468,6 +497,7 @@ def clone(self, with_memory: bool = False) -> ChatAgent:
468497
mask_tool_output=self.mask_tool_output,
469498
pause_event=self.pause_event,
470499
prune_tool_calls_from_memory=self.prune_tool_calls_from_memory,
500+
step_timeout=self.step_timeout,
471501
)
472502

473503
new_agent.process_task_id = self.process_task_id
@@ -746,7 +776,7 @@ def search_agent(options: Chat):
746776
"browser_enter",
747777
"browser_visit_page",
748778
"browser_scroll",
749-
"browser_get_som_screenshot",
779+
# "browser_get_som_screenshot",
750780
],
751781
)
752782

@@ -869,9 +899,6 @@ def search_agent(options: Chat):
869899
interactive elements, not the full page text. To see more content on
870900
long pages, Navigate with `browser_click`, `browser_back`, and
871901
`browser_forward`. Manage multiple pages with `browser_switch_tab`.
872-
- **Analysis**: Use `browser_get_som_screenshot` to understand the page
873-
layout and identify interactive elements. Since this is a heavy
874-
operation, only use it when visual analysis is necessary.
875902
- **Interaction**: Use `browser_type` to fill out forms and
876903
`browser_enter` to submit or confirm search.
877904

0 commit comments

Comments
 (0)