⚡ Add aggressive content limiting to prevent MCP 25k token errors
- Implement smart content truncation at ~80k chars (~20k tokens) - Preserve document structure when truncating (stop at natural breaks) - Add clear truncation notices with guidance for smaller ranges - Update chunking suggestions to use safer 8-page chunks - Enhance recommendations to suggest 3-8 page ranges - Prevent 29,869 > 25,000 token errors while maintaining usability
This commit is contained in:
parent
9c2f299d49
commit
3dffce6904
@ -375,10 +375,27 @@ async def convert_to_markdown(
|
|||||||
if "table_of_contents" in markdown_result:
|
if "table_of_contents" in markdown_result:
|
||||||
result["table_of_contents"] = markdown_result["table_of_contents"]
|
result["table_of_contents"] = markdown_result["table_of_contents"]
|
||||||
else:
|
else:
|
||||||
# Include full content for smaller documents or page ranges
|
# Include content with automatic size limiting to prevent MCP errors
|
||||||
result["markdown"] = markdown_result["content"]
|
content = markdown_result["content"]
|
||||||
result["metadata"]["character_count"] = len(markdown_result["content"])
|
|
||||||
result["metadata"]["word_count"] = len(markdown_result["content"].split())
|
# Apply aggressive content limiting to stay under 25k token limit
|
||||||
|
# Rough estimate: ~4 chars per token, leave buffer for metadata
|
||||||
|
max_content_chars = 80000 # ~20k tokens worth of content
|
||||||
|
|
||||||
|
if len(content) > max_content_chars:
|
||||||
|
# Truncate but try to preserve structure
|
||||||
|
truncated_content = _smart_truncate_content(content, max_content_chars)
|
||||||
|
result["markdown"] = truncated_content
|
||||||
|
result["content_truncated"] = True
|
||||||
|
result["original_length"] = len(content)
|
||||||
|
result["truncated_length"] = len(truncated_content)
|
||||||
|
result["truncation_note"] = f"Content truncated to stay under MCP 25k token limit. Original: {len(content):,} chars, Shown: {len(truncated_content):,} chars. Use smaller page ranges for full content."
|
||||||
|
else:
|
||||||
|
result["markdown"] = content
|
||||||
|
result["content_truncated"] = False
|
||||||
|
|
||||||
|
result["metadata"]["character_count"] = len(content)
|
||||||
|
result["metadata"]["word_count"] = len(content.split())
|
||||||
|
|
||||||
# Add image info
|
# Add image info
|
||||||
if include_images and markdown_result.get("images"):
|
if include_images and markdown_result.get("images"):
|
||||||
@ -1522,6 +1539,49 @@ def _extract_markdown_structure(content: str) -> dict[str, Any]:
|
|||||||
return structure
|
return structure
|
||||||
|
|
||||||
|
|
||||||
|
def _smart_truncate_content(content: str, max_chars: int) -> str:
|
||||||
|
"""Intelligently truncate content while preserving structure and readability."""
|
||||||
|
if len(content) <= max_chars:
|
||||||
|
return content
|
||||||
|
|
||||||
|
lines = content.split('\n')
|
||||||
|
truncated_lines = []
|
||||||
|
current_length = 0
|
||||||
|
|
||||||
|
# Try to preserve structure by stopping at a natural break point
|
||||||
|
for line in lines:
|
||||||
|
line_length = len(line) + 1 # +1 for newline
|
||||||
|
|
||||||
|
# If adding this line would exceed limit
|
||||||
|
if current_length + line_length > max_chars:
|
||||||
|
# Try to find a good stopping point
|
||||||
|
if truncated_lines:
|
||||||
|
# Check if we're in the middle of a section
|
||||||
|
last_lines = '\n'.join(truncated_lines[-3:]) if len(truncated_lines) >= 3 else '\n'.join(truncated_lines)
|
||||||
|
|
||||||
|
# If we stopped mid-paragraph, remove incomplete paragraph
|
||||||
|
if not (line.strip() == '' or line.startswith('#') or line.startswith('|')):
|
||||||
|
# Remove lines until we hit a natural break
|
||||||
|
while truncated_lines and not (
|
||||||
|
truncated_lines[-1].strip() == '' or
|
||||||
|
truncated_lines[-1].startswith('#') or
|
||||||
|
truncated_lines[-1].startswith('|') or
|
||||||
|
truncated_lines[-1].startswith('-') or
|
||||||
|
truncated_lines[-1].startswith('*')
|
||||||
|
):
|
||||||
|
truncated_lines.pop()
|
||||||
|
break
|
||||||
|
|
||||||
|
truncated_lines.append(line)
|
||||||
|
current_length += line_length
|
||||||
|
|
||||||
|
# Add truncation notice
|
||||||
|
result = '\n'.join(truncated_lines)
|
||||||
|
result += f"\n\n---\n**[CONTENT TRUNCATED]**\nShowing {len(result):,} of {len(content):,} characters.\nUse smaller page ranges (e.g., 3-5 pages) for full content without truncation.\n---"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _estimate_section_length(heading_level: int) -> int:
|
def _estimate_section_length(heading_level: int) -> int:
|
||||||
"""Estimate how many pages a section might span based on heading level."""
|
"""Estimate how many pages a section might span based on heading level."""
|
||||||
# Higher level headings (H1) tend to have longer sections
|
# Higher level headings (H1) tend to have longer sections
|
||||||
@ -1598,7 +1658,8 @@ def _generate_chunking_suggestions(sections: list) -> list[dict[str, Any]]:
|
|||||||
section_pages = section["estimated_end_page"] - section["start_page"] + 1
|
section_pages = section["estimated_end_page"] - section["start_page"] + 1
|
||||||
|
|
||||||
# If adding this section would make chunk too large, finalize current chunk
|
# If adding this section would make chunk too large, finalize current chunk
|
||||||
if current_chunk_pages + section_pages > 15 and chunk_sections:
|
# Use smaller chunks (8 pages) to prevent MCP token limit issues
|
||||||
|
if current_chunk_pages + section_pages > 8 and chunk_sections:
|
||||||
suggestions.append({
|
suggestions.append({
|
||||||
"chunk_number": len(suggestions) + 1,
|
"chunk_number": len(suggestions) + 1,
|
||||||
"page_range": f"{chunk_start}-{chunk_sections[-1]['estimated_end_page']}",
|
"page_range": f"{chunk_start}-{chunk_sections[-1]['estimated_end_page']}",
|
||||||
@ -1761,13 +1822,15 @@ def _get_processing_recommendation(
|
|||||||
"Consider using recommended workflow for better performance."
|
"Consider using recommended workflow for better performance."
|
||||||
)
|
)
|
||||||
recommendation["suggested_workflow"] = [
|
recommendation["suggested_workflow"] = [
|
||||||
"1. First: Call with summary_only=true to get document overview",
|
"1. First: Call with summary_only=true to get document overview and TOC",
|
||||||
"2. Then: Use page_range to process specific sections (e.g., '1-10', '20-30')",
|
"2. Then: Use page_range to process specific sections (e.g., '1-5', '6-10', '15-20')",
|
||||||
"3. Alternative: Process in chunks of 10-15 pages to avoid response limits"
|
"3. Recommended: Use 3-8 page chunks to stay under 25k token MCP limit",
|
||||||
|
"4. The tool auto-truncates if content is too large, but smaller ranges work better"
|
||||||
]
|
]
|
||||||
recommendation["warnings"] = [
|
recommendation["warnings"] = [
|
||||||
"Full document processing may hit 25k token response limit",
|
"Page ranges >8 pages may hit 25k token response limit and get truncated",
|
||||||
"Large responses may be slow and consume significant resources"
|
"Use smaller page ranges (3-5 pages) for dense content documents",
|
||||||
|
"Auto-truncation preserves structure but loses content completeness"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Medium document recommendations
|
# Medium document recommendations
|
||||||
|
Loading…
x
Reference in New Issue
Block a user