article-q 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. article_q-0.2.1/.claude/settings.local.json +8 -0
  2. article_q-0.2.1/.gitignore +221 -0
  3. article_q-0.2.1/LICENSE +21 -0
  4. article_q-0.2.1/PKG-INFO +394 -0
  5. article_q-0.2.1/PLAN.md +80 -0
  6. article_q-0.2.1/README.md +374 -0
  7. article_q-0.2.1/articleq.example.toml +28 -0
  8. article_q-0.2.1/docs/assets/article-q-logo-old.png +0 -0
  9. article_q-0.2.1/docs/assets/article-q-logo-old2.png +0 -0
  10. article_q-0.2.1/docs/assets/article-q-logo.png +0 -0
  11. article_q-0.2.1/docs/assets/extra.css +6 -0
  12. article_q-0.2.1/docs/getting-started.md +152 -0
  13. article_q-0.2.1/docs/index.md +66 -0
  14. article_q-0.2.1/docs/reference/api.md +19 -0
  15. article_q-0.2.1/docs/reference/cli.md +112 -0
  16. article_q-0.2.1/docs/user-guide/configuration.md +79 -0
  17. article_q-0.2.1/docs/user-guide/evaluation.md +58 -0
  18. article_q-0.2.1/docs/user-guide/questions-file.md +54 -0
  19. article_q-0.2.1/docs/user-guide/visualization.md +77 -0
  20. article_q-0.2.1/mkdocs.yml +83 -0
  21. article_q-0.2.1/overrides/main.html +6 -0
  22. article_q-0.2.1/pyproject.toml +48 -0
  23. article_q-0.2.1/src/articleq/__init__.py +3 -0
  24. article_q-0.2.1/src/articleq/__main__.py +5 -0
  25. article_q-0.2.1/src/articleq/agents/__init__.py +1 -0
  26. article_q-0.2.1/src/articleq/agents/consensus.py +158 -0
  27. article_q-0.2.1/src/articleq/agents/extraction.py +74 -0
  28. article_q-0.2.1/src/articleq/agents/prompts.py +137 -0
  29. article_q-0.2.1/src/articleq/agents/provider.py +54 -0
  30. article_q-0.2.1/src/articleq/agents/validation.py +62 -0
  31. article_q-0.2.1/src/articleq/cli.py +296 -0
  32. article_q-0.2.1/src/articleq/config.py +100 -0
  33. article_q-0.2.1/src/articleq/eval.py +344 -0
  34. article_q-0.2.1/src/articleq/models/__init__.py +23 -0
  35. article_q-0.2.1/src/articleq/models/answer.py +58 -0
  36. article_q-0.2.1/src/articleq/models/config.py +55 -0
  37. article_q-0.2.1/src/articleq/models/paper.py +46 -0
  38. article_q-0.2.1/src/articleq/models/question.py +115 -0
  39. article_q-0.2.1/src/articleq/models/review.py +49 -0
  40. article_q-0.2.1/src/articleq/output/__init__.py +1 -0
  41. article_q-0.2.1/src/articleq/output/json_output.py +20 -0
  42. article_q-0.2.1/src/articleq/output/tabular_output.py +55 -0
  43. article_q-0.2.1/src/articleq/output/visualizer.py +780 -0
  44. article_q-0.2.1/src/articleq/parsing/__init__.py +1 -0
  45. article_q-0.2.1/src/articleq/parsing/base.py +31 -0
  46. article_q-0.2.1/src/articleq/parsing/marker_parser.py +80 -0
  47. article_q-0.2.1/src/articleq/parsing/pymupdf_parser.py +80 -0
  48. article_q-0.2.1/src/articleq/parsing/questions.py +92 -0
  49. article_q-0.2.1/src/articleq/pipeline/__init__.py +1 -0
  50. article_q-0.2.1/src/articleq/pipeline/orchestrator.py +409 -0
  51. article_q-0.2.1/src/articleq/pipeline/progress.py +27 -0
  52. article_q-0.2.1/src/articleq/pipeline/scheduler.py +43 -0
  53. article_q-0.2.1/src/articleq/utils/__init__.py +1 -0
  54. article_q-0.2.1/src/articleq/utils/chunking.py +90 -0
  55. article_q-0.2.1/src/articleq/utils/logging.py +21 -0
  56. article_q-0.2.1/tests/__init__.py +0 -0
  57. article_q-0.2.1/tests/conftest.py +117 -0
  58. article_q-0.2.1/tests/fixtures/questions.csv +5 -0
  59. article_q-0.2.1/tests/test_dependencies.py +345 -0
  60. article_q-0.2.1/uv.lock +4030 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python3:*)",
5
+ "Bash(python:*)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,221 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # MacOS
221
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tom Pollard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,394 @@
1
+ Metadata-Version: 2.4
2
+ Name: article-q
3
+ Version: 0.2.1
4
+ Summary: Agent/LLM-enabled narrative reviews of academic manuscripts
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: jinja2>=3.1
9
+ Requires-Dist: marker-pdf>=1.0
10
+ Requires-Dist: openpyxl>=3.1
11
+ Requires-Dist: pandas>=2.0
12
+ Requires-Dist: pydantic-ai>=0.1.0
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: pymupdf4llm>=0.0.17
15
+ Requires-Dist: pymupdf>=1.24
16
+ Requires-Dist: rich>=13.0
17
+ Requires-Dist: tomli-w>=1.0
18
+ Requires-Dist: typer>=0.12
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Article-Q
22
+
23
+ Agent/LLM-enabled narrative reviews of academic manuscripts. Parses PDFs, extracts structured data using LLM agents guided by a questions spreadsheet, and validates results through a multi-agent consensus mechanism.
24
+
25
+ ## Installation
26
+
27
+ Requires Python 3.11+.
28
+
29
+ ```bash
30
+ pip install -e .
31
+ ```
32
+
33
+ ## Step-by-step workflow
34
+
35
+ ### Step 1: Initialize the project
36
+
37
+ ```bash
38
+ articleq init
39
+ ```
40
+
41
+ This creates `articleq.toml` with default settings. Open it and set:
42
+
43
+ - `project.papers_dir` — directory containing your PDF manuscripts
44
+ - `project.questions_file` — path to your questions CSV (see Step 2)
45
+ - `llm.api_keys.openai` or `llm.api_keys.google` — your API key (or set the `OPENAI_API_KEY` / `GEMINI_API_KEY` environment variables)
46
+
47
+ ### Step 2: Create a questions file
48
+
49
+ Prepare a CSV (or Excel) file defining the data you want to extract. Required columns are `id` and `question`. Optional columns:
50
+
51
+ | Column | Description | Default |
52
+ |---|---|---|
53
+ | `id` | Unique identifier for the question | *(required)* |
54
+ | `question` | The question text | *(required)* |
55
+ | `category` | Grouping label (e.g. "methods", "outcomes") | `general` |
56
+ | `output_type` | One of `text`, `category`, `numeric`, `boolean`, `list` | `text` |
57
+ | `options` | Comma-separated valid answers (for `category` type) | |
58
+ | `description` | Additional guidance for the extraction agent | |
59
+ | `depends_on` | Comma-separated IDs of questions this one depends on | |
60
+
61
+ Example:
62
+
63
+ ```csv
64
+ id,question,category,output_type,options,description,depends_on
65
+ sample_size,What was the total sample size?,demographics,numeric,,Total number of participants enrolled,
66
+ primary_outcome,What was the primary outcome?,outcomes,text,,The main outcome measure,
67
+ study_design,What was the study design?,methods,category,"RCT,cohort,case-control,cross-sectional",,
68
+ study_design_other,If other please specify,methods,text,,,study_design
69
+ blinding,Was the study blinded?,methods,boolean,,Whether any form of blinding was used,
70
+ ```
71
+
72
+ ### Step 3: Parse PDFs
73
+
74
+ ```bash
75
+ articleq parse -c articleq.toml
76
+ ```
77
+
78
+ This converts each PDF to structured markdown and saves the output to `output/parsed/`. Each paper produces:
79
+ - A `.json` file containing the parsed blocks (the source of truth)
80
+ - A `.md` file for human-readable review
81
+ - Extracted figures saved as PNGs in `output/parsed/figures/`
82
+
83
+ Two parsing backends are available (set `parsing.backend` in config):
84
+ - `pymupdf` (default) — fast, uses pymupdf4llm layout detection
85
+ - `marker` — uses marker-pdf with OCR; better for scanned documents
86
+
87
+ ### Step 4: Review and clean parsed content (optional)
88
+
89
+ Preview the parsed papers in a browser:
90
+
91
+ ```bash
92
+ articleq visualize --parsed-dir output/parsed/
93
+ ```
94
+
95
+ The JSON files in `output/parsed/` are the source of truth. Each file contains a `blocks` array — the LLM agents read from the `content` field of each block, so edits there directly affect extraction. Do **not** edit the `.md` files or the `raw_markdown` field in the JSON; both are regenerated by `articleq rebuild`.
96
+
97
+ Each block looks like this:
98
+
99
+ ```json
100
+ {
101
+ "block_type": "text",
102
+ "content": "The study enrolled 150 partcipants between Jan and Dec 2020.",
103
+ "page_number": 3,
104
+ "section": "Methods"
105
+ }
106
+ ```
107
+
108
+ Common edits:
109
+
110
+ - **Fix OCR errors** — correct garbled text, broken words, or misrecognized characters (e.g. `"partcipants"` → `"participants"`)
111
+ - **Remove noise** — delete blocks containing headers, footers, page numbers, or watermarks that the parser picked up
112
+ - **Fix broken tables** — repair malformed markdown tables in `"table"` blocks
113
+ - **Remove irrelevant blocks** — delete entire blocks (e.g. reference lists, copyright notices) that add noise without useful content
114
+
115
+ After editing blocks, rebuild the markdown:
116
+
117
+ ```bash
118
+ articleq rebuild -c articleq.toml
119
+ ```
120
+
121
+ This regenerates the `.md` files and updates `raw_markdown` in the JSON caches to match the block content.
122
+
123
+ ### Step 5: Run LLM extraction
124
+
125
+ ```bash
126
+ articleq extract -c articleq.toml
127
+ ```
128
+
129
+ This sends each question to the extraction agents for every paper, runs validation, and writes results to `output/results.json`. The `extract` command will refuse to run if the markdown is out of sync with the blocks — run `articleq rebuild` first if you've edited blocks.
130
+
131
+ Alternatively, run everything (parse + extract) in one shot:
132
+
133
+ ```bash
134
+ articleq run -c articleq.toml
135
+ ```
136
+
137
+ ### Step 6: Export and visualize results
138
+
139
+ Convert results to CSV or Excel:
140
+
141
+ ```bash
142
+ articleq export output/results.json --format csv
143
+ articleq export output/results.json --format excel
144
+ ```
145
+
146
+ Generate an interactive HTML evidence viewer:
147
+
148
+ ```bash
149
+ articleq visualize -r output/results.json
150
+ ```
151
+
152
+ The viewer shows each paper's content alongside extracted answers, with evidence passages highlighted in the text. Pass `-q questions.csv` to include question text in the results panel.
153
+
154
+ ## How it works
155
+
156
+ Each question for each paper goes through a three-agent workflow:
157
+
158
+ ```
159
+ Paper + Question
160
+ |
161
+ v
162
+ Extraction Agent --> Answer A
163
+ |
164
+ v
165
+ Validation Agent --> Answer B (blind, independent)
166
+ |
167
+ v
168
+ Compare A and B
169
+ |
170
+ +-- AGREE + high confidence --> Accept A as final
171
+ |
172
+ +-- DISAGREE --> Consensus Agent reviews both --> Final answer
173
+ ```
174
+
175
+ - The **extraction agent** reads the paper and extracts an answer with evidence quotes, page numbers, and a confidence score.
176
+ - The **validation agent** performs a blind, independent re-extraction (it does not see the first answer).
177
+ - If the two answers **agree** and both have confidence above `auto_accept_threshold`, the answer is accepted directly.
178
+ - If they **disagree**, the **consensus agent** reviews both answers against the source material and produces a final arbitrated answer.
179
+
180
+ ### Question dependencies
181
+
182
+ Some questions depend on the answers to earlier questions. For example, a follow-up like "If other, please specify" only makes sense after the study type has been determined. Use the `depends_on` column to declare these relationships:
183
+
184
+ ```csv
185
+ id,question,depends_on
186
+ study_type,What was the study design?,
187
+ study_type_other,"If other, please specify",study_type
188
+ ```
189
+
190
+ When dependencies are present, questions are processed in **waves** — all questions with no unmet dependencies run concurrently, then questions whose dependencies are satisfied by the previous wave, and so on. Within each wave, concurrency is controlled by `pipeline.concurrency` as usual. Dependent questions receive a "Prior Answers" section in their prompt containing the question text and answer of each dependency.
191
+
192
+ The `depends_on` column is optional. CSVs without it continue to work as before (all questions run concurrently in a single wave). Circular dependencies and references to nonexistent question IDs are caught at load time.
193
+
194
+ Agreement checking is type-aware:
195
+ - **Categorical/boolean**: exact match
196
+ - **Numeric**: within 5% tolerance
197
+ - **Text**: normalized string comparison
198
+
199
+ ## Configuration reference
200
+
201
+ ```toml
202
+ [project]
203
+ name = "my-review" # Project name used in output
204
+ papers_dir = "./papers" # Directory containing PDF files
205
+ questions_file = "./questions.csv" # Path to questions CSV/Excel
206
+ output_dir = "./output" # Where results are written
207
+ # context_file = "./context.md" # Optional: additional instructions for the LLM
208
+
209
+ [parsing]
210
+ backend = "pymupdf" # "pymupdf" or "marker"
211
+ reparse = false # Force re-parsing even if cached results exist
212
+
213
+ [llm]
214
+ extraction_model = "openai:gpt-4o" # Model for primary extraction
215
+ validation_model = "openai:gpt-4o" # Model for validation pass
216
+ consensus_model = "openai:gpt-4o" # Model for arbitration
217
+ # temperature = 0.0 # LLM sampling temperature (omit to use provider default)
218
+
219
+ [llm.api_keys]
220
+ openai = "${OPENAI_API_KEY}" # Supports environment variable expansion
221
+ google = "${GEMINI_API_KEY}"
222
+
223
+ [pipeline]
224
+ concurrency = 5 # Max concurrent agent calls
225
+ skip_validation = false # Set true to skip the validation/consensus step
226
+ checkpoint = true # Save per-paper checkpoints for resume
227
+ chunk_max_tokens = 8000 # Max tokens per chunk for large PDFs
228
+
229
+ [validation]
230
+ auto_accept_threshold = 0.9 # Min confidence to auto-accept agreement
231
+ always_validate_categories = ["primary_outcome"] # Always run full 3-agent flow for these
232
+ ```
233
+
234
+ ## Additional topics
235
+
236
+ ### Caching and re-parsing
237
+
238
+ During `parse`, each parsed PDF is saved as both a markdown file and a JSON cache file under `{output_dir}/parsed/`. On subsequent runs, cached JSON files are loaded automatically, skipping PDF re-parsing.
239
+
240
+ To force re-parsing (e.g. after replacing a PDF or upgrading the parser), use the `--reparse` flag:
241
+
242
+ ```bash
243
+ articleq parse -c articleq.toml --reparse
244
+ ```
245
+
246
+ To re-parse a single paper, delete its cached `.json` file and run `parse` again.
247
+
248
+ Output directory structure:
249
+
250
+ ```
251
+ output/
252
+ ├── parsed/
253
+ │ ├── study_smith_2020.pdf.md
254
+ │ ├── study_smith_2020.pdf.json # cached ParsedPaper (used on re-runs)
255
+ │ ├── study_jones_2021.pdf.md
256
+ │ ├── study_jones_2021.pdf.json
257
+ │ └── figures/
258
+ │ ├── study_smith_2020_img1.png
259
+ │ ├── study_smith_2020_img2.png
260
+ │ └── study_jones_2021_img1.png
261
+ └── results.json
262
+ ```
263
+
264
+ ### Context file
265
+
266
+ You can provide a markdown file with additional instructions and domain knowledge to guide the LLM agents. Set `context_file` in the `[project]` section of your config:
267
+
268
+ ```toml
269
+ [project]
270
+ context_file = "./context.md"
271
+ ```
272
+
273
+ The contents are passed as additional system instructions to all three agents (extraction, validation, consensus). Use this for:
274
+
275
+ - Domain-specific definitions and terminology
276
+ - Important distinctions the LLM should be aware of
277
+ - Guidance on how to handle ambiguous cases
278
+ - Any background knowledge relevant to the review
279
+
280
+ Example `context.md`:
281
+
282
+ ```markdown
283
+ # Extraction Context
284
+
285
+ This review focuses on dentin hypersensitivity (DH) clinical trials.
286
+
287
+ ## Key Definitions
288
+
289
+ The Holland 1997 definition of DH: "short, sharp pain arising from exposed
290
+ dentine in response to stimuli typically thermal, evaporative, tactile, osmotic
291
+ or chemical and which cannot be ascribed to any other form of dental defect or
292
+ pathology."
293
+
294
+ ## Important Distinctions
295
+
296
+ - Distinguish between stimuli used for DIAGNOSIS versus OUTCOME MEASURES
297
+ - "dh_threshold_teeth" refers to minimum teeth per PATIENT, not total in study
298
+ ```
299
+
300
+ ### Large PDFs
301
+
302
+ Papers exceeding `chunk_max_tokens` are handled with a two-pass approach:
303
+
304
+ 1. The paper is split into chunks by content blocks.
305
+ 2. Chunks are scored for relevance to the current question using keyword overlap.
306
+ 3. Only the most relevant chunks (within the token budget) are sent to the agent.
307
+
308
+ ### Multimodal support
309
+
310
+ Figures extracted from PDFs are sent to the LLM as binary images alongside the text content. This happens automatically — if a parsed paper contains image data, the images are included in the prompt sent to the extraction, validation, and consensus agents.
311
+
312
+ - Both the `pymupdf` and `marker` backends extract images and store them as base64 in the parsed data.
313
+ - Text placeholders like `[Image from page N]` remain in the text for positional context, and the actual image binaries are appended after the text.
314
+ - No configuration is needed. If image data exists in the parsed paper, it is included. Models that do not support vision will receive only the text portion.
315
+
316
+ ### Evaluation
317
+
318
+ You can evaluate extraction results against manually-created ground truth using the `eval` command. This is useful for benchmarking accuracy across models, prompts, or configurations.
319
+
320
+ **Benchmark layout:**
321
+
322
+ ```
323
+ benchmarks/
324
+ └── example/
325
+ ├── papers/ # PDF manuscripts
326
+ ├── questions.csv # Questions used for extraction
327
+ └── expected.csv # Ground truth answers
328
+ ```
329
+
330
+ The `expected.csv` uses the same column format as `articleq export` output. At minimum it needs `paper`, `question_id`, and `final_value` columns:
331
+
332
+ ```csv
333
+ paper,question_id,final_value
334
+ study_smith_2020.pdf,sample_size,150
335
+ study_smith_2020.pdf,study_design,RCT
336
+ study_smith_2020.pdf,primary_outcome,overall survival
337
+ ```
338
+
339
+ **Running an evaluation:**
340
+
341
+ ```bash
342
+ articleq run -c benchmarks/example/config.toml
343
+ articleq export output/results.json --format csv
344
+ articleq eval output/results.csv benchmarks/example/expected.csv -q benchmarks/example/questions.csv
345
+ ```
346
+
347
+ The `-q` flag is optional but recommended — it enables type-aware comparison (numeric tolerance, boolean normalization, etc.) by reading each question's `output_type` from the questions file.
348
+
349
+ The report shows:
350
+ - **Overall accuracy** — percentage of answers matching ground truth
351
+ - **Per-question breakdown** — accuracy for each question across all papers
352
+ - **Detailed mismatches** — expected vs actual value for every disagreement
353
+
354
+ **LLM-as-judge evaluation:**
355
+
356
+ Strict string comparison can produce false negatives for free-text answers where the meaning matches but the wording differs (e.g. "RCT, parallel group" vs "Randomised controlled trial - Parallel group trial"). The `--judge-model` option enables an LLM judge that re-evaluates deterministic mismatches for semantic equivalence:
357
+
358
+ ```bash
359
+ articleq eval output/results.csv benchmarks/example/expected.csv \
360
+ -q benchmarks/example/questions.csv \
361
+ --judge-model openai:gpt-4o-mini \
362
+ -c benchmarks/example/config.toml
363
+ ```
364
+
365
+ When enabled:
366
+ - Answers that match deterministically are accepted as before (no LLM call).
367
+ - Mismatches on `text`, `category`, and `list` type questions are sent to the judge model, which decides whether the answers are semantically equivalent.
368
+ - `boolean` and `numeric` types keep their existing deterministic checks only.
369
+ - The `-q` questions file is required when using `--judge-model` (the question text provides context to the judge).
370
+ - The `-c` config file is optional — used to resolve API keys. Without it, keys are read from environment variables.
371
+
372
+ The report distinguishes strict matches from judge matches and includes the judge's reasoning for any answers it accepted:
373
+
374
+ ```
375
+ Strict matches: 12
376
+ Judge matches: 3
377
+ Mismatches: 5
378
+ Matched: 15
379
+ Accuracy: 75.0%
380
+ ```
381
+
382
+ ## CLI reference
383
+
384
+ ```
385
+ articleq init [-o PATH] Generate a starter config file
386
+ articleq run -c CONFIG [--reparse] Run the full pipeline (parse + extract)
387
+ articleq parse -c CONFIG [--reparse] Parse PDFs and save as markdown (no LLM calls)
388
+ articleq rebuild -c CONFIG Rebuild markdown and JSON from edited blocks
389
+ articleq extract -c CONFIG Run LLM extraction on pre-parsed papers
390
+ articleq export RESULTS [--format csv|excel] [-o PATH] Export to CSV/Excel
391
+ articleq eval RESULTS EXPECTED [-q QUESTIONS] [--judge-model MODEL] [-c CONFIG] Evaluate against ground truth
392
+ articleq visualize -r RESULTS [-o PATH] [-q QUESTIONS] Generate HTML evidence viewer
393
+ articleq visualize --parsed-dir DIR [-o PATH] Preview parsed papers (no results)
394
+ ```