"""
Documentation Tools for OllamaTools.
This module provides utilities for generating and processing documentation.
It includes tools for creating formatted documentation, extracting relevant
context from documents, and summarizing text.
All functions support integration with the permission system for security.
"""
import markdown
import re
from collections import Counter
from typing import Optional, Dict, Any, List
def generate_markdown(
content: str,
title: str,
format_type: str = 'markdown',
permission_manager = None
) -> str:
"""
Generate formatted documentation.
Args:
content: Document content to format
title: Document title
format_type: Output format ('markdown' or 'html')
permission_manager: Optional permission manager for security checks
Returns:
str: Formatted documentation
Raises:
ValueError: If format_type is not supported
PermissionError: If permission is denied
"""
# Permission check
if permission_manager is not None:
args = {
'content_preview': content[:50] + ('...' if len(content) > 50 else ''),
'title': title,
'format_type': format_type
}
if not permission_manager.request_permission('generate_markdown', args):
raise PermissionError("Permission denied to generate markdown document")
# Input validation
if not content or not isinstance(content, str):
raise ValueError("Content must be a non-empty string")
if not title or not isinstance(title, str):
raise ValueError("Title must be a non-empty string")
if format_type not in ['markdown', 'html']:
raise ValueError("Format type must be 'markdown' or 'html'")
# Create the formatted document
if format_type == 'markdown':
# For markdown, we'll create a simple markdown document with a title
formatted_doc = f"# {title}\n\n{content}"
return formatted_doc
else: # html
# For HTML, we'll convert the markdown to HTML
html_content = markdown.markdown(content)
formatted_doc = f"""<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>{title}</h1>
{html_content}
</body>
</html>"""
return formatted_doc
def extract_context(
query: str,
documents: List[str],
max_length: int = 1000,
permission_manager = None
) -> str:
"""
Extract relevant context from documents based on a query.
This function extracts passages from documents that are most relevant to the query
based on keyword matching. It prioritizes sentences containing query keywords.
Args:
query: Search query to find relevant contexts
documents: List of document contents
max_length: Maximum total length of extracted context
permission_manager: Optional permission manager for security checks
Returns:
str: Extracted context
Raises:
ValueError: If inputs are invalid
PermissionError: If permission is denied
"""
# Permission check
if permission_manager is not None:
args = {
'query': query,
'documents_count': len(documents) if documents else 0,
'max_length': max_length
}
if not permission_manager.request_permission('extract_context', args):
raise PermissionError("Permission denied to extract context from documents")
# Input validation
if not query or not isinstance(query, str):
raise ValueError("Query must be a non-empty string")
if not documents or not isinstance(documents, list):
raise ValueError("Documents must be a non-empty list of strings")
if not all(isinstance(doc, str) for doc in documents):
raise ValueError("All documents must be strings")
if not max_length or not isinstance(max_length, int) or max_length <= 0:
raise ValueError("Max length must be a positive integer")
# Extract keywords from the query
# Normalize by removing common words and punctuation
query = query.lower()
query_words = re.findall(r'\b\w+\b', query)
# Remove common stop words
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'is', 'are', 'of', 'to', 'in',
'for', 'with', 'by', 'at', 'on', 'from'}
keywords = [word for word in query_words if word not in stop_words]
if not keywords:
# Use the original words if all were stop words
keywords = query_words
# Find sentences in documents containing keywords
relevant_sentences = []
for doc in documents:
# Split documents into sentences (simple heuristic)
sentences = re.split(r'(?<=[.!?])\s+', doc)
for sentence in sentences:
# Check for keyword presence
sentence_lower = sentence.lower()
sentence_words = set(re.findall(r'\b\w+\b', sentence_lower))
# Calculate relevance score as the number of keywords found
keywords_found = sum(1 for keyword in keywords if keyword in sentence_words)
if keywords_found > 0:
relevant_sentences.append((sentence, keywords_found))
# Sort sentences by relevance (number of keywords found)
relevant_sentences.sort(key=lambda x: x[1], reverse=True)
# Build context up to max_length
context = []
current_length = 0
for sentence, _ in relevant_sentences:
if current_length + len(sentence) + 1 <= max_length: # +1 for space
context.append(sentence)
current_length += len(sentence) + 1
else:
# If we can't fit the next sentence, stop
break
# If no relevant sentences found, return empty string
if not context:
return ""
return " ".join(context)
def summarize_text(
text: str,
max_length: int = 500,
permission_manager = None
) -> str:
"""
Generate a summary of text using extractive summarization.
This function extracts the most important sentences from the text
based on word frequency analysis.
Args:
text: Text to summarize
max_length: Maximum summary length
permission_manager: Optional permission manager for security checks
Returns:
str: Text summary
Raises:
ValueError: If inputs are invalid
PermissionError: If permission is denied
"""
# Permission check
if permission_manager is not None:
args = {
'text_preview': text[:50] + ('...' if len(text) > 50 else ''),
'text_length': len(text),
'max_length': max_length
}
if not permission_manager.request_permission('summarize_text', args):
raise PermissionError("Permission denied to summarize text")
# Input validation
if not text or not isinstance(text, str):
raise ValueError("Text must be a non-empty string")
if not max_length or not isinstance(max_length, int) or max_length <= 0:
raise ValueError("Max length must be a positive integer")
# If text is already shorter than max_length, return it as is
if len(text) <= max_length:
return text
# Split text into sentences
sentences = re.split(r'(?<=[.!?])\s+', text)
# Handle case with only one sentence
if len(sentences) <= 1:
return text[:max_length] + "..." if len(text) > max_length else text
# Extract all words, lowercase them, and remove stop words
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'is', 'are', 'of', 'to', 'in',
'for', 'with', 'by', 'at', 'on', 'from'}
words = []
for sentence in sentences:
sentence_words = re.findall(r'\b\w+\b', sentence.lower())
words.extend([word for word in sentence_words if word not in stop_words])
# Count word frequencies
word_freq = Counter(words)
# Score sentences based on word frequencies
sentence_scores = []
for i, sentence in enumerate(sentences):
sentence_words = re.findall(r'\b\w+\b', sentence.lower())
score = sum(word_freq[word] for word in sentence_words if word not in stop_words)
# Normalize by sentence length to avoid bias towards longer sentences
if len(sentence_words) > 0:
score = score / len(sentence_words)
sentence_scores.append((i, sentence, score))
# Sort sentences by score
sentence_scores.sort(key=lambda x: x[2], reverse=True)
# Select top sentences and restore original order
top_sentences = sentence_scores[:len(sentences)//3 + 1] # Take ~1/3 of sentences
top_sentences.sort(key=lambda x: x[0]) # Sort by original position
# Build summary up to max_length
summary = []
current_length = 0
for _, sentence, _ in top_sentences:
if current_length + len(sentence) + 1 <= max_length: # +1 for space
summary.append(sentence)
current_length += len(sentence) + 1
else:
break
# If no sentences fit, take the first sentence and truncate
if not summary:
return sentences[0][:max_length] + "..."
return " ".join(summary)