From fe7cb7ac479fdde69ed7016d22f5897fc3250e82 Mon Sep 17 00:00:00 2001 From: Aditya Chhabra Date: Mon, 13 Oct 2025 18:35:37 +0530 Subject: [PATCH] fix: fix multi agent paper writer langgraph script, mypy and pylint issues --- src/cleveragents/agents/base.py | 8 +- src/cleveragents/agents/tool.py | 100 +++++------------- src/cleveragents/core/application.py | 6 +- ...test_multi_agent_paper_writer_langgraph.sh | 8 +- 4 files changed, 39 insertions(+), 83 deletions(-) diff --git a/src/cleveragents/agents/base.py b/src/cleveragents/agents/base.py index 6e8f025d..4496cdda 100644 --- a/src/cleveragents/agents/base.py +++ b/src/cleveragents/agents/base.py @@ -70,8 +70,8 @@ class Agent(ABC): def _setup_processing_pipeline(self) -> None: """Set up the reactive processing pipeline.""" self.input_stream.pipe( - ops.map(self._process_wrapper), # type: ignore[arg-type] - ops.flat_map(rx.from_future), # type: ignore[arg-type] + ops.map(self._process_wrapper), + ops.flat_map(rx.from_future), ).subscribe( on_next=self.output_stream.on_next, on_error=self.output_stream.on_error ) @@ -306,7 +306,7 @@ class StreamableAgent(Agent): scheduler=scheduler, ) - return rx.create(subscribe) # type: ignore[no-untyped-call] + return rx.create(subscribe) return operator @@ -331,7 +331,7 @@ class StreamableAgent(Agent): return await result_future - return ops.map(process_value) # type: ignore[arg-type] + return ops.map(process_value) def filter_operator(self, condition_func: Callable[[Any], bool]) -> Any: """ diff --git a/src/cleveragents/agents/tool.py b/src/cleveragents/agents/tool.py index 35ec4b63..3d2ac68e 100644 --- a/src/cleveragents/agents/tool.py +++ b/src/cleveragents/agents/tool.py @@ -96,6 +96,7 @@ class ToolAgent(Agent): Raises: json.JSONDecodeError: If message appears to be JSON but is malformed. """ + # pylint: disable=too-many-return-statements message_stripped = message.strip() # If message looks like JSON (starts with { and ends with }), it must be valid JSON @@ -111,14 +112,11 @@ class ToolAgent(Agent): code_block_pattern = r'```(?:json)?\s*(\{.*?\})\s*```' match = re.search(code_block_pattern, message, re.DOTALL) if match: - try: - parsed = json.loads(match.group(1)) - if isinstance(parsed, dict): # ✅ Add type check - return parsed - return None # Not a dict - except json.JSONDecodeError: - # Code block had invalid JSON, raise error - raise + # Code block had JSON, parse it (let JSONDecodeError propagate if invalid) + parsed = json.loads(match.group(1)) + if isinstance(parsed, dict): + return parsed + return None # Not a dict # Try to find any JSON object in the message json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}' @@ -367,48 +365,6 @@ class ToolAgent(Agent): except Exception as e: raise ExecutionError(f"File read failed: {e}") from e - def _clean_control_markers(self, content: str) -> str: - """ - Remove control markers from content before writing to files. - - This ensures that workflow control signals like [SECTION_COMPLETE], - [PAPER_COMPLETE], [WRITER SPEAKING], etc., don't end up in the output files. - - Args: - content: The raw content with potential control markers - - Returns: - Cleaned content without control markers - """ - import re - - # List of control markers to remove - control_markers = [ - r'\[SECTION_COMPLETE\]', - r'\[PAPER_COMPLETE\]', - r'\[WRITER SPEAKING\]', - r'\[RESEARCHER SPEAKING\]', - r'\[REVIEWER SPEAKING\]', - r'\[REQUIREMENTS_COMPLETE\]', - r'\[REVIEW_COMPLETE\]', - r'\[ROUTE:\w+\]', - r'\[FILE_READ_SUCCESS\]', - r'\[FILE_CONTENT_START\]', - r'\[FILE_CONTENT_END\]', - ] - - # Remove each marker - cleaned = content - for marker in control_markers: - cleaned = re.sub(marker, '', cleaned) - - # Clean up excessive whitespace that might result from marker removal - # But preserve intentional paragraph breaks - cleaned = re.sub(r'\n\n\n+', '\n\n', cleaned) - cleaned = cleaned.strip() - - return cleaned - def _validate_file_write_args( self, filepath: str, content: str ) -> None: @@ -435,16 +391,16 @@ class ToolAgent(Agent): raise ExecutionError("Unsafe file path blocked in safe mode") def _prepare_append_content( - self, filepath: str, cleaned_content: str + self, filepath: str, content: str ) -> str: """Prepare content for append mode with proper spacing for sections.""" # Only add spacing if content looks like a document section (starts with #) # This prevents breaking simple append operations - is_section = cleaned_content.lstrip().startswith('#') + is_section = content.lstrip().startswith('#') if not is_section: # Simple append without spacing - return cleaned_content + return content try: with open(filepath, "r", encoding="utf-8") as f: @@ -461,10 +417,13 @@ class ToolAgent(Agent): except FileNotFoundError: prefix = '' # File doesn't exist yet - return prefix + cleaned_content + return prefix + content def _handle_insert_position( - self, filepath: str, cleaned_content: str, position: Union[None, int, Literal["start", "end"]] + self, + filepath: str, + content: str, + position: Union[None, int, Literal["start", "end"]], ) -> tuple[list[str], int]: """Handle insert mode positioning logic.""" try: @@ -475,8 +434,8 @@ class ToolAgent(Agent): # Ensure content ends with newline formatted_content = ( - cleaned_content if cleaned_content.endswith('\n') - else cleaned_content + '\n' + content if content.endswith('\n') + else content + '\n' ) # Determine insertion position @@ -509,10 +468,6 @@ class ToolAgent(Agent): # Validate inputs self._validate_file_write_args(filepath, content) - - # Clean control markers from content before writing - cleaned_content = self._clean_control_markers(content) - # Check unsafe mode requirement unsafe_mode = context and context.get("_unsafe_mode", False) if not unsafe_mode: @@ -526,32 +481,31 @@ class ToolAgent(Agent): if mode == "w": # Standard write (overwrite) with open(filepath, "w", encoding="utf-8") as f: - f.write(cleaned_content) - return f"Successfully wrote {len(cleaned_content)} characters to {filepath}" + f.write(content) + return f"Successfully wrote {len(content)} characters to {filepath}" - elif mode == "a": + if mode == "a": # Append mode with proper spacing - content_to_append = self._prepare_append_content(filepath, cleaned_content) + content_to_append = self._prepare_append_content(filepath, content) with open(filepath, "a", encoding="utf-8") as f: f.write(content_to_append) - return f"Successfully appended {len(cleaned_content)} characters to {filepath}" + return f"Successfully appended {len(content)} characters to {filepath}" - elif mode == "insert": + if mode == "insert": # Insert mode at specified position existing_lines, insert_location = self._handle_insert_position( - filepath, cleaned_content, position + filepath, content, position ) with open(filepath, "w", encoding="utf-8") as f: f.writelines(existing_lines) return ( - f"Successfully inserted {len(cleaned_content)} characters " + f"Successfully inserted {len(content)} characters " f"at line {insert_location} in {filepath}" ) - else: - raise ExecutionError( - f"Invalid mode '{mode}'. Use 'w' (write), 'a' (append), or 'insert'." - ) + raise ExecutionError( + f"Invalid mode '{mode}'. Use 'w' (write), 'a' (append), or 'insert'." + ) except Exception as e: logger.error("File write failed for %s: %s", filepath, e) raise ExecutionError(f"File write failed: {e}") from e diff --git a/src/cleveragents/core/application.py b/src/cleveragents/core/application.py index 8a7ac644..146063c1 100644 --- a/src/cleveragents/core/application.py +++ b/src/cleveragents/core/application.py @@ -44,6 +44,8 @@ from cleveragents.templates.registry import TemplateRegistry from cleveragents.templates.renderer import TemplateEngine from cleveragents.templates.renderer import TemplateRenderer +logger = logging.getLogger(__name__) + class ReactiveCleverAgentsApp: # pylint: disable=too-many-instance-attributes """ @@ -92,7 +94,7 @@ class ReactiveCleverAgentsApp: # pylint: disable=too-many-instance-attributes self.loop = asyncio.get_event_loop() self.scheduler = AsyncIOScheduler(self.loop) self.stream_router = ReactiveStreamRouter(self.scheduler) - self.config_parser = ReactiveConfigParser() # type: ignore[no-untyped-call] + self.config_parser = ReactiveConfigParser() self.agent_factory: Optional[AgentFactory] = None # Initialize LangGraph bridge @@ -414,7 +416,7 @@ class ReactiveCleverAgentsApp: # pylint: disable=too-many-instance-attributes self._use_enhanced_registry = True self.logger.info("Using enhanced template registry for complex templates") else: - self.template_registry = TemplateRegistry() # type: ignore[no-untyped-call] + self.template_registry = TemplateRegistry() # Register all templates if self.config.templates: diff --git a/tests/scripts/test_multi_agent_paper_writer_langgraph.sh b/tests/scripts/test_multi_agent_paper_writer_langgraph.sh index bc6fe116..502f8ac3 100644 --- a/tests/scripts/test_multi_agent_paper_writer_langgraph.sh +++ b/tests/scripts/test_multi_agent_paper_writer_langgraph.sh @@ -18,7 +18,7 @@ echo "==========================================" echo "" echo "Testing complete workflow:" echo " 1. Coordinator routes requests" -echo " 2. Researcher gathers requirements" +echo " 2. Researcher gathers information" echo " 3. Writer creates the paper" echo " 4. Reviewer provides feedback" echo " 5. Writer applies suggestions" @@ -34,8 +34,8 @@ echo "" # Run the interactive session with incremental commands cat << 'EOF' | timeout 240 python -m cleveragents interactive -c examples/multi_agent_paper_writer_langgraph.yaml --unsafe 2>&1 | tee /tmp/paper_writer_test.log /graph paper_writing_workflow hello, who are you and how can you help me? -/graph paper_writing_workflow I want to write a paper about quantum computing advantages -/graph paper_writing_workflow yes, write a brief 250 word paper on that topic +/graph paper_writing_workflow I want to write a paper about quantum computing advantages. First, research the key advantages of quantum computing including speed, parallelism, and applications. +/graph paper_writing_workflow Now write a brief 250 word paper on quantum computing advantages covering: computational speed, quantum parallelism, and real-world applications. Target audience is general tech professionals. /graph paper_writing_workflow please review the paper that was just written /graph paper_writing_workflow apply the reviewer's suggestions and improve the paper /graph paper_writing_workflow save the final paper to quantum_test_paper.txt @@ -70,7 +70,7 @@ if [ -f "quantum_test_paper.txt" ]; then echo "----------------------------------------" head -20 quantum_test_paper.txt echo "----------------------------------------" - + # Verify content quality if grep -qi "quantum" quantum_test_paper.txt && \ grep -qi "computing" quantum_test_paper.txt; then