LLM Document Processing#
ScrapeGraphAI handles documents that exceed a model's context window through a four-stage pipeline: token budget resolution, token-budget-aware chunking in ParseNode, parallel per-chunk inference via LangChain's RunnableParallel in GenerateAnswerNode, and merge-step synthesis where a final LLM call consolidates chunk results into a single coherent answer.
This pattern is used by all scraping graphs (e.g., SmartScraperGraph) and sidesteps truncation errors without requiring retrieval-augmented generation (RAG) for cases where the full document must be scanned.
Stage 1: Token Budget β models_tokens + AbstractGraph#
Every graph initializes an LLM via AbstractGraph._create_llm(). During initialization, the model's maximum token count (model_token) is resolved from the models_tokens registry β a nested dict keyed by provider and model name (e.g., openai/gpt-4o β 128000) β and stored as self.model_token . If a model is not in the registry, it defaults to 8192 tokens . This value is then passed directly to ParseNode as chunk_size .
Stage 2: Chunking β ParseNode + split_text_into_chunks#
ParseNode.execute() splits the fetched document into token-bounded chunks. The effective chunk size is reduced to leave room for prompt overhead:
- HTML mode (default):
chunk_size - 250 - Non-HTML mode:
min(chunk_size - 500, chunk_size * 0.8)
Chunking is delegated to split_text_into_chunks(), which defaults to the semchunk library for semantically coherent splits. It applies a further 10% reduction (chunk_size * 0.9) before calling semchunk.chunk() . Token counting is standardized across all LLM providers using tiktoken with the gpt-4o encoding, regardless of the actual model in use .
The resulting List[str] chunks are written to state["parsed_doc"] .
Stage 3: Parallel Inference β GenerateAnswerNode#
GenerateAnswerNode.execute() branches on chunk count:
- Single chunk (
len(doc) == 1): UsesTEMPLATE_NO_CHUNKS/TEMPLATE_NO_CHUNKS_MDβ a single prompt with the full content and user question . - Multiple chunks: Builds a
chains_dictwhere each key ischunk1,chunk2, β¦ and each value is aPromptTemplate | llmchain usingTEMPLATE_CHUNKS/TEMPLATE_CHUNKS_MDβ the chunk's content is embedded as a partial variable, so the only runtime input is{question}. All chains are wrapped in a LangChainRunnableParallel, which dispatches all chunk LLM calls concurrently and returns a dict of results .
Stage 4: Merge#
After parallel chunk processing, batch_results (the dict of per-chunk answers) is fed into a final merge chain using TEMPLATE_MERGE / TEMPLATE_MERGE_MD . The merge prompt explicitly instructs the LLM to:
- Combine all chunk answers into one response without repetitions
- Respect any maximum item count from the user's instructions
- Return valid JSON conforming to the output schema
All chains (chunk and merge) run with a configurable timeout, defaulting to 480 seconds . Timeout and JSON parse errors return structured error dicts rather than raising .
Key Files#
| File | Role |
|---|---|
helpers/models_tokens.py | Token limit registry per provider/model |
graphs/abstract_graph.py | Resolves model_token; passes it to nodes |
nodes/parse_node.py | Splits document into token-bounded chunks |
utils/split_text_into_chunks.py | semchunk-based chunker with token counter |
utils/tokenizer.py | num_tokens_calculus() via tiktoken/gpt-4o |
nodes/generate_answer_node.py | Parallel chunk inference + merge step |
prompts/generate_answer_node_prompts.py | TEMPLATE_CHUNKS, TEMPLATE_NO_CHUNKS, TEMPLATE_MERGE |