class HybridResponseHandler
Orchestrates the complete workflow for generating hybrid PDF documents that combine LLM text responses with dynamically generated graphics (charts, diagrams, illustrations).
/tf/active/vicechatdev/e-ink-llm/hybrid_response_handler.py
39 - 202
complex
Purpose
HybridResponseHandler is responsible for processing LLM responses that contain special graphic placeholders, generating the requested graphics using an AI graphics generator, and assembling them into a final PDF document. It parses placeholder syntax, coordinates asynchronous graphic generation, handles fallback to standard text-only PDFs when no graphics are present, and manages the complete lifecycle from raw LLM response to final hybrid PDF output. This class is designed for applications that need to enhance text responses with visual elements optimized for e-ink displays.
Source Code
class HybridResponseHandler:
"""Handles the complete hybrid response generation workflow"""
def __init__(self, api_key: str):
self.graphics_generator = GraphicsGenerator(api_key)
self.pdf_generator = HybridPDFGenerator()
self.placeholder_pattern = re.compile(
r'\[GRAPHIC:(\w+):([^:]+):([^\]]+)\]',
re.IGNORECASE
)
async def process_hybrid_response(self,
llm_response: str,
metadata: Dict[str, Any],
output_path: str,
conversation_id: Optional[str] = None,
exchange_number: Optional[int] = None) -> str:
"""
Process a hybrid LLM response and generate final PDF
Args:
llm_response: LLM response containing text and graphic placeholders
metadata: Processing metadata
output_path: Path for final PDF
conversation_id: Session conversation ID
exchange_number: Exchange number in conversation
Returns:
Path to generated hybrid PDF
"""
print(f"🎨 Processing hybrid response with graphics...")
# Step 1: Parse response for graphics placeholders
hybrid_response = self._parse_hybrid_response(llm_response, metadata)
if not hybrid_response.placeholders:
print(f" • No graphics detected, using standard text generation")
# Fall back to standard PDF generation
from pdf_generator import PDFGenerator
standard_generator = PDFGenerator()
return standard_generator.create_response_pdf(
llm_response, "", metadata, output_path, conversation_id, exchange_number
)
print(f" • Found {len(hybrid_response.placeholders)} graphic placeholders")
# Step 2: Generate graphics
generated_graphics = await self._generate_graphics(hybrid_response.placeholders, metadata)
# Step 3: Assemble final PDF
final_pdf_path = await self._assemble_hybrid_pdf(
hybrid_response, generated_graphics, output_path,
conversation_id, exchange_number
)
print(f"✅ Hybrid PDF generated: {Path(final_pdf_path).name}")
return final_pdf_path
def _parse_hybrid_response(self, llm_response: str, metadata: Dict[str, Any]) -> HybridResponse:
"""
Parse LLM response to extract text content and graphic placeholders
Expected placeholder format:
[GRAPHIC:chart:Sales Data Comparison:{"type":"bar","data":[10,20,30],"labels":["A","B","C"]}]
[GRAPHIC:diagram:Process Flow:{"steps":["Step 1","Step 2","Step 3"],"style":"flowchart"}]
[GRAPHIC:illustration:Mathematical Concept:{"concept":"derivatives","style":"educational"}]
"""
placeholders = []
# Find all graphic placeholders
matches = self.placeholder_pattern.findall(llm_response)
for match in matches:
graphic_type, description, params_json = match
try:
parameters = json.loads(params_json)
except json.JSONDecodeError:
print(f"⚠️ Warning: Invalid JSON in graphic placeholder: {params_json}")
parameters = {"description": description}
# Generate unique ID for this graphic
placeholder_text = f"[GRAPHIC:{graphic_type}:{description}:{params_json}]"
graphic_id = hashlib.md5(placeholder_text.encode()).hexdigest()[:8]
placeholder = GraphicPlaceholder(
id=graphic_id,
graphic_type=graphic_type,
description=description,
parameters=parameters,
position_marker=placeholder_text
)
placeholders.append(placeholder)
return HybridResponse(
text_content=llm_response,
graphics=[], # Will be populated after generation
placeholders=placeholders,
metadata=metadata
)
async def _generate_graphics(self,
placeholders: List[GraphicPlaceholder],
metadata: Dict[str, Any]) -> List[GraphicSpec]:
"""Generate all graphics from placeholders"""
generated_graphics = []
for placeholder in placeholders:
print(f" 🎨 Generating {placeholder.graphic_type}: {placeholder.description}")
try:
# Convert placeholder to GraphicSpec
graphic_spec = GraphicSpec(
id=placeholder.id,
type=GraphicType(placeholder.graphic_type),
description=placeholder.description,
parameters=placeholder.parameters,
style_preferences={
"eink_optimized": True,
"high_contrast": True,
"simple_style": True
}
)
# Generate the graphic
generated_graphic = await self.graphics_generator.generate_graphic(graphic_spec)
if generated_graphic:
generated_graphics.append(generated_graphic)
print(f" ✅ Generated {placeholder.graphic_type}")
else:
print(f" ❌ Failed to generate {placeholder.graphic_type}")
except Exception as e:
print(f" ❌ Error generating {placeholder.graphic_type}: {e}")
continue
return generated_graphics
async def _assemble_hybrid_pdf(self,
hybrid_response: HybridResponse,
generated_graphics: List[GraphicSpec],
output_path: str,
conversation_id: Optional[str] = None,
exchange_number: Optional[int] = None) -> str:
"""Assemble final PDF with text and graphics"""
print(f" 📄 Assembling hybrid PDF...")
# Create graphics lookup
graphics_lookup = {graphic.id: graphic for graphic in generated_graphics}
# Generate final PDF
final_path = await self.pdf_generator.create_hybrid_pdf(
text_content=hybrid_response.text_content,
placeholders=hybrid_response.placeholders,
graphics=graphics_lookup,
metadata=hybrid_response.metadata,
output_path=output_path,
conversation_id=conversation_id,
exchange_number=exchange_number
)
return final_path
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
- | - |
Parameter Details
api_key: API key for the graphics generation service (passed to GraphicsGenerator). Required for authenticating requests to generate visual content like charts, diagrams, and illustrations.
Return Value
The constructor returns a HybridResponseHandler instance. The main method process_hybrid_response returns a string containing the file path to the generated hybrid PDF. If no graphics are detected, it falls back to standard PDF generation and returns that path instead.
Class Interface
Methods
__init__(self, api_key: str)
Purpose: Initialize the handler with required dependencies and compile the regex pattern for placeholder parsing
Parameters:
api_key: API key for the graphics generation service
Returns: None (constructor)
async process_hybrid_response(self, llm_response: str, metadata: Dict[str, Any], output_path: str, conversation_id: Optional[str] = None, exchange_number: Optional[int] = None) -> str
Purpose: Main entry point that orchestrates the complete workflow: parse placeholders, generate graphics, and assemble final PDF
Parameters:
llm_response: Raw LLM response text containing graphic placeholders in the format [GRAPHIC:type:description:json_params]metadata: Dictionary containing processing metadata like query, timestamp, model usedoutput_path: File system path where the final PDF should be savedconversation_id: Optional unique identifier for the conversation sessionexchange_number: Optional sequential number of this exchange in the conversation
Returns: String containing the file path to the generated hybrid PDF document
_parse_hybrid_response(self, llm_response: str, metadata: Dict[str, Any]) -> HybridResponse
Purpose: Parse the LLM response to extract text content and identify all graphic placeholders using regex pattern matching
Parameters:
llm_response: Raw LLM response text to parsemetadata: Processing metadata to include in the HybridResponse object
Returns: HybridResponse object containing the original text, empty graphics list (populated later), list of parsed placeholders, and metadata
async _generate_graphics(self, placeholders: List[GraphicPlaceholder], metadata: Dict[str, Any]) -> List[GraphicSpec]
Purpose: Asynchronously generate all graphics from the parsed placeholders using the GraphicsGenerator
Parameters:
placeholders: List of GraphicPlaceholder objects extracted from the LLM responsemetadata: Processing metadata (currently unused but available for future enhancements)
Returns: List of GraphicSpec objects representing successfully generated graphics (failed generations are skipped)
async _assemble_hybrid_pdf(self, hybrid_response: HybridResponse, generated_graphics: List[GraphicSpec], output_path: str, conversation_id: Optional[str] = None, exchange_number: Optional[int] = None) -> str
Purpose: Assemble the final PDF by combining text content with generated graphics at their placeholder positions
Parameters:
hybrid_response: HybridResponse object containing text content and placeholder informationgenerated_graphics: List of successfully generated GraphicSpec objectsoutput_path: File system path for the output PDFconversation_id: Optional conversation identifierexchange_number: Optional exchange number
Returns: String containing the file path to the assembled hybrid PDF
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
graphics_generator |
GraphicsGenerator | Instance of GraphicsGenerator used to create visual content from specifications | instance |
pdf_generator |
HybridPDFGenerator | Instance of HybridPDFGenerator used to assemble text and graphics into final PDF | instance |
placeholder_pattern |
re.Pattern | Compiled regex pattern for matching graphic placeholders in format [GRAPHIC:type:description:json_params], case-insensitive | instance |
Dependencies
jsonreasynciobase64typingdataclassespathlibhashlibgraphics_generatorhybrid_pdf_generatorpdf_generator
Required Imports
import json
import re
import asyncio
import base64
from typing import Dict, Any, List, Optional, Tuple, Union
from dataclasses import dataclass
from pathlib import Path
import hashlib
from graphics_generator import GraphicsGenerator, GraphicSpec, GraphicType
from hybrid_pdf_generator import HybridPDFGenerator
Conditional/Optional Imports
These imports are only needed under specific conditions:
from pdf_generator import PDFGenerator
Condition: only when no graphics placeholders are detected in the LLM response (fallback to standard PDF generation)
Required (conditional)Usage Example
import asyncio
from hybrid_response_handler import HybridResponseHandler
# Initialize handler with API key
handler = HybridResponseHandler(api_key="your-api-key-here")
# LLM response with graphic placeholders
llm_response = '''
Here is the sales analysis:
[GRAPHIC:chart:Sales Data Comparison:{"type":"bar","data":[10,20,30],"labels":["Q1","Q2","Q3"]}]
The data shows steady growth across quarters.
'''
# Metadata for processing
metadata = {
"query": "Show me sales data",
"timestamp": "2024-01-15T10:30:00",
"model": "gpt-4"
}
# Process and generate hybrid PDF
async def main():
pdf_path = await handler.process_hybrid_response(
llm_response=llm_response,
metadata=metadata,
output_path="./output/response.pdf",
conversation_id="conv_123",
exchange_number=1
)
print(f"Generated PDF: {pdf_path}")
asyncio.run(main())
Best Practices
- Always use async/await when calling process_hybrid_response as it performs asynchronous graphics generation
- Ensure the API key provided has sufficient permissions and quota for graphics generation
- The placeholder format must be strictly followed: [GRAPHIC:type:description:json_params] where json_params is valid JSON
- Handle the case where graphics generation may fail - the class will continue processing but skip failed graphics
- The output_path directory must exist and be writable before calling process_hybrid_response
- conversation_id and exchange_number are optional but recommended for tracking multi-turn conversations
- The class automatically falls back to standard PDF generation if no graphics placeholders are found
- Graphics are optimized for e-ink displays with high contrast and simple styles by default
- Each graphic placeholder generates a unique ID based on MD5 hash to prevent duplicates
- Error handling is built-in: failed graphics are logged but don't stop the entire process
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function demo_hybrid_response 83.8% similar
-
class HybridResponse 77.1% similar
-
class HybridPromptEnhancer 71.8% similar
-
class HybridPDFGenerator 70.7% similar
-
class HybridSessionDocTemplate 66.8% similar