symfonic.capabilities.knowledge.attachments¶
attachments ¶
The attachment bridge: file bytes, extractors, and OCR behind one port.
The extractors themselves stay where they are. What moves here is the part that was never really theirs: the decision about what is allowed to reach them and what is allowed to leave. Format parsers are assumed compromise-prone (AS-ING-5), so this module treats one as a hostile black box behind a single-method port — bounded on the way in, exception-wrapped on the way out, and never handed a filesystem path or a credential.
Input bytes and zip declarations are checked before dispatch (AS-ING-3/6). Raw bytes limit upload size; declared size, ratio, member count, and nesting bound Office zips. The final text ceiling applies across all attachments.
.. note::
:data:ExtractionLimits.max_total_chars bounds what this bridge emits;
the prompt compiler applies its own, much smaller, learned-content cap
(RenderPolicy.max_learned_chars, 500 by default) and drops any block
above it. A composition root wiring this contribution must raise that cap to
at least the ceiling it sets here, or every attachment set will compile to
nothing but a diagnostic.
AttachmentRef
dataclass
¶
One attachment as it arrived: an untrusted name and untrusted bytes.
AttachmentSource
dataclass
¶
AttachmentSource(refs: Sequence[AttachmentRef], extractor: TextExtractor, limits: ExtractionLimits = DEFAULT_LIMITS, on_rejection: Literal['omit', 'raise'] = 'omit', scope_aware: bool = False, offline_safe: bool = True)
A :class:~.contracts.ContextSource over a fixed set of attachments.
select ¶
Extract the attachments, and say why each omitted one was omitted.
on_rejection='raise' makes a refusal loud by ending the read;
'omit' — the default, and the right one when four of five files
should still reach the model — used to make it silent. It is neither
now: the omission and its reason are carried out on the selection, so a
composition root can surface "report.pdf: parser failed" the way the
prompting capability surfaces a PromptDiagnostic, rather than
leaving an operator to guess why a file they attached is absent.
Source code in src/symfonic/capabilities/knowledge/attachments.py
ExtractedAttachment
dataclass
¶
One attachment after extraction, carrying an opaque label.
ExtractionLimits
dataclass
¶
ExtractionLimits(max_bytes: int = 8 * 1024 * 1024, max_uncompressed_bytes: int = 8 * 1024 * 1024, max_expansion_ratio: float = 100.0, max_members: int = 1024, max_nesting_depth: int = 1, max_pages: int = 20, max_text_chars: int = 50000, max_total_chars: int = 120000)
The ceilings applied around a parser call.
TextExtractor ¶
Bases: Protocol
The port a format extractor (PDF, office, image OCR) satisfies.
One method, and it receives bytes rather than a path. That is deliberate: an extractor that took a path would need the framework to write untrusted bytes to a name derived from an untrusted label, which is the AS-ING-2 failure with extra steps.
attachment_contribution ¶
attachment_contribution(contribution_id: str, source: AttachmentSource, *, order: int = 0, scope: str | None = None) -> ContextContribution
Declare this turn's attachments as volatile (L2), session-tier context.
Source code in src/symfonic/capabilities/knowledge/attachments.py
extract_attachment ¶
extract_attachment(ref: AttachmentRef, extractor: TextExtractor, limits: ExtractionLimits = DEFAULT_LIMITS) -> ExtractedAttachment
Extract one attachment's text under the ingestion ceilings.
Raises :class:~.errors.IngestionRejected when a guard fires and
:class:~.errors.ExtractionFailed when the parser itself loses. Nothing a
third-party parser raises escapes this function untyped.
Source code in src/symfonic/capabilities/knowledge/attachments.py
normalise_extracted ¶
Unix line endings, control characters stripped, trailing space removed.