-
Notifications
You must be signed in to change notification settings - Fork 1
fix: preserve semantic image evidence tables #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0269b9e
3cd2d6e
e055463
f5b2345
2702fd6
fe0a4f2
e5d0221
b7e6e82
497d9e3
817681b
a9b4be2
7e4cc10
ba71bc1
d8e8ede
b9bba3b
93c8314
fc61f60
a4a6008
1c861f4
bdf3716
7727cb1
497ac12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,9 +14,11 @@ | |
|
|
||
|
|
||
| def _pop_first_env(*names: str) -> str: | ||
| """Read the first configured alias without leaving credentials in the environment.""" | ||
| """Read the first alias, removing quotes preserved by Docker env files.""" | ||
| for name in names: | ||
| value = os.environ.pop(name, "").strip() | ||
| if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}: | ||
| value = value[1:-1] | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| if value: | ||
| return value | ||
| return "" | ||
|
|
@@ -27,7 +29,7 @@ def main() -> None: | |
| provider_key = _pop_first_env("LLM_GATEWAY_API_KEY", "LLM_API_KEY", "NVIDIA_NIM_API_KEY") | ||
| if not provider_key: | ||
| raise SystemExit("LLM_GATEWAY_API_KEY or LLM_API_KEY is required to start the real LLM service") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: CONTEXTUAL_ORCHESTRATOR_TOKEN is now popped (removed) from the environment start.py changed Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| auth_token = os.environ.get("CONTEXTUAL_ORCHESTRATOR_TOKEN", "").strip() | ||
| auth_token = _pop_first_env("CONTEXTUAL_ORCHESTRATOR_TOKEN") | ||
| if not auth_token: | ||
| raise SystemExit("CONTEXTUAL_ORCHESTRATOR_TOKEN is required to start the authenticated LLM service") | ||
|
|
||
|
|
@@ -36,14 +38,14 @@ def main() -> None: | |
| raise SystemExit("LLM_GATEWAY_API_URL or LLM_GATEWAY_URL is required to start the gateway") | ||
| if not provider_url.rstrip("/").endswith("/v1"): | ||
| provider_url = provider_url.rstrip("/") + "/v1" | ||
| raw_limit = os.environ.pop("LLM_GATEWAY_MAX_OUTPUT_TOKENS", "4096").strip() | ||
| raw_limit = _pop_first_env("LLM_GATEWAY_MAX_OUTPUT_TOKENS") or "4096" | ||
| try: | ||
| max_output_tokens = int(raw_limit) | ||
| except ValueError as exc: | ||
| raise SystemExit("LLM_GATEWAY_MAX_OUTPUT_TOKENS must be an integer") from exc | ||
| if not 64 <= max_output_tokens <= 4096: | ||
| raise SystemExit("LLM_GATEWAY_MAX_OUTPUT_TOKENS must be between 64 and 4096") | ||
| raw_body_limit = os.environ.pop("CONTEXTUAL_ORCHESTRATOR_MAX_BODY_BYTES", str(8 * 1024 * 1024)).strip() | ||
| raw_body_limit = _pop_first_env("CONTEXTUAL_ORCHESTRATOR_MAX_BODY_BYTES") or str(8 * 1024 * 1024) | ||
| try: | ||
| max_body_bytes = int(raw_body_limit) | ||
| except ValueError as exc: | ||
|
|
@@ -56,7 +58,7 @@ def main() -> None: | |
| agent["base_url"] = provider_url | ||
| agent["credential_key"] = "LLM_GATEWAY_API_KEY" | ||
| agent.setdefault("provider_protocol", "auto") | ||
| embedding_model = os.environ.get("LLM_GATEWAY_EMBEDDING_MODEL", "").strip() | ||
| embedding_model = _pop_first_env("LLM_GATEWAY_EMBEDDING_MODEL") | ||
| if embedding_model: | ||
| embedding_agents = [ | ||
| agent | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # ADR 0105: Preserve explicit metric scripts in semantic text | ||
|
|
||
| **Status:** Accepted on this PR; not protected-main truth | ||
| **Date:** 2026-08-21 | ||
| **Owners:** LineageWeave ingestion and buyer-surface maintainers | ||
|
|
||
| ## Context | ||
|
|
||
| Source posts commonly encode a unit such as `m<sup>3</sup>`, `m<sub>3</sub>`, | ||
| `m^3`, or `m_3` with HTML or plain-text notation. Dropping the markup changes | ||
| the searchable meaning to `m3`, while treating every numeric `sup` element as | ||
| mathematics would break the existing numeric-footnote contract. Full MathML | ||
| parsing is not yet justified by the current product surface, but the loss of | ||
| explicit unit scripts is a buyer-visible defect. | ||
|
|
||
| MathML 4 defines `msup`, `msub`, and `msubsup` as structural script elements; | ||
| HTML `sup`/`sub` are a permitted lighter-weight notation when detailed | ||
| mathematical markup is not required. This decision therefore adds a bounded | ||
| normalization boundary and keeps the source representation unchanged. | ||
|
|
||
| ## Decision | ||
|
|
||
| 1. Preserve the immutable source body exactly as imported. | ||
| 2. In derived semantic text only, normalize an explicitly bounded metric base | ||
| (`m`, `cm`, `mm`, `km`, or `kg`, optionally preceded by a number) followed | ||
| by numeric `sup`/`sub` markup or plain-text `^`/`_` notation into Unicode | ||
| superscript/subscript digits. For example, `5m<sup>3</sup>` and `5m^3` | ||
| become `5m³`, while `m<sub>3</sub>` and `m_3` become `m₃`. | ||
| 3. Keep ordinary numeric superscripts and caret expressions on prose under the existing footnote | ||
| role contract. Do not infer a mathematical formula from an arbitrary word. | ||
| 4. Apply the same bounded normalization in backend semantic chunks and the | ||
| React buyer display so search text and visible text agree. | ||
| 5. Defer full MathML/LaTeX parsing, expression trees, and ontology term | ||
| creation until an authorized fixture demonstrates a need beyond metric | ||
| scripts. Any such change requires a new ADR and parser contract. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - Search and the buyer popup retain the visible distinction between `m³` and | ||
| `m3` without exposing source HTML to the embedding model. | ||
| - Existing numeric-footnote tests remain unchanged because the bounded metric | ||
| pattern is the only new conversion. | ||
| - The current implementation does not claim to understand arbitrary equations; | ||
| unsupported script markup remains ordinary source text and must not be | ||
| presented as a parsed ontology expression. | ||
|
|
||
| ## References (APA 7th) | ||
|
|
||
| World Wide Web Consortium. (2026). *Mathematical Markup Language (MathML) | ||
| Version 4.0* (W3C Recommendation). https://www.w3.org/TR/mathml4/ |
|
devin-ai-integration[bot] marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,7 @@ function parsePipeDelimitedTable(text: string): string[][] | null { | |
| const rows = text | ||
| .split(/\r?\n/) | ||
| .map((row) => { | ||
| const cells = row.split("|").map((cell) => cell.trim()); | ||
| const cells = row.split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, "|")); | ||
| if (cells[0] === "") cells.shift(); | ||
| if (cells[cells.length - 1] === "") cells.pop(); | ||
| return cells; | ||
|
|
@@ -27,10 +27,20 @@ function parsePipeDelimitedTable(text: string): string[][] | null { | |
| function renderImageText(text: string) { | ||
| const rows = parsePipeDelimitedTable(text); | ||
| if (!rows) return <p>{text}</p>; | ||
| const [header, ...bodyRows] = rows; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: First OCR row now promoted to a table header for headerless image tables
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| return ( | ||
| <table className="post-body-table post-image-text-table"> | ||
| <thead> | ||
| <tr> | ||
| {header.map((cell, cellIndex) => ( | ||
| <th key={`post-image-text-header-${cellIndex}`} scope="col"> | ||
| {cell} | ||
| </th> | ||
| ))} | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {rows.map((row, rowIndex) => ( | ||
| {bodyRows.map((row, rowIndex) => ( | ||
| <tr key={`post-image-text-row-${rowIndex}`}> | ||
| {row.map((cell, cellIndex) => ( | ||
| <td key={`post-image-text-cell-${rowIndex}-${cellIndex}`}>{cell}</td> | ||
|
|
@@ -77,7 +87,14 @@ function renderImageEvidence( | |
| <ol> | ||
| {imageContent.regions.map((region) => ( | ||
| <li key={region.region_index}> | ||
| <span>{region.caption || region.extracted_text || t("Unknown")}</span> | ||
| {region.caption ? <p>{region.caption}</p> : null} | ||
| {region.extracted_text ? ( | ||
| <div className="post-image-region-text"> | ||
| {renderImageText(region.extracted_text)} | ||
| </div> | ||
| ) : region.caption ? null : ( | ||
| t("Unknown") | ||
| )} | ||
| {region.tags.length ? ( | ||
| <small> | ||
| {t("Image tags")}: {region.tags.join(", ")} | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: _pop_first_env strips exactly one matched outer quote pair
start.py strips a single pair of matching outer quotes so Docker env-file quoting (e.g.
KEY='value') no longer corrupts the credential or the/v1URL suffix check. It only removes one layer; a doubly-quoted value like''value''would retain inner quotes, and an intentionally empty quoted value ("") collapses to empty and falls through to the next alias. Real credentials are unlikely to legitimately start and end with the same quote char, so the practical risk is low.Was this helpful? React with 👍 or 👎 to provide feedback.