From 73d64d7bb59eeaa3a7166b0a5eb777b0e3045d93 Mon Sep 17 00:00:00 2001 From: Saurav Panda Date: Thu, 12 Mar 2026 12:44:10 -0700 Subject: [PATCH] remove read long content action --- browser_use/filesystem/file_system.py | 2 +- browser_use/tools/service.py | 270 -------------------------- browser_use/tools/views.py | 11 -- 3 files changed, 1 insertion(+), 282 deletions(-) diff --git a/browser_use/filesystem/file_system.py b/browser_use/filesystem/file_system.py index a3be9a8f8..49946a1ea 100644 --- a/browser_use/filesystem/file_system.py +++ b/browser_use/filesystem/file_system.py @@ -648,7 +648,7 @@ class FileSystem: truncation_note = ( f'\n\n[Showing {len(pages_included)} of {num_pages} pages. ' f'Skipped pages: {skipped[:10]}{"..." if len(skipped) > 10 else ""}. ' - f'Use read_long_content with a specific goal to find relevant sections.]' + f'Use extract with start_from_char to read further into the file.]' ) else: truncation_note = '' diff --git a/browser_use/tools/service.py b/browser_use/tools/service.py index 5e0a40b30..25f12590d 100644 --- a/browser_use/tools/service.py +++ b/browser_use/tools/service.py @@ -47,7 +47,6 @@ from browser_use.tools.views import ( InputTextAction, NavigateAction, NoParamsAction, - ReadContentAction, SaveAsPdfAction, ScreenshotAction, ScrollAction, @@ -1696,275 +1695,6 @@ You will be given a query and the markdown of a webpage that has been filtered t include_extracted_content_only_once=True, ) - # Intelligent content reading - - @self.registry.action( - 'Intelligently read long content to find specific information. Works on current page (source="page") or files. For large content, uses search to identify relevant sections. Best for long articles, documents, or any content where you know what you are looking for.', - param_model=ReadContentAction, - ) - async def read_long_content( - params: ReadContentAction, - browser_session: BrowserSession, - page_extraction_llm: BaseChatModel, - available_file_paths: list[str], - ): - import re - - from browser_use.llm.messages import UserMessage - - goal = params.goal - context = params.context - source = params.source - max_chars = 50000 - - async def extract_search_terms(goal: str, context: str) -> list[str]: - """Use LLM to extract search terms from goal.""" - prompt = f"""Extract 3-5 key search terms from this goal that would help find relevant sections. -Return only the terms, one per line, no numbering or bullets. - -Goal: {goal} - -Context: {context}""" - response = await page_extraction_llm.ainvoke([UserMessage(content=prompt)]) - return [term.strip() for term in response.completion.strip().split('\n') if term.strip()][:5] - - def search_text(content: str, pattern: str, context_chars: int = 100) -> list[dict]: - """Search content for pattern, return matches with positions.""" - try: - regex = re.compile(pattern, re.IGNORECASE) - except re.error: - regex = re.compile(re.escape(pattern), re.IGNORECASE) - - matches = [] - for match in regex.finditer(content): - start = max(0, match.start() - context_chars) - end = min(len(content), match.end() + context_chars) - matches.append( - { - 'position': match.start(), - 'snippet': content[start:end], - } - ) - return matches - - def chunk_content(content: str, chunk_size: int = 2000) -> list[dict]: - """Split content into chunks with positions.""" - chunks = [] - for i in range(0, len(content), chunk_size): - chunks.append( - { - 'start': i, - 'end': min(i + chunk_size, len(content)), - 'text': content[i : i + chunk_size], - } - ) - return chunks - - try: - if source.lower() == 'page': - # Read from current webpage - from browser_use.dom.markdown_extractor import extract_clean_markdown - - # Clear DOM cache and wait for page to settle before extracting - if browser_session._dom_watchdog: - browser_session._dom_watchdog.clear_cache() - - wait_time = browser_session.browser_profile.wait_for_network_idle_page_load_time - await asyncio.sleep(wait_time) - - content, _ = await extract_clean_markdown(browser_session=browser_session, extract_links=False) - source_name = 'current page' - - if not content: - return ActionResult( - extracted_content='Error: No page content available', - long_term_memory='Failed to read page: no content', - ) - - else: - # Read from file - file_path = source - - # Validate file path against whitelist (available_file_paths + downloaded files) - allowed_paths = set(available_file_paths or []) - allowed_paths.update(browser_session.downloaded_files) - if file_path not in allowed_paths: - return ActionResult( - extracted_content=f'Error: File path not in available_file_paths: {file_path}. ' - f'The user must add this path to available_file_paths when creating the Agent.', - long_term_memory=f'Failed to read: file path not allowed: {file_path}', - ) - - if not os.path.exists(file_path): - return ActionResult( - extracted_content=f'Error: File not found: {file_path}', - long_term_memory='Failed to read: file not found', - ) - - ext = os.path.splitext(file_path)[1].lower() - source_name = os.path.basename(file_path) - - if ext == '.pdf': - # Read PDF directly using pypdf - import pypdf - - reader = pypdf.PdfReader(file_path) - num_pages = len(reader.pages) - - # Extract all page text - page_texts: list[str] = [] - total_chars = 0 - for page in reader.pages: - text = page.extract_text() or '' - page_texts.append(text) - total_chars += len(text) - - # If PDF is small enough, return it all - if total_chars <= max_chars: - content_parts = [] - for i, text in enumerate(page_texts, 1): - if text.strip(): - content_parts.append(f'--- Page {i} ---\n{text}') - content = '\n\n'.join(content_parts) - - memory = f'Read {source_name} ({num_pages} pages, {total_chars:,} chars) for goal: {goal[:50]}' - logger.info(f'📄 {memory}') - return ActionResult( - extracted_content=f'PDF: {source_name} ({num_pages} pages)\n\n{content}', - long_term_memory=memory, - include_extracted_content_only_once=True, - ) - - # PDF too large - use intelligent extraction - logger.info(f'PDF has {total_chars:,} chars across {num_pages} pages, using intelligent extraction') - - # Extract search terms from goal - search_terms = await extract_search_terms(goal, context) - - # Search and score pages by relevance - page_scores: dict[int, int] = {} # 1-indexed page -> score - for term in search_terms: - try: - term_pattern = re.compile(re.escape(term), re.IGNORECASE) - except re.error: - continue - for i, text in enumerate(page_texts, 1): - if term_pattern.search(text): - page_scores[i] = page_scores.get(i, 0) + 1 - - # Select pages: always include page 1, then most relevant - pages_to_read = [1] - sorted_pages = sorted(page_scores.items(), key=lambda x: -x[1]) - for page_num, _ in sorted_pages: - if page_num not in pages_to_read: - pages_to_read.append(page_num) - - # Build result respecting char limit, truncating pages if needed - content_parts = [] - chars_used = 0 - pages_included = [] - for page_num in sorted(set(pages_to_read)): - text = page_texts[page_num - 1] - page_header = f'--- Page {page_num} ---\n' - remaining = max_chars - chars_used - if remaining < len(page_header) + 50: - break # no room for meaningful content - page_content = page_header + text - if len(page_content) > remaining: - page_content = page_content[: remaining - len('\n[...truncated]')] + '\n[...truncated]' - content_parts.append(page_content) - chars_used += len(page_content) - pages_included.append(page_num) - - content = '\n\n'.join(content_parts) - memory = f'Read {source_name} ({len(pages_included)} relevant pages of {num_pages}) for goal: {goal[:50]}' - logger.info(f'📄 {memory}') - return ActionResult( - extracted_content=f'PDF: {source_name} ({num_pages} pages, showing {len(pages_included)} relevant)\n\n{content}', - long_term_memory=memory, - include_extracted_content_only_once=True, - ) - - else: - # Text file - async with await anyio.open_file(file_path, 'r', encoding='utf-8', errors='ignore') as f: - content = await f.read() - - # Check if content fits in budget - if len(content) <= max_chars: - memory = f'Read {source_name} ({len(content):,} chars) for goal: {goal[:50]}' - logger.info(f'📄 {memory}') - return ActionResult( - extracted_content=f'Content from {source_name} ({len(content):,} chars):\n\n{content}', - long_term_memory=memory, - include_extracted_content_only_once=True, - ) - - # Content too large - use intelligent extraction - logger.info(f'Content has {len(content):,} chars, using intelligent extraction') - - # Extract search terms from goal - search_terms = await extract_search_terms(goal, context) - - # Search for each term and score chunks - chunks = chunk_content(content, chunk_size=2000) - chunk_scores: dict[int, int] = {} # chunk index -> relevance score - - for term in search_terms: - matches = search_text(content, term) - for match in matches: - # Find which chunk this match belongs to - for i, chunk in enumerate(chunks): - if chunk['start'] <= match['position'] < chunk['end']: - chunk_scores[i] = chunk_scores.get(i, 0) + 1 - break - - if not chunk_scores: - # No matches - return first max_chars - truncated = content[:max_chars] - memory = f'Read {source_name} (truncated to {max_chars:,} chars, no matches for search terms)' - logger.info(f'📄 {memory}') - return ActionResult( - extracted_content=f'Content from {source_name} (first {max_chars:,} of {len(content):,} chars):\n\n{truncated}', - long_term_memory=memory, - include_extracted_content_only_once=True, - ) - - # Sort chunks by relevance and collect most relevant ones - sorted_chunks = sorted(chunk_scores.items(), key=lambda x: -x[1]) - - # Always include first chunk for context - selected_indices = {0} # Start with first chunk - for chunk_idx, _ in sorted_chunks: - selected_indices.add(chunk_idx) - - # Build result from selected chunks in order - result_parts = [] - total_chars = 0 - for i in sorted(selected_indices): - chunk = chunks[i] - if total_chars + len(chunk['text']) > max_chars: - break - if i > 0 and (i - 1) not in selected_indices: - result_parts.append('\n[...]\n') # Indicate gap - result_parts.append(chunk['text']) - total_chars += len(chunk['text']) - - result_content = ''.join(result_parts) - memory = f'Read {source_name} ({len(selected_indices)} relevant sections of {len(chunks)}) for goal: {goal[:50]}' - logger.info(f'📄 {memory}') - - return ActionResult( - extracted_content=f'Content from {source_name} (relevant sections, {total_chars:,} of {len(content):,} chars):\n\n{result_content}', - long_term_memory=memory, - include_extracted_content_only_once=True, - ) - - except Exception as e: - error_msg = f'Error reading content: {str(e)}' - logger.error(error_msg) - return ActionResult(extracted_content=error_msg, long_term_memory=error_msg) - @self.registry.action( """Execute browser JavaScript. Best practice: wrap in IIFE (function(){...})() with try-catch for safety. Use ONLY browser APIs (document, window, DOM). NO Node.js APIs (fs, require, process). Example: (function(){try{const el=document.querySelector('#id');return el?el.value:'not found'}catch(e){return 'Error: '+e.message}})() Avoid comments. Use for hover, drag, zoom, custom selectors, extract/filter links, or analysing page structure. IMPORTANT: Shadow DOM elements with [index] markers can be clicked directly with click(index) — do NOT use evaluate() to click them. Only use evaluate for shadow DOM elements that are NOT indexed. Limit output size.""", terminates_sequence=True, diff --git a/browser_use/tools/views.py b/browser_use/tools/views.py index a8102ecf9..8274bea19 100644 --- a/browser_use/tools/views.py +++ b/browser_use/tools/views.py @@ -159,17 +159,6 @@ class SaveAsPdfAction(BaseModel): ) -class ReadContentAction(BaseModel): - """Action for intelligent reading of long content.""" - - goal: str = Field(description='What to look for or extract from the content') - source: str = Field( - default='page', - description='What to read: "page" for current webpage, or a file path', - ) - context: str = Field(default='', description='Additional context about the task') - - class GetDropdownOptionsAction(BaseModel): index: int