Add Biotech Agentic Analyst demo - #256
Conversation
📝 WalkthroughWalkthroughAdded a Streamlit application that uploads scientific PDFs, extracts figures with Mistral OCR, analyzes them with CrewAI, and displays structured figure intelligence. ChangesBiotech figure analysis
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant StreamlitApp
participant MistralOCR
participant ScienceFlow
participant FigureAnalystCrew
StreamlitApp->>MistralOCR: Upload PDF and request OCR annotations
MistralOCR-->>StreamlitApp: Return extracted figures and thumbnails
StreamlitApp->>ScienceFlow: Submit extracted figures
ScienceFlow->>FigureAnalystCrew: Analyze figure chunks with image paths
FigureAnalystCrew-->>ScienceFlow: Return FigureIntelligenceList
ScienceFlow-->>StreamlitApp: Display stored figure intelligence
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (5)
biotech-agentic-analyst/app.py (1)
80-88: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRendering every page at 2x scale is memory heavy.
_render_pdf_pagesrasterizes the complete PDF at a 2.0 matrix and keeps every PNG in the Streamlit data cache, keyed by the full file bytes. A 100-page paper produces 100 high-resolution PNGs before the user starts the analysis. Limit the preview to the first N pages, or lower the matrix scale.♻️ Proposed refactor
+_PREVIEW_MAX_PAGES = 10 +_PREVIEW_SCALE = 1.5 + `@st.cache_data`(show_spinner=False) def _render_pdf_pages(pdf_bytes: bytes) -> list[bytes]: import fitz doc = fitz.open(stream=pdf_bytes, filetype="pdf") - mat = fitz.Matrix(2.0, 2.0) - pages = [doc[i].get_pixmap(matrix=mat).tobytes("png") for i in range(len(doc))] + mat = fitz.Matrix(_PREVIEW_SCALE, _PREVIEW_SCALE) + limit = min(len(doc), _PREVIEW_MAX_PAGES) + pages = [doc[i].get_pixmap(matrix=mat).tobytes("png") for i in range(limit)] doc.close() return pages🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/app.py` around lines 80 - 88, Reduce the memory usage in _render_pdf_pages by limiting rasterization to a small first-page preview and/or lowering the fitz.Matrix scale from 2.0. Ensure the function no longer renders and caches every PDF page while preserving its existing list-of-PNGs return contract.biotech-agentic-analyst/mistral_ocr_pipeline/pipeline.py (1)
101-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLog the delete failure instead of silently discarding it.
The
except Exception: passblock hides storage-cleanup failures. Uploaded papers can then stay in Mistral file storage without any signal. Log the exception at warning level.♻️ Proposed refactor
finally: # Don't leave analyzed papers sitting in Mistral file storage. try: client.files.delete(file_id=uploaded.id) - except Exception: - pass + except Exception as exc: # noqa: BLE001 - cleanup must not mask OCR errors + logger.warning("Failed to delete uploaded file %s: %s", uploaded.id, exc)Add the logger at the top of the module:
import logging logger = logging.getLogger(__name__)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/mistral_ocr_pipeline/pipeline.py` around lines 101 - 106, Replace the silent exception handling in the cleanup block of the pipeline’s finally path with a warning-level log that includes the deletion failure details; define and reuse a module-level logger if one is not already available, while preserving the existing cleanup attempt.Source: Linters/SAST tools
biotech-agentic-analyst/README.md (1)
26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the project directory and the optional model override.
Two clarifications help the setup. First, "root directory" is ambiguous in this monorepo; state that the
.envfile goes inbiotech-agentic-analyst/. Second,biotech-agentic-analyst/flow/crews/figure_analyst/crew.pyLine 15 reads an optionalFIGURE_ANALYST_MODELvariable. Add it to the example as an optional entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/README.md` around lines 26 - 32, Update the README’s “Create .env File” section to specify that the file belongs in the biotech-agentic-analyst/ directory, and add FIGURE_ANALYST_MODEL as an optional environment variable alongside MISTRAL_API_KEY, matching the variable read by the figure analyst crew.biotech-agentic-analyst/flow/crews/figure_analyst/config/tasks.yaml (1)
25-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRemove the contradictory instruction for
quantitative_highlights.The text demands a minimum of 2 items and then allows an empty list. The model can resolve this conflict by inventing values. State the empty-list rule only.
♻️ Proposed wording
- - quantitative_highlights: list all numerical result, p-value, fold-change, or percentage - explicitly readable from the figure or caption — minimum 2 items per figure; - if none are present, return an empty list + - quantitative_highlights: list every numerical result, p-value, fold-change, or percentage + explicitly readable from the figure or caption. Do not infer or estimate values. + If no numerical value is present, return an empty list.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/flow/crews/figure_analyst/config/tasks.yaml` around lines 25 - 27, Update the quantitative_highlights instruction in the task configuration to remove the minimum-two-items requirement and retain only the rule to list explicitly readable numerical results, p-values, fold-changes, or percentages, returning an empty list when none are present.biotech-agentic-analyst/flow/science_flow.py (1)
82-101: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRetry every exception type with a fixed delay.
The handler retries any
Exception. Authentication errors, invalid model ids, and Pydantic validation errors are not transient. Each one costs two extra calls and 6 seconds of blocking wait on the Streamlit thread. Add exponential backoff, and stop early for non-transient errors. Uselogginginstead of🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/flow/science_flow.py` around lines 82 - 101, The retry handler around FigureAnalystCrew().crew().kickoff must retry only transient failures, immediately propagate authentication, invalid-model, and Pydantic validation errors, and use exponential rather than fixed backoff. Replace the print call with appropriately leveled logging while preserving last_exc handling for exhausted transient retries.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@biotech-agentic-analyst/app.py`:
- Around line 197-205: Escape all model- and PDF-derived text before rendering
it with unsafe_allow_html=True: update _pill, _axis_pill, and the key_finding
markup to pass each interpolated value through html.escape, including the
corresponding rendering block around lines 231-259. Preserve the existing pill
styling and layout while ensuring chart labels, axis values, and fi.key_finding
cannot inject HTML.
In `@biotech-agentic-analyst/flow/science_flow.py`:
- Around line 103-110: Update the chunk-failure handling in the science flow
before assigning figure_intelligences so failed chunks set self.state.error and
downgrade self.state.quality from "good" while preserving the existing skip
behavior. Also update the app status rendering to surface flow.state.error
whenever quality is not "poor", ensuring incomplete analysis is communicated to
the user.
In `@biotech-agentic-analyst/mistral_ocr_pipeline/pipeline.py`:
- Around line 196-215: Update the fallback page resolution in the loop over
all_figs so raw_page_number is consistently treated as 1-based input and
converted to a 0-based index. Change the next(...) fallback default from
raw_page_number to the corresponding 0-based interpretation, preserving the
existing candidate validation and page_num = real_index + 1 behavior.
In `@biotech-agentic-analyst/models.py`:
- Around line 32-47: Make is_quantitative_chart and passes_sanity_check in the
model’s field definitions required by removing their True defaults, or
explicitly default them to False if omission must represent rejection. Preserve
their existing descriptions and ensure absent classifications cannot be
interpreted as accepted.
In `@biotech-agentic-analyst/pyproject.toml`:
- Around line 6-8: Update the requires-python declaration in the project
metadata to constrain supported versions to Python 3.13 through below 3.14,
keeping the existing CrewAI dependency unchanged.
In `@biotech-agentic-analyst/README.md`:
- Around line 41-46: Update the Windows PowerShell setup commands in the README
to invoke the PowerShell-specific virtual-environment activation script,
replacing `.venv\Scripts\activate` with `.venv\Scripts\Activate.ps1` while
keeping `uv sync` unchanged.
In `@biotech-agentic-analyst/utils.py`:
- Around line 16-20: Update the image-decoding helper containing the
base64.b64decode and Image.open calls to invoke the returned image’s load()
method inside the existing try block before returning it. Preserve the except
behavior so truncated or corrupt payloads return None.
---
Nitpick comments:
In `@biotech-agentic-analyst/app.py`:
- Around line 80-88: Reduce the memory usage in _render_pdf_pages by limiting
rasterization to a small first-page preview and/or lowering the fitz.Matrix
scale from 2.0. Ensure the function no longer renders and caches every PDF page
while preserving its existing list-of-PNGs return contract.
In `@biotech-agentic-analyst/flow/crews/figure_analyst/config/tasks.yaml`:
- Around line 25-27: Update the quantitative_highlights instruction in the task
configuration to remove the minimum-two-items requirement and retain only the
rule to list explicitly readable numerical results, p-values, fold-changes, or
percentages, returning an empty list when none are present.
In `@biotech-agentic-analyst/flow/science_flow.py`:
- Around line 82-101: The retry handler around
FigureAnalystCrew().crew().kickoff must retry only transient failures,
immediately propagate authentication, invalid-model, and Pydantic validation
errors, and use exponential rather than fixed backoff. Replace the print call
with appropriately leveled logging while preserving last_exc handling for
exhausted transient retries.
In `@biotech-agentic-analyst/mistral_ocr_pipeline/pipeline.py`:
- Around line 101-106: Replace the silent exception handling in the cleanup
block of the pipeline’s finally path with a warning-level log that includes the
deletion failure details; define and reuse a module-level logger if one is not
already available, while preserving the existing cleanup attempt.
In `@biotech-agentic-analyst/README.md`:
- Around line 26-32: Update the README’s “Create .env File” section to specify
that the file belongs in the biotech-agentic-analyst/ directory, and add
FIGURE_ANALYST_MODEL as an optional environment variable alongside
MISTRAL_API_KEY, matching the variable read by the figure analyst crew.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b9360988-05e9-4978-9e37-d0b618ed058c
⛔ Files ignored due to path filters (2)
biotech-agentic-analyst/assets/Journal-of-Biology-and-Nature.pdfis excluded by!**/*.pdfbiotech-agentic-analyst/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (16)
biotech-agentic-analyst/.env.examplebiotech-agentic-analyst/README.mdbiotech-agentic-analyst/app.pybiotech-agentic-analyst/flow/__init__.pybiotech-agentic-analyst/flow/crews/__init__.pybiotech-agentic-analyst/flow/crews/figure_analyst/__init__.pybiotech-agentic-analyst/flow/crews/figure_analyst/config/agents.yamlbiotech-agentic-analyst/flow/crews/figure_analyst/config/tasks.yamlbiotech-agentic-analyst/flow/crews/figure_analyst/crew.pybiotech-agentic-analyst/flow/science_flow.pybiotech-agentic-analyst/flow/state.pybiotech-agentic-analyst/mistral_ocr_pipeline/__init__.pybiotech-agentic-analyst/mistral_ocr_pipeline/pipeline.pybiotech-agentic-analyst/models.pybiotech-agentic-analyst/pyproject.tomlbiotech-agentic-analyst/utils.py
| chart_types = [ct.strip() for ct in fi.chart_type.split(",") if ct.strip()] | ||
| pills_html = " ".join( | ||
| _pill(ct, *_TAG_COLORS[i % len(_TAG_COLORS)]) | ||
| for i, ct in enumerate(chart_types[:3]) | ||
| ) | ||
| st.markdown( | ||
| f"{pills_html} <strong>{fi.key_finding}</strong>", | ||
| unsafe_allow_html=True, | ||
| ) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Escape model-generated text before you insert it into HTML.
_pill, _axis_pill, and the key_finding markdown at Lines 202-205 interpolate strings that originate from the uploaded PDF and from the LLM. All of these render with unsafe_allow_html=True. A crafted PDF can therefore inject markup such as <img src=x onerror=...> or a full-width styled overlay into the results page.
Escape every interpolated value with html.escape.
🛡️ Proposed fix
+import html
+
def _pill(text: str, bg: str, fg: str) -> str:
return (
f'<span style="background:{bg};color:{fg};padding:3px 10px;'
f"border-radius:12px;font-size:0.8em;font-weight:500;"
- f'margin:2px;display:inline-block">{text}</span>'
+ f'margin:2px;display:inline-block">{html.escape(text)}</span>'
)Apply the same change to the value argument in _axis_pill and to fi.key_finding at Line 203.
Also applies to: 231-259
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/app.py` around lines 197 - 205, Escape all model- and
PDF-derived text before rendering it with unsafe_allow_html=True: update _pill,
_axis_pill, and the key_finding markup to pass each interpolated value through
html.escape, including the corresponding rendering block around lines 231-259.
Preserve the existing pill styling and layout while ensuring chart labels, axis
values, and fi.key_finding cannot inject HTML.
| if last_exc is not None: | ||
| print( | ||
| f"[ScienceFlow] Skipping chunk {chunk_ids} after " | ||
| f"{1 + _MAX_RETRIES} attempts: {last_exc}" | ||
| ) | ||
|
|
||
| self.state.figure_intelligences = all_intelligences | ||
| return "analyzed" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Report chunk failures in the flow state.
If every attempt for a chunk fails, the code prints a message and continues. self.state.error stays None and self.state.quality stays "good". biotech-agentic-analyst/app.py Lines 395-408 then renders a success status and a figure count that silently omits the failed figures. The user cannot tell that analysis was incomplete.
Record the failure in the state so the UI can warn the user.
🐛 Proposed fix
`@listen`("analyze")
def analyze_figures(self):
all_intelligences = []
+ failed_ids: list[str] = []
@@
if last_exc is not None:
print(
f"[ScienceFlow] Skipping chunk {chunk_ids} after "
f"{1 + _MAX_RETRIES} attempts: {last_exc}"
)
+ failed_ids.extend(chunk_ids)
self.state.figure_intelligences = all_intelligences
+ if failed_ids:
+ self.state.error = (
+ f"Analysis failed for {len(failed_ids)} figure(s): "
+ f"{', '.join(failed_ids)}"
+ )
return "analyzed"biotech-agentic-analyst/app.py must also surface flow.state.error when quality is not "poor".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/flow/science_flow.py` around lines 103 - 110, Update
the chunk-failure handling in the science flow before assigning
figure_intelligences so failed chunks set self.state.error and downgrade
self.state.quality from "good" while preserving the existing skip behavior. Also
update the app status rendering to surface flow.state.error whenever quality is
not "poor", ensuring incomplete analysis is communicated to the user.
| for fig in all_figs: | ||
| resolved_index = _resolve_page_index(fig, pages) | ||
| if resolved_index is not None: | ||
| real_index = resolved_index | ||
| else: | ||
| # Model's self-reported page_number convention (0- vs. | ||
| # 1-based); try both. | ||
| raw_page_number = fig.get("page_number", 0) | ||
| candidates = [raw_page_number, raw_page_number - 1] | ||
| real_index = next( | ||
| ( | ||
| c | ||
| for c in candidates | ||
| if page_cursor.get(c, 0) < len(page_images.get(c, [])) | ||
| ), | ||
| raw_page_number, | ||
| ) | ||
|
|
||
| thumb = _take_image(real_index) | ||
| page_num = real_index + 1 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fallback page resolution mixes 1-based and 0-based page numbers.
The schema at Lines 27-37 instructs the model to return a 1-based page_number. The fallback treats raw_page_number as a 0-based index in the candidate list, and the final next(...) default is raw_page_number itself. Line 215 then computes page_num = real_index + 1. If neither candidate has a spare image, the displayed page number is one higher than the model reported.
Set the default to the 0-based interpretation so the two branches agree.
🐛 Proposed fix
raw_page_number = fig.get("page_number", 0)
- candidates = [raw_page_number, raw_page_number - 1]
+ # Schema asks for a 1-based page number; prefer the 0-based
+ # conversion and keep the raw value as a tolerance fallback.
+ candidates = [raw_page_number - 1, raw_page_number]
real_index = next(
(
c
for c in candidates
if page_cursor.get(c, 0) < len(page_images.get(c, []))
),
- raw_page_number,
+ max(raw_page_number - 1, 0),
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for fig in all_figs: | |
| resolved_index = _resolve_page_index(fig, pages) | |
| if resolved_index is not None: | |
| real_index = resolved_index | |
| else: | |
| # Model's self-reported page_number convention (0- vs. | |
| # 1-based); try both. | |
| raw_page_number = fig.get("page_number", 0) | |
| candidates = [raw_page_number, raw_page_number - 1] | |
| real_index = next( | |
| ( | |
| c | |
| for c in candidates | |
| if page_cursor.get(c, 0) < len(page_images.get(c, [])) | |
| ), | |
| raw_page_number, | |
| ) | |
| thumb = _take_image(real_index) | |
| page_num = real_index + 1 | |
| for fig in all_figs: | |
| resolved_index = _resolve_page_index(fig, pages) | |
| if resolved_index is not None: | |
| real_index = resolved_index | |
| else: | |
| # Model's self-reported page_number convention (0- vs. | |
| # 1-based); try both. | |
| raw_page_number = fig.get("page_number", 0) | |
| # Schema asks for a 1-based page number; prefer the 0-based | |
| # conversion and keep the raw value as a tolerance fallback. | |
| candidates = [raw_page_number - 1, raw_page_number] | |
| real_index = next( | |
| ( | |
| c | |
| for c in candidates | |
| if page_cursor.get(c, 0) < len(page_images.get(c, [])) | |
| ), | |
| max(raw_page_number - 1, 0), | |
| ) | |
| thumb = _take_image(real_index) | |
| page_num = real_index + 1 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/mistral_ocr_pipeline/pipeline.py` around lines 196 -
215, Update the fallback page resolution in the loop over all_figs so
raw_page_number is consistently treated as 1-based input and converted to a
0-based index. Change the next(...) fallback default from raw_page_number to the
corresponding 0-based interpretation, preserving the existing candidate
validation and page_num = real_index + 1 behavior.
| is_quantitative_chart: bool = Field( | ||
| default=True, | ||
| description=( | ||
| "True only for statistical/data charts (bar, line, scatter, pie, box plot, " | ||
| "histogram, heatmap, dose-response curve, survival curve, forest plot, etc). " | ||
| "False for micrographs, Western blots, photographs, illustrations, or any " | ||
| "image that is not itself a plotted chart of data." | ||
| ), | ||
| ) | ||
| passes_sanity_check: bool = Field( | ||
| default=True, | ||
| description=( | ||
| "True only if the image is clear/legible (not blurry, cropped, or low-res) " | ||
| "AND its visual content matches the caption and data_summary." | ||
| ), | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
curl -fsSL https://docs.pydantic.dev/2.13/concepts/fields/ |
grep -F "still required"Repository: patchy631/ai-engineering-hub
Length of output: 946
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
fd -a 'models.py|app.py' . | sed 's#^\./##'
echo
echo "== relevant models.py lines =="
sed -n '1,90p' biotech-agentic-analyst/models.py
echo
echo "== relevant app.py filtering/rendering lines =="
sed -n '130,280p' biotech-agentic-analyst/app.py
echo
echo "== search FigureIntelligence construction/usage =="
rg -n "FigureIntelligence|model_validate|parse_obj|is_quantitative_chart|passes_sanity_check" biotech-agentic-analyst pyproject.toml requirements*.txt 2>/dev/null || trueRepository: patchy631/ai-engineering-hub
Length of output: 13940
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
try:
import inspect
import pydantic
from pydantic import BaseModel, Field
class Example(BaseModel):
a: bool = Field(default=True)
b: bool = Field()
class Missing(BaseModel):
b: bool = Field(default=True)
print("pydantic=", getattr(pydantic, "__version__", "unknown"))
try:
Example()
print("Example() =", Example().model_dump())
except Exception as exc:
print("Example() exception:", type(exc).__name__, str(exc).splitlines()[0])
try:
print("Example(a=False) =", Example(a=False).model_dump())
except Exception as exc:
print("Example(a=False) exception:", type(exc).__name__, str(exc).splitlines()[0])
print("Missing('text') =", Missing("text").model_dump())
print("is field 'b' required?", Example.model_fields["b"].is_required())
print("model_dump behavior b default:", pydantic.BaseModel.model_computed_fields)
source = inspect.getsource(pydantic.BaseModel.model_dump)
print("has mode='python' in model_dump:", "mode='python'" in source)
except Exception as exc:
print("ERROR:", type(exc).__name__, str(exc))
PYRepository: patchy631/ai-engineering-hub
Length of output: 220
Require explicit quality classifications.
If the agent omits is_quantitative_chart or passes_sanity_check, the current bool fields remain optional with True defaults, so incomplete or skipped analysis can render as accepted. Make both required unless absent fields should default to rejected.
Proposed fix
is_quantitative_chart: bool = Field(
- default=True,
description=(
@@
passes_sanity_check: bool = Field(
- default=True,
description=(📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| is_quantitative_chart: bool = Field( | |
| default=True, | |
| description=( | |
| "True only for statistical/data charts (bar, line, scatter, pie, box plot, " | |
| "histogram, heatmap, dose-response curve, survival curve, forest plot, etc). " | |
| "False for micrographs, Western blots, photographs, illustrations, or any " | |
| "image that is not itself a plotted chart of data." | |
| ), | |
| ) | |
| passes_sanity_check: bool = Field( | |
| default=True, | |
| description=( | |
| "True only if the image is clear/legible (not blurry, cropped, or low-res) " | |
| "AND its visual content matches the caption and data_summary." | |
| ), | |
| ) | |
| is_quantitative_chart: bool = Field( | |
| description=( | |
| "True only for statistical/data charts (bar, line, scatter, pie, box plot, " | |
| "histogram, heatmap, dose-response curve, survival curve, forest plot, etc). " | |
| "False for micrographs, Western blots, photographs, illustrations, or any " | |
| "image that is not itself a plotted chart of data." | |
| ), | |
| ) | |
| passes_sanity_check: bool = Field( | |
| description=( | |
| "True only if the image is clear/legible (not blurry, cropped, or low-res) " | |
| "AND its visual content matches the caption and data_summary." | |
| ), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/models.py` around lines 32 - 47, Make
is_quantitative_chart and passes_sanity_check in the model’s field definitions
required by removing their True defaults, or explicitly default them to False if
omission must represent rejection. Preserve their existing descriptions and
ensure absent classifications cannot be interpreted as accepted.
| requires-python = ">=3.13" | ||
| dependencies = [ | ||
| "crewai[litellm]>=1.6.1", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python - <<'PY'
import json
from urllib.request import urlopen
with urlopen("https://pypi.org/pypi/crewai/json", timeout=20) as response:
metadata = json.load(response)
print("CrewAI requires-python:", metadata["info"]["requires_python"])
PYRepository: patchy631/ai-engineering-hub
Length of output: 2795
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repository files around pyproject:"
git ls-files | rg '(^|/)pyproject\.toml$' || true
echo
echo "biotech-agentic-analyst/pyproject.toml relevant lines:"
if [ -f biotech-agentic-analyst/pyproject.toml ]; then
nl -ba biotech-agentic-analyst/pyproject.toml | sed -n '1,60p'
else
echo "missing biotech-agentic-analyst/pyproject.toml"
fi
echo
echo "Search for crewai dependency pins across repository:"
rg -n "crewai\s*(\[|@|>=|~=|=)" . --glob '*.toml' --glob 'requirements*.txt' --glob 'poetry.lock' --glob 'uv.lock' --glob 'Pipfile' --glob 'setup.py' --glob 'setup.cfg' || trueRepository: patchy631/ai-engineering-hub
Length of output: 2116
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "biotech-agentic-analyst/pyproject.toml lines:"
if [ -f biotech-agentic-analyst/pyproject.toml ]; then
awk '{printf "%6d\t%s\n", NR, $0}' biotech-agentic-analyst/pyproject.toml | sed -n '1,80p'
else
echo "missing biotech-agentic-analyst/pyproject.toml"
fi
echo
echo "Search for crewai dependency pins across repository:"
rg -n "crewai\s*(\[|@|>=|~=|=)" . --glob '*.toml' --glob 'requirements*.txt' --glob 'poetry.lock' --glob 'uv.lock' --glob 'Pipfile' --glob 'setup.py' --glob 'setup.cfg' || true
echo
echo "Python version constraint metadata for crewai==1.6.1:"
python3 - <<'PY'
import json, os, tempfile
from urllib.request import urlopen, urlretrieve
pkg = "crewai"
version = "1.6.1"
url = f"https://pypi.org/pypi/{pkg}/{version}/json"
print(f"Fetching {url}")
with urlopen(url) as r:
data = json.load(r)
info = data["info"]
print("requires_python:", info.get("requires_python"))
print("python_requires:", info.get("python_requires"))
for rel in info.get("release_url", "").split("release_url",1)[0].split("/")[-2:-1] or []:
pass
PYRepository: patchy631/ai-engineering-hub
Length of output: 4538
🌐 Web query:
PyPI crewai 1.6.1 requires-python
💡 Result:
The Python version requirement for crewai version 1.6.1 is >=3.10, <3.14 [1]. This means the package supports Python versions 3.10, 3.11, 3.12, and 3.13, but does not support versions 3.9 or earlier, nor versions 3.14 or later [1].
Citations:
🌐 Web query:
crewai 1.6.1 Python version requirements
💡 Result:
For version 1.6.1 of the crewAI Python package, the required Python version is >=3.10 and <3.14 [1][2][3]. This requirement is explicitly defined in the project's configuration files (such as pyproject.toml) and documented on the Python Package Index (PyPI) for that specific version [1][2].
Citations:
- 1: https://pypi.org/project/crewai/1.6.1/
- 2: https://github.com/crewAIInc/crewAI/blob/81bd81e5f5e78be0485a771af74fe13fa5c8a1fd/pyproject.toml
- 3: https://pypi.org/project/crewai-tools/1.6.1/
Cap the declared Python range.
requires-python = ">=3.13" allows Python 3.14+, but the pinned crewai[litellm]>=1.6.1 resolves to a release that requires >=3.10, <3.14. Set this project to requires-python = ">=3.13,<3.14" until the selected CrewAI release supports Python 3.14.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/pyproject.toml` around lines 6 - 8, Update the
requires-python declaration in the project metadata to constrain supported
versions to Python 3.13 through below 3.14, keeping the existing CrewAI
dependency unchanged.
| On Windows (PowerShell): | ||
|
|
||
| ```powershell | ||
| uv sync | ||
| .venv\Scripts\activate | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the PowerShell activation command.
In PowerShell, .venv\Scripts\activate resolves to activate.bat, which does not activate the environment in the current PowerShell session. Use the PowerShell script.
📝 Proposed fix
```powershell
uv sync
-.venv\Scripts\activate
+.venv\Scripts\Activate.ps1</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
On Windows (PowerShell):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/README.md` around lines 41 - 46, Update the Windows
PowerShell setup commands in the README to invoke the PowerShell-specific
virtual-environment activation script, replacing `.venv\Scripts\activate` with
`.venv\Scripts\Activate.ps1` while keeping `uv sync` unchanged.
| try: | ||
| data = base64.b64decode(b64_str) | ||
| return Image.open(io.BytesIO(data)) | ||
| except Exception: | ||
| return None |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Image.open is lazy, so invalid image data escapes this handler.
Image.open reads only the header. It does not decode the pixel data. If the payload is truncated or corrupt, the error is raised later during rendering in st.image, not here. The results page in biotech-agentic-analyst/app.py Line 435 runs outside any try block, so that error reaches the user as an unhandled exception.
Call load() inside the try so the function returns None for any invalid payload.
🐛 Proposed fix
try:
data = base64.b64decode(b64_str)
- return Image.open(io.BytesIO(data))
+ img = Image.open(io.BytesIO(data))
+ img.load()
+ return img
except Exception:
return None📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try: | |
| data = base64.b64decode(b64_str) | |
| return Image.open(io.BytesIO(data)) | |
| except Exception: | |
| return None | |
| try: | |
| data = base64.b64decode(b64_str) | |
| img = Image.open(io.BytesIO(data)) | |
| img.load() | |
| return img | |
| except Exception: | |
| return None |
🧰 Tools
🪛 Ruff (0.16.1)
[warning] 19-19: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biotech-agentic-analyst/utils.py` around lines 16 - 20, Update the
image-decoding helper containing the base64.b64decode and Image.open calls to
invoke the returned image’s load() method inside the existing try block before
returning it. Preserve the except behavior so truncated or corrupt payloads
return None.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
biotech-agentic-analyst/app.py (3)
409-428: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPersist partial-failure status through reruns.
The
(partial failures)label and warning exist only during the flow step beforest.rerun(). The storedlabelis added tost.session_state.pipeline_steps, so reruns show a successful step, and the results section does not renderstate.error.Update the pipeline step label when
flow.state.erroris set, and renderflow.state.errorin the final result state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/app.py` around lines 409 - 428, Update the flow-state handling around flow.state.error so partial failures persist across st.rerun(): store the “(partial failures)” label in st.session_state.pipeline_steps instead of the original label, and update the final results rendering to display flow.state.error when present rather than treating the step as successful.
270-295: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReset analysis state when the selected PDF changes.
A selected file exists for all reruns, so
st.session_state.flow_state is not None and uploaded_file is not Nonecan render results from a previous PDF after selecting a new PDF. Resetflow_stateand pipeline state before displaying results, or bindon_change=_reset_analysis_metrics(increment_uploader=True)tost.file_uploader.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/app.py` around lines 270 - 295, Reset analysis and pipeline state when the selected PDF changes by wiring the existing _reset_analysis_metrics with increment_uploader=True to the file uploader’s on_change callback, or perform the equivalent reset before rendering results. Ensure stale flow_state results cannot appear for a newly selected PDF while preserving the current upload and analysis behavior.
80-88: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick winBound PDF preview work before release.
_render_pdf_pagesrasterizes every page at 2x before display, and it caches full page image lists withoutmax_entries. Large multi-page documents can exhaust server or browser memory. Limit preview renderings to a fixed page cap, lower the scale, and bound the cache while keeping document page count for captions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/app.py` around lines 80 - 88, Update _render_pdf_pages to render only a fixed maximum number of document pages at a lower scale than 2x, and configure its st.cache_data decorator with a finite max_entries bound. Preserve access to the full PDF page count separately so captions can still report the document’s total pages.
🧹 Nitpick comments (1)
biotech-agentic-analyst/app.py (1)
62-66: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMake cached client creation depend on configuration.
init_mistral_client()readsMISTRAL_API_KEYbut has no cache-key input. If the app starts without a key, or client creation fails once, later reruns reuse the cached failure and keep the analysis button disabled until the cache or app is restarted. Streamlit resource caches are global and reuse results for the same arguments. (raw.githubusercontent.com)Pass a configuration fingerprint to the cached function, or clear the cache when the environment configuration changes. Verify by starting without
MISTRAL_API_KEY, adding the key, and rerunning the app.Proposed cache-key change
`@st.cache_resource` -def init_mistral_client(): - api_key = os.getenv("MISTRAL_API_KEY") +def init_mistral_client(api_key: str | None): if not api_key: return None, "MISTRAL_API_KEY not set in environment" ... - client, client_err = init_mistral_client() + client, client_err = init_mistral_client(os.getenv("MISTRAL_API_KEY"))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@biotech-agentic-analyst/app.py` around lines 62 - 66, Update init_mistral_client so its Streamlit cache key includes a fingerprint or value derived from the current MISTRAL_API_KEY configuration, while continuing to read the environment value for client creation. Ensure reruns after adding or changing the key do not reuse a cached failure from the previous configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@biotech-agentic-analyst/app.py`:
- Around line 409-428: Update the flow-state handling around flow.state.error so
partial failures persist across st.rerun(): store the “(partial failures)” label
in st.session_state.pipeline_steps instead of the original label, and update the
final results rendering to display flow.state.error when present rather than
treating the step as successful.
- Around line 270-295: Reset analysis and pipeline state when the selected PDF
changes by wiring the existing _reset_analysis_metrics with
increment_uploader=True to the file uploader’s on_change callback, or perform
the equivalent reset before rendering results. Ensure stale flow_state results
cannot appear for a newly selected PDF while preserving the current upload and
analysis behavior.
- Around line 80-88: Update _render_pdf_pages to render only a fixed maximum
number of document pages at a lower scale than 2x, and configure its
st.cache_data decorator with a finite max_entries bound. Preserve access to the
full PDF page count separately so captions can still report the document’s total
pages.
---
Nitpick comments:
In `@biotech-agentic-analyst/app.py`:
- Around line 62-66: Update init_mistral_client so its Streamlit cache key
includes a fingerprint or value derived from the current MISTRAL_API_KEY
configuration, while continuing to read the environment value for client
creation. Ensure reruns after adding or changing the key do not reuse a cached
failure from the previous configuration.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c835337e-542f-4ff3-8d61-8da2e98e37da
⛔ Files ignored due to path filters (1)
biotech-agentic-analyst/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
biotech-agentic-analyst/README.mdbiotech-agentic-analyst/app.pybiotech-agentic-analyst/flow/science_flow.pybiotech-agentic-analyst/mistral_ocr_pipeline/pipeline.pybiotech-agentic-analyst/models.pybiotech-agentic-analyst/pyproject.tomlbiotech-agentic-analyst/utils.py
💤 Files with no reviewable changes (1)
- biotech-agentic-analyst/models.py
🚧 Files skipped from review as they are similar to previous changes (5)
- biotech-agentic-analyst/pyproject.toml
- biotech-agentic-analyst/README.md
- biotech-agentic-analyst/mistral_ocr_pipeline/pipeline.py
- biotech-agentic-analyst/utils.py
- biotech-agentic-analyst/flow/science_flow.py
Summary by CodeRabbit