From 7e654b7aceb0a9b445fccf6b83c8a94c1a6dbf4a Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 8 Jun 2026 09:44:50 +0200 Subject: [PATCH] feat(tools): Add web_fetch tool Improves the web searching and crawling capabilities of Context Agent, as the duckduckgo tool doesn't allow exploring websites Signed-off-by: Marcel Klehr --- ex_app/lib/agent.py | 6 ++++-- ex_app/lib/all_tools/files.py | 4 ++-- ex_app/lib/all_tools/web.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 ex_app/lib/all_tools/web.py diff --git a/ex_app/lib/agent.py b/ex_app/lib/agent.py index 89e4540..b150da8 100644 --- a/ex_app/lib/agent.py +++ b/ex_app/lib/agent.py @@ -127,7 +127,7 @@ async def call_model( At the end of each message to the user, if you have carried out a task or answered a question, suggest up to three actions for things you can do for the user based on the tools you have available and details of the previous task. For example: If the user wants to know the weather for some location, they might be planning an event, you can suggest to create an event for them, or if they searched for a file, they may want to share it with others, suggest to create a share link for them, if they want a summary of something, you can suggest them to send the summary to somebody. """ if tool_enabled("duckduckgo_results_json"): - system_prompt_text += "Only use the duckduckgo_results_json tool if the user explicitly asks for a web search.\n" + system_prompt_text += "Use the duckduckgo_results_json tool if you don't know about a topic or concept that the user is referencing.\n" if tool_enabled("list_talk_conversations"): system_prompt_text += "Use the list_talk_conversations tool to check which conversations exist.\n" if tool_enabled("list_calendars"): @@ -140,8 +140,10 @@ async def call_model( system_prompt_text += "Use the find_details_of_current_user tool to find the current user's location and timezone.\n" if tool_enabled("list_mails"): system_prompt_text += "Always check for the mail account id before requesting a folder list.\n" + if tool_enabled("web_fetch"): + system_prompt_text += "Use the web_fetch tool to fetch web content. You can fetch the complete page content of a duckduckgo search result using web_fetch as well.\n" - if task['input'].get('memories', None) is not None: + if task['input'].get('memories', None) is not None and task['input'].get('memories', None) is not []: system_prompt_text += "You can remember things from other conversations with the user. If relevant, take into account the following memories:\n\n" + "\n".join(task['input']['memories']) + "\n\n" # this is similar to customizing the create_react_agent with state_modifier, but is a lot more flexible system_prompt = SystemMessage( diff --git a/ex_app/lib/all_tools/files.py b/ex_app/lib/all_tools/files.py index 445b85d..e20b15b 100644 --- a/ex_app/lib/all_tools/files.py +++ b/ex_app/lib/all_tools/files.py @@ -15,7 +15,7 @@ async def get_tools(nc: AsyncNextcloudApp): @safe_tool async def get_file_content(file_path: str): """ - Get the content of a file + Get the content of a nextcloud-internal file of the current user :param file_path: the path of the file :return: """ @@ -33,7 +33,7 @@ async def get_file_content(file_path: str): async def get_file_content_by_file_link(file_url: str): """ Get the content of a file given an internal Nextcloud link (e.g., https://host/index.php/f/12345) - :param file_url: the internal file URL + :param file_url: the nextcloud-internal file URL :return: text content of the file """ diff --git a/ex_app/lib/all_tools/web.py b/ex_app/lib/all_tools/web.py new file mode 100644 index 0000000..2d78930 --- /dev/null +++ b/ex_app/lib/all_tools/web.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +import niquests +from langchain_core.tools import tool +from nc_py_api import AsyncNextcloudApp + +from ex_app.lib.all_tools.lib.decorator import safe_tool + + +async def get_tools(nc: AsyncNextcloudApp): + + @tool + @safe_tool + async def web_fetch(url: str) -> str: + """ + Get the contents of a web page via HTTP + :param url: The HTTP URL to the web page (e.g. https://nextcloud.com/team/ ) + :return: the web page content + """ + res = await niquests.get(url) + return res.text() + + return [ + web_fetch, + ] + +def get_category_name(): + return "Web access" + +async def is_available(nc: AsyncNextcloudApp): + return True \ No newline at end of file