forked from cleveragents/cleveragents-core
fix: fix multi agent paper writer langgraph script, mypy and pylint issues
This commit is contained in:
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user