Skip to content

Commit 99f1c2d

Browse files
authored
Merge pull request #1128 from ScrapeGraphAI/pre/beta
release: promote pre/beta (2.2.0-beta.6) to main
2 parents ca112db + cb8f9b5 commit 99f1c2d

7 files changed

Lines changed: 296 additions & 7 deletions

File tree

.github/workflows/test-suite.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
- name: Run unit tests
3939
run: >
4040
uv run pytest
41+
tests/test_atlascloud_model.py
4142
tests/test_batch_api.py
4243
tests/test_csv_scraper_multi_graph.py
4344
tests/test_depth_search_graph.py
@@ -46,4 +47,5 @@ jobs:
4647
tests/test_scrape_do.py
4748
tests/test_search_graph.py
4849
tests/utils/convert_to_md_test.py
50+
tests/utils/output_parser_test.py
4951
tests/utils/parse_state_keys_test.py

AGENTS.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# AGENTS.md
2+
3+
Instructions for AI coding agents (Claude Code, Codex, Cursor, Copilot agents, …)
4+
working on **ScrapeGraphAI**. Human contributors should read
5+
[CONTRIBUTING.md](CONTRIBUTING.md); everything here is in addition to it.
6+
7+
---
8+
9+
## 1. Golden rule: everything goes to `pre/beta`
10+
11+
**`main` is never written to directly. All work is based on and merged into `pre/beta`.**
12+
13+
`pre/beta` is the prerelease branch: pushes to it publish a `beta` prerelease via
14+
semantic-release (see `.releaserc.yml`). `main` only receives releases when a
15+
maintainer promotes `pre/beta`.
16+
17+
```bash
18+
# 1. always start from an up-to-date pre/beta
19+
git fetch origin
20+
git checkout -b feat/my-change origin/pre/beta
21+
22+
# 2. commit your work
23+
git add <only the files you touched>
24+
git commit -m "feat(nodes): add X"
25+
26+
# 3. push and open the PR against pre/beta
27+
git push -u origin feat/my-change
28+
gh pr create --base pre/beta --title "feat(nodes): add X" --body "..."
29+
```
30+
31+
Checklist before you commit:
32+
33+
- [ ] The branch is based on `origin/pre/beta` (`git merge-base --is-ancestor origin/pre/beta HEAD`).
34+
- [ ] The PR base is `pre/beta`, **not** `main`.
35+
- [ ] No commits directly on `main` or `pre/beta`, no force-push to either.
36+
- [ ] One logical change per branch/PR.
37+
38+
If a task genuinely requires targeting `main` (e.g. a hotfix on a released
39+
version), stop and ask a maintainer first.
40+
41+
## 2. Environment setup
42+
43+
Python `>=3.12`, dependencies managed with [uv](https://docs.astral.sh/uv/):
44+
45+
```bash
46+
uv sync # create the venv and install deps
47+
uv run pre-commit install # install the git hooks
48+
```
49+
50+
Never hand-edit `uv.lock`; regenerate it with `uv lock` / `uv sync` and commit
51+
the result only when you actually changed dependencies in `pyproject.toml`.
52+
53+
## 3. Checks to run before pushing
54+
55+
```bash
56+
make lint # ruff + black --check + isort --check-only
57+
make type-check # mypy (strict)
58+
make test # pytest with coverage
59+
make pre-commit # run all hooks on all files
60+
```
61+
62+
Run at least `make lint` and the tests covering what you touched. Report the
63+
real result: if something fails or you skipped a step, say so in the PR
64+
description instead of implying a clean run.
65+
66+
Style: PEP 8 + Google Python docstrings, `black` formatting, line length 88.
67+
Match the conventions of the surrounding file rather than introducing new ones.
68+
69+
## 4. Commit messages
70+
71+
Commits are parsed by semantic-release (Conventional Commits, `conventionalcommits`
72+
preset), so the message decides the next version number. Use:
73+
74+
```
75+
feat: ✨ new feature -> minor bump
76+
fix: 🐛 bug fix -> patch bump
77+
docs: 📚 documentation
78+
style: 💅 formatting only
79+
refactor: ♻️ no behaviour change
80+
perf: ⚡ performance
81+
test: 🧪 tests
82+
build: 📦 build system / deps
83+
ci: 🤖 CI configuration
84+
chore: 🧹 everything else
85+
```
86+
87+
Format: `type(optional-scope): imperative summary`, optional body, and
88+
`BREAKING CHANGE:` in the footer for incompatible changes. Reference issues with
89+
`Fixes #123`.
90+
91+
## 5. Files agents must not touch
92+
93+
- `CHANGELOG.md` and the `version` field in `pyproject.toml` — owned by
94+
semantic-release; editing them by hand breaks releases.
95+
- Git tags and release notes on GitHub.
96+
- `.github/workflows/*` — only when the task is explicitly about CI.
97+
- Anything under `htmlcov/`, `coverage.xml`, `.pytest_cache/`, `__pycache__/`:
98+
build artifacts, never commit them.
99+
100+
Also: never commit secrets. API keys go in a local `.env` (git-ignored) and are
101+
read via `os.getenv`; examples and tests must use placeholders such as
102+
`OPENAI_APIKEY` from the environment.
103+
104+
## 6. Repository layout
105+
106+
```
107+
scrapegraphai/
108+
├── graphs/ # pipelines (SmartScraperGraph, SearchGraph, …)
109+
├── nodes/ # single graph steps (FetchNode, ParseNode, GenerateAnswerNode, …)
110+
├── models/ # LLM wrappers and token/model metadata
111+
├── docloaders/ # loaders (ChromiumLoader, …)
112+
├── prompts/ # prompt templates
113+
├── helpers/ # shared constants and schemas
114+
├── integrations/ # third-party / managed-API integrations
115+
└── utils/ # utilities (html cleanup, tokenization, …)
116+
examples/ # runnable usage examples, one folder per graph
117+
tests/ # pytest suite, mirrors the package layout
118+
docs/ # documentation sources
119+
```
120+
121+
When adding a node or graph, register it in the corresponding `__init__.py` and
122+
add a test under `tests/` next to the existing ones for that layer. New
123+
user-facing features need an entry in `examples/` and, when they change public
124+
behaviour, a docs update.
125+
126+
## 7. Working style expected from agents
127+
128+
- Prefer small, reviewable diffs; do not reformat or "clean up" untouched files.
129+
- Do not add dependencies unless the task requires it — say why in the PR.
130+
- Write all commits, PR titles/bodies, issue comments, code comments and
131+
docstrings **in English**.
132+
- Do not delete or rewrite existing tests to make a change pass.
133+
- If a test is already failing on `pre/beta`, mention it rather than silently
134+
fixing unrelated things in the same PR.
135+
- Never commit other people's in-progress work: check `git status` and stage
136+
only the files belonging to your change.

CHANGELOG.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1+
## [2.2.0-beta.6](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.2.0-beta.5...v2.2.0-beta.6) (2026-08-19)
2+
3+
4+
### Bug Fixes
5+
6+
* **graph:** expose when the 8192 token fallback was used ([470da9d](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/470da9d51d6220a8020ee8a1d91b618d3d622042)), closes [#1121](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1121)
7+
* lazy imports to prevent torchcodec FFmpeg DLL crash on Windows ([#1089](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1089)) ([#1092](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1092)) ([e5c2a42](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/e5c2a4292174bae948731379c88aafb14197ec6c))
8+
* pop model_tokens so it is not forwarded to the model client ([#1100](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1100)) ([d2b970c](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/d2b970ca77da64b6b6b91fa2353f2e41b9cfe105))
9+
* **search:** restore SearchGraph by migrating DuckDuckGo backend to ddgs ([#1083](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1083)) ([2139e37](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/2139e37c0addfb9039e3b985bb33801f20e4e05b)), closes [#1082](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1082) [#1082](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1082)
10+
* update MiniMax model metadata and endpoints ([#1103](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1103)) ([e5f8f2b](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/e5f8f2bf008cd7ce7f3d5980bf6f98e6153b2264))
11+
12+
13+
### Docs
14+
15+
* add AGENTS.md with contribution rules for AI agents ([3b2f986](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/3b2f986612bbc70879a0337197a1064cf5875245))
16+
* **readme:** add Open Source vs Managed API comparison ([#1091](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1091)) ([ef3523b](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/ef3523b1e1c62052b5c0e6b14cebe7933441a7e5))
17+
* swap Integrations infographic for new API banner; fix CTA links ([71ab440](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/71ab4406867ef02c22349c2dedc18c57d8bdfdec))
18+
19+
20+
### CI
21+
22+
* **release:** 2.1.3 [skip ci] ([cfb815f](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/cfb815fb0bba3dfc8262a24a7ddab28e22e13893)), closes [#1082](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1082) [#1082](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1082)
23+
* **release:** 2.1.4 [skip ci] ([7fc9c57](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/7fc9c57a0bb861d829f38ed672e35c5ba0e6c79c)), closes [#1089](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1089) [#1092](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1092)
24+
* **release:** 2.1.5 [skip ci] ([c9e0bd0](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/c9e0bd036a1f5def8ac92759cda90ba52e3b35e5)), closes [#1100](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1100) [#1091](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1091) [#1095](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1095)
25+
* **release:** 2.1.6 [skip ci] ([27d9d28](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/27d9d289f964844a28006f6d7d1518e293fd49ff)), closes [#1103](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1103)
26+
* **release:** 2.1.7 [skip ci] ([ca112db](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/ca112db4d8b1bd34961e9f1b7e3adf0c7d600c01)), closes [#1121](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1121)
27+
* run only deterministic unit suites in Test Suite workflow ([#1095](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1095)) ([037a42e](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/037a42ed73354c8b3eabd9dcc28236152602ef9e))
28+
* run the two new deterministic unit suites ([23e3d06](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/23e3d064e3850de222bcfc13907695278ca3bba9)), closes [#1104](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1104) [#1085](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1085)
29+
130
## [2.1.7](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.1.6...v2.1.7) (2026-08-19)
231

332

@@ -68,6 +97,44 @@
6897
## [2.2.0-beta.2](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-06-01)
6998

7099

100+
### Features
101+
102+
* upgrade MiniMax default model to M3 ([#1080](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1080)) ([1b16c26](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/1b16c268f4e9044c1386ccfaf67b38692b487e5a))
103+
104+
## [2.2.0-beta.5](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-06-23)
105+
106+
107+
### Bug Fixes
108+
109+
* lazy imports to prevent torchcodec FFmpeg DLL crash on Windows ([#1089](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1089)) ([2f55377](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/2f55377010fb16c81655aa1c8d08b88daa880797))
110+
111+
## [2.2.0-beta.4](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.2.0-beta.3...v2.2.0-beta.4) (2026-06-11)
112+
113+
114+
### Bug Fixes
115+
116+
* **nodes:** tolerate doubled-brace JSON output from models like DeepSeek ([#1085](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1085)) ([aaa5d2c](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/aaa5d2cf6d2657f8e8a3b9020ab8b40cdb311c46))
117+
118+
## [2.2.0-beta.3](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.2.0-beta.2...v2.2.0-beta.3) (2026-06-01)
119+
120+
121+
### Bug Fixes
122+
123+
* **nodes:** update outdated ChatOllama import path to langchain_ollama ([#1076](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1076)) ([e6054cb](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/e6054cbf19a7fe940899ea70b30706f676f86fa7))
124+
125+
126+
### Docs
127+
128+
* 📚 Standardize and fix links across translated READMEs ([#1074](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1074)) ([458d36a](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/458d36a6b83f4a412206cdbe9935a059e9d47f57))
129+
130+
131+
### CI
132+
133+
* **release:** 2.1.2 [skip ci] ([210c992](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/210c99280048863774fa27053412185a5c18150d)), closes [#1076](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1076) [#1074](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1074)
134+
135+
## [2.2.0-beta.2](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-06-01)
136+
137+
71138
### Features
72139

73140
* upgrade MiniMax default model to M3 ([#1080](https://github.com/ScrapeGraphAI/Scrapegraph-ai/issues/1080)) ([1b16c26](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/1b16c268f4e9044c1386ccfaf67b38692b487e5a))
@@ -1608,7 +1675,6 @@ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
16081675

16091676
* implement ScrapeGraph class for only web scraping automation ([612c644](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/612c644623fa6f4fe77a64a5f1a6a4d6cd5f4254))
16101677
* Implement SmartScraperMultiParseMergeFirstGraph class that scrapes a list of URLs and merge the content first and finally generates answers to a given prompt. ([3e3e1b2](https://github.com/ScrapeGraphAI/Scrapegraph-ai/commit/3e3e1b2f3ae8ed803d03b3b44b199e139baa68d4))
1611-
=======
16121678
## [1.26.7](https://github.com/ScrapeGraphAI/Scrapegraph-ai/compare/v1.26.6...v1.26.7) (2024-10-19)
16131679

16141680

@@ -3682,7 +3748,6 @@ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
36823748
* **release:** 1.6.1 [skip ci] ([44fbd71](https://github.com/VinciGit00/Scrapegraph-ai/commit/44fbd71742a57a4b10f22ed33781bb67aa77e58d))
36833749

36843750
## [1.6.1](https://github.com/VinciGit00/Scrapegraph-ai/compare/v1.6.0...v1.6.1) (2024-06-15)
3685-
=======
36863751

36873752

36883753
### Bug Fixes

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "scrapegraphai"
33

4-
version = "2.1.7"
4+
version = "2.2.0b6"
55

66
description = "A web scraping library based on LangChain which uses LLM and direct graph logic to create scraping pipelines."
77
authors = [

scrapegraphai/nodes/generate_answer_node.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from langchain_core.prompts import PromptTemplate
1010
from langchain_aws import ChatBedrock
1111
from langchain_ollama import ChatOllama
12-
from langchain_core.output_parsers import JsonOutputParser
1312
from langchain_core.runnables import RunnableParallel
1413
from langchain_openai import ChatOpenAI
1514
from requests.exceptions import Timeout
@@ -23,7 +22,10 @@
2322
TEMPLATE_NO_CHUNKS,
2423
TEMPLATE_NO_CHUNKS_MD,
2524
)
26-
from ..utils.output_parser import get_pydantic_output_parser
25+
from ..utils.output_parser import (
26+
TolerantJsonOutputParser,
27+
get_pydantic_output_parser,
28+
)
2729
from .base_node import BaseNode
2830

2931

@@ -148,7 +150,7 @@ def execute(self, state: dict) -> dict:
148150
format_instructions = ""
149151
else:
150152
if not isinstance(self.llm_model, ChatBedrock):
151-
output_parser = JsonOutputParser()
153+
output_parser = TolerantJsonOutputParser()
152154
format_instructions = (
153155
"You must respond with a JSON object. Your response should be formatted as a valid JSON "
154156
"with a 'content' field containing your analysis. For example:\n"

scrapegraphai/utils/output_parser.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,53 @@
22
Functions to retrieve the correct output parser and format instructions for the LLM model.
33
"""
44

5-
from typing import Any, Callable, Dict, Type, Union
5+
from typing import Any, Callable, Dict, List, Type, Union
66

7+
from langchain_core.exceptions import OutputParserException
8+
from langchain_core.outputs import Generation
79
from langchain_core.output_parsers import JsonOutputParser
810
from pydantic import BaseModel as BaseModelV2
911
from pydantic.v1 import BaseModel as BaseModelV1
1012

1113

14+
def _strip_doubled_braces(text: str) -> str:
15+
"""Strip one layer of the doubled braces some models echo from the prompt.
16+
17+
The default ``format_instructions`` show the expected shape using LangChain's
18+
escaped braces, e.g. ``{{"content": "..."}}``. Strongly instruction-following
19+
models (GPT-4o, etc.) emit single braces, but some models (notably DeepSeek)
20+
copy the doubled braces verbatim, producing ``{{"content": "..."}}`` which is
21+
not valid JSON. This normalizes that single case and is a no-op otherwise.
22+
"""
23+
stripped = text.strip()
24+
if stripped.startswith("{{") and stripped.endswith("}}"):
25+
return stripped[1:-1]
26+
return text
27+
28+
29+
class TolerantJsonOutputParser(JsonOutputParser):
30+
"""A :class:`JsonOutputParser` tolerant of doubled-brace output.
31+
32+
Behaviour is unchanged on the happy path: valid JSON is parsed by the parent
33+
parser exactly as before. Only when parsing fails AND the output is wrapped in
34+
doubled braces (``{{ ... }}``) does it retry once with a single layer of braces
35+
removed. This keeps providers like DeepSeek working without altering output for
36+
any model that already returns clean JSON.
37+
"""
38+
39+
def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any:
40+
try:
41+
return super().parse_result(result, partial=partial)
42+
except OutputParserException:
43+
text = result[0].text
44+
normalized = _strip_doubled_braces(text)
45+
if normalized != text:
46+
return super().parse_result(
47+
[Generation(text=normalized)], partial=partial
48+
)
49+
raise
50+
51+
1252
def get_structured_output_parser(
1353
schema: Union[Dict[str, Any], Type[BaseModelV1 | BaseModelV2], Type],
1454
) -> Callable:

tests/utils/output_parser_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Tests for scrapegraphai.utils.output_parser.TolerantJsonOutputParser."""
2+
3+
import pytest
4+
5+
from scrapegraphai.utils.output_parser import (
6+
TolerantJsonOutputParser,
7+
_strip_doubled_braces,
8+
)
9+
10+
11+
def test_strip_doubled_braces_unwraps_single_layer():
12+
assert _strip_doubled_braces('{{"content": "hi"}}') == '{"content": "hi"}'
13+
14+
15+
def test_strip_doubled_braces_is_noop_for_clean_json():
16+
text = '{"content": "hi"}'
17+
assert _strip_doubled_braces(text) == text
18+
19+
20+
def test_strip_doubled_braces_ignores_unbalanced():
21+
text = '{{"content": "hi"}'
22+
assert _strip_doubled_braces(text) == text
23+
24+
25+
def test_tolerant_parser_parses_clean_json_unchanged():
26+
parser = TolerantJsonOutputParser()
27+
assert parser.parse('{"content": "hi"}') == {"content": "hi"}
28+
29+
30+
def test_tolerant_parser_recovers_doubled_braces():
31+
"""Models such as DeepSeek echo the prompt's escaped braces verbatim."""
32+
parser = TolerantJsonOutputParser()
33+
assert parser.parse('{{"content": "hi"}}') == {"content": "hi"}
34+
35+
36+
def test_tolerant_parser_recovers_doubled_braces_with_whitespace():
37+
parser = TolerantJsonOutputParser()
38+
assert parser.parse(' {{"content": "hi"}} ') == {"content": "hi"}
39+
40+
41+
def test_tolerant_parser_still_raises_on_irrecoverable_output():
42+
parser = TolerantJsonOutputParser()
43+
with pytest.raises(Exception):
44+
parser.parse("this is not json at all")

0 commit comments

Comments
 (0)