ruby-spacy 0.5.0 → 0.7.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +97 -0
- data/CHANGELOG.md +58 -0
- data/Gemfile +7 -8
- data/README.md +89 -5
- data/docs/syntax_trees.md +55 -0
- data/examples/rsyntaxtree/outputs/tree_ar_projection.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_de_projection.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_en_chunks.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_en_morphology.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_en_projection.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_ja_chunks.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_ja_projection.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_ru_morphology.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_ru_projection.png +0 -0
- data/examples/rsyntaxtree/outputs/tree_zh_projection.png +0 -0
- data/examples/rsyntaxtree/syntax_tree_ar.rb +36 -0
- data/examples/rsyntaxtree/syntax_tree_de.rb +19 -0
- data/examples/rsyntaxtree/syntax_tree_en.rb +26 -0
- data/examples/rsyntaxtree/syntax_tree_ja.rb +21 -0
- data/examples/rsyntaxtree/syntax_tree_ru.rb +23 -0
- data/examples/rsyntaxtree/syntax_tree_zh.rb +21 -0
- data/examples/rule_based_matching/creating_spans_from_matches.rb +1 -1
- data/lib/ruby-spacy/syntax_tree.rb +304 -0
- data/lib/ruby-spacy/version.rb +1 -1
- data/lib/ruby-spacy.rb +210 -28
- data/ruby-spacy.gemspec +6 -6
- metadata +52 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eff16e19adc4863d93ac22803e63e5ac2ab8bba077eca7cbc8c2403174704aac
|
|
4
|
+
data.tar.gz: 1766dc8fbb6c2c4b10314c925ddae484907f6677f9fb314ede756c9b3752923e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1ef879141c89307e0fa05d5130fa997c0e1746ab0fc4a466dca032a6c4f1007341e8e6a5d1369d1f5a260070e161df8df60f87042343870c862d931faaa7789
|
|
7
|
+
data.tar.gz: 54baa96c6f475f147b374d8b27a80c6aa792937d759793178fd4f78592588f93d121f4e1edbfb89fdb7e8da5cd6b74abf8b1ccc404ef8bd6f836cc88b55fa407
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# Every branch, so that work in progress is tested before a pull request exists
|
|
5
|
+
push:
|
|
6
|
+
branches: ["**"]
|
|
7
|
+
pull_request:
|
|
8
|
+
# Weekly run to detect breakage from new spaCy / PyCall / Ruby releases
|
|
9
|
+
# even when the repository itself is untouched
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: "23 3 * * 2"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: ruby ${{ matrix.ruby }} / python ${{ matrix.python }}
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
# Pull requests from this repository are already covered by the push event;
|
|
18
|
+
# only run the pull_request event for forks, to avoid duplicate matrices
|
|
19
|
+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
|
|
20
|
+
# This project has known hang paths (pipeline calls from non-main threads,
|
|
21
|
+
# spacy.load hangs that Ruby's Timeout cannot interrupt); never let a job
|
|
22
|
+
# run until GitHub's 6-hour limit
|
|
23
|
+
timeout-minutes: 30
|
|
24
|
+
continue-on-error: ${{ matrix.experimental == true }}
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
ruby: ["3.2", "3.3", "3.4", "4.0"]
|
|
29
|
+
python: ["3.11", "3.12", "3.13", "3.14"]
|
|
30
|
+
include:
|
|
31
|
+
# Ruby 4.1 is not released yet; track the development branch instead
|
|
32
|
+
- { ruby: head, python: "3.13", experimental: true }
|
|
33
|
+
env:
|
|
34
|
+
# PyCall locates the Python interpreter via this variable
|
|
35
|
+
PYTHON: python
|
|
36
|
+
# rsyntaxtree (used by the syntax_tree tests) is in the :rendering
|
|
37
|
+
# bundler group and needs pango/rsvg system libraries; install it only
|
|
38
|
+
# on representative jobs
|
|
39
|
+
BUNDLE_WITHOUT: ${{ matrix.python != '3.13' && 'rendering' || '' }}
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
|
+
|
|
43
|
+
# Must precede ruby/setup-ruby: installing the pycall gem compiles its
|
|
44
|
+
# native extension against this Python
|
|
45
|
+
- uses: actions/setup-python@v7
|
|
46
|
+
with:
|
|
47
|
+
python-version: ${{ matrix.python }}
|
|
48
|
+
|
|
49
|
+
# Must precede ruby/setup-ruby: bundler-cache installs the pango/rsvg2
|
|
50
|
+
# gems, which compile against these system libraries
|
|
51
|
+
- name: Install rsyntaxtree system dependencies (representative jobs)
|
|
52
|
+
if: matrix.python == '3.13'
|
|
53
|
+
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev librsvg2-dev libgirepository1.0-dev
|
|
54
|
+
|
|
55
|
+
- uses: ruby/setup-ruby@v1
|
|
56
|
+
with:
|
|
57
|
+
ruby-version: ${{ matrix.ruby }}
|
|
58
|
+
bundler-cache: true
|
|
59
|
+
|
|
60
|
+
- id: site-packages
|
|
61
|
+
run: echo "path=$(python -c 'import site; print(site.getsitepackages()[0])')" >> "$GITHUB_OUTPUT"
|
|
62
|
+
|
|
63
|
+
# Cache the language models only (en_core_web_lg alone is several hundred
|
|
64
|
+
# MB). spaCy itself is intentionally installed fresh on every run so that
|
|
65
|
+
# new releases are exercised immediately. Bump the key suffix when
|
|
66
|
+
# upgrading the models. The ja/ru/de/zh models are installed only on the
|
|
67
|
+
# python 3.13 jobs (where the syntax_tree tests run); listing them here
|
|
68
|
+
# for other jobs is harmless
|
|
69
|
+
- uses: actions/cache@v6
|
|
70
|
+
with:
|
|
71
|
+
path: |
|
|
72
|
+
${{ steps.site-packages.outputs.path }}/en_core_web_sm*
|
|
73
|
+
${{ steps.site-packages.outputs.path }}/en_core_web_lg*
|
|
74
|
+
${{ steps.site-packages.outputs.path }}/ja_core_news_sm*
|
|
75
|
+
${{ steps.site-packages.outputs.path }}/ru_core_news_sm*
|
|
76
|
+
${{ steps.site-packages.outputs.path }}/de_core_news_sm*
|
|
77
|
+
${{ steps.site-packages.outputs.path }}/zh_core_web_sm*
|
|
78
|
+
key: spacy-models-${{ matrix.python }}-v2
|
|
79
|
+
|
|
80
|
+
# en_core_web_lg and the ja/ru/de/zh models are large; install them only
|
|
81
|
+
# on representative jobs (where the syntax_tree tests run). Tests that
|
|
82
|
+
# need them are skipped automatically where they are absent.
|
|
83
|
+
- name: Install spaCy and language models
|
|
84
|
+
run: |
|
|
85
|
+
python -m pip install --upgrade pip
|
|
86
|
+
python -m pip install --upgrade spacy
|
|
87
|
+
python -m spacy download en_core_web_sm
|
|
88
|
+
if [ "${{ matrix.python }}" = "3.13" ]; then
|
|
89
|
+
python -m spacy download en_core_web_lg
|
|
90
|
+
python -m spacy download ja_core_news_sm
|
|
91
|
+
python -m spacy download ru_core_news_sm
|
|
92
|
+
python -m spacy download de_core_news_sm
|
|
93
|
+
python -m spacy download zh_core_web_sm
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
- name: Run tests
|
|
97
|
+
run: bundle exec rake test
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 0.7.0 - 2026-09-03
|
|
4
|
+
### Added
|
|
5
|
+
- `Doc#syntax_tree` and `Span#syntax_tree` — turn a parse into rsyntaxtree
|
|
6
|
+
bracket notation, or draw it as SVG, PNG, PDF, TikZ, or JSON. Two styles:
|
|
7
|
+
`:projection` builds a phrase-structure-like tree from the head words,
|
|
8
|
+
`:chunks` lays the noun chunks out flat. Noun chunks are shaded, named
|
|
9
|
+
entities that coincide with one are labeled, and `morphology: true` attaches
|
|
10
|
+
a table of features to every leaf. rsyntaxtree (>= 2.4.0) is an optional
|
|
11
|
+
dependency, required on first use rather than at load time
|
|
12
|
+
- `Language.new(py_nlp:)` — wrap a Python pipeline built elsewhere, for the
|
|
13
|
+
languages spaCy ships no trained model for. Every right-to-left language is
|
|
14
|
+
one of them, and their trees are drawn mirrored so the leaves run in reading
|
|
15
|
+
order. A package such as spacy-stanza builds the pipeline; ruby-spacy gains
|
|
16
|
+
no dependency
|
|
17
|
+
- `docs/syntax_trees.md` — a gallery of trees in six languages, generated by
|
|
18
|
+
the scripts in `examples/rsyntaxtree/`
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- CI runs the syntax tree tests against English, Japanese, Russian, German, and
|
|
22
|
+
Chinese models, which between them cover the paths that differ: with and
|
|
23
|
+
without noun chunks, two-line entity labels, and a mirrored right-to-left tree
|
|
24
|
+
|
|
25
|
+
## 0.6.0 - 2026-08-31
|
|
26
|
+
### Added
|
|
27
|
+
- GitHub Actions CI — Ruby 3.2 to 4.0 (plus ruby-head) and Python 3.11 to 3.14,
|
|
28
|
+
with a weekly run to catch breakage from new spaCy or PyCall releases
|
|
29
|
+
- `:label` in `Matcher#match` results — the label string of the matched pattern
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- PyCall 1.5.3 or later is now required; earlier versions freeze the whole
|
|
33
|
+
process when Ruby's GC releases a Python object on a non-main thread, which
|
|
34
|
+
affects any threaded application (Rails, Puma, Sidekiq)
|
|
35
|
+
- Minimum Ruby version raised to 3.2
|
|
36
|
+
- Removed the `numpy` gem dependency; NumPy is used directly through PyCall
|
|
37
|
+
- Relaxed the `terminal-table` requirement to `>= 3.0, < 5`
|
|
38
|
+
- Language models are no longer stored in Python's `__main__`, so creating a
|
|
39
|
+
`Language` no longer keeps a pipeline alive until the process exits
|
|
40
|
+
- `Language.new(timeout:)` now actually fires. The wait happens on a Python
|
|
41
|
+
thread rather than through Ruby's `Timeout`, whose watcher thread cannot run
|
|
42
|
+
while PyCall holds the GVL. `timeout: nil` waits indefinitely, and a timeout
|
|
43
|
+
is never retried
|
|
44
|
+
- README documents the thread-safety constraint: all spaCy calls must be made
|
|
45
|
+
from the thread that initialized PyCall
|
|
46
|
+
|
|
47
|
+
### Deprecated
|
|
48
|
+
- `Language#spacy_nlp_id` — use `#py_nlp` instead; referencing it still works
|
|
49
|
+
but creates a Python global variable that is never released
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- `Matcher#match` returned a corrupted `:match_id` (often `0`) for labels whose
|
|
53
|
+
hash was `2**62` or larger, since unsigned 64-bit values do not survive the
|
|
54
|
+
PyCall boundary. Ids now round-trip through `Language#vocab_string_lookup`,
|
|
55
|
+
which accepts large ids again
|
|
56
|
+
- Integer attributes reached through `method_missing`, such as `Token#orth`,
|
|
57
|
+
were silently corrupted for the same reason: 7 of 12 common words returned a
|
|
58
|
+
negative `orth`. They are now fetched safely, which also restores
|
|
59
|
+
`Token#rank` for out-of-vocabulary tokens
|
|
60
|
+
|
|
3
61
|
## 0.5.0 - 2026-07-21
|
|
4
62
|
### Added
|
|
5
63
|
- `Language#with_llm(provider:)` — provider-neutral block-based LLM API
|
data/Gemfile
CHANGED
|
@@ -5,15 +5,14 @@ source "https://rubygems.org"
|
|
|
5
5
|
# Specify your gem's dependencies in ruby-spacy.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
-
gem "fiddle" # Required for Ruby 4.0+ (moved from default to bundled gem)
|
|
9
|
-
gem "numpy"
|
|
10
|
-
gem "pycall", "~> 1.5.1"
|
|
11
|
-
gem "terminal-table"
|
|
12
|
-
|
|
13
8
|
group :development do
|
|
14
9
|
gem "github-markup"
|
|
15
|
-
gem "minitest", "~> 5.0"
|
|
16
|
-
gem "rake", "~> 13.0"
|
|
17
10
|
gem "redcarpet"
|
|
18
|
-
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Used only for rendering in Doc#syntax_tree / Span#syntax_tree tests.
|
|
14
|
+
# Kept in its own group so CI jobs without the pango/rsvg system libraries
|
|
15
|
+
# can exclude it with BUNDLE_WITHOUT=rendering.
|
|
16
|
+
group :rendering do
|
|
17
|
+
gem "rsyntaxtree", ">= 2.4.0", require: false
|
|
19
18
|
end
|
data/README.md
CHANGED
|
@@ -10,29 +10,31 @@
|
|
|
10
10
|
| ✅ | Part-of-speech tagging and dependency parsing |
|
|
11
11
|
| ✅ | Named entity recognition |
|
|
12
12
|
| ✅ | Syntactic dependency visualization |
|
|
13
|
+
| ✅ | Syntax tree visualization (via rsyntaxtree) |
|
|
13
14
|
| ✅ | Access to pre-trained word vectors |
|
|
14
15
|
| ✅ | LLM integration: OpenAI, Anthropic (Claude), and local models |
|
|
15
16
|
|
|
16
|
-
Current Version: `0.
|
|
17
|
+
Current Version: `0.7.0`
|
|
17
18
|
|
|
18
|
-
- Ruby 4.0 supported
|
|
19
|
+
- Ruby 3.2 to 4.0 supported (PyCall 1.5.3 or later required)
|
|
19
20
|
- spaCy 3.8 supported
|
|
21
|
+
- Syntax trees drawn with rsyntaxtree, including right-to-left languages
|
|
20
22
|
- Multi-provider LLM API: OpenAI, Anthropic (Claude), and local models via Ollama or any OpenAI-compatible server
|
|
21
23
|
- Structured outputs (JSON Schema) support
|
|
22
24
|
- Block-based LLM API with linguistic analysis
|
|
23
25
|
|
|
24
26
|
## Installation of Prerequisites
|
|
25
27
|
|
|
26
|
-
**IMPORTANT**: Make sure that the `enable-shared` option is enabled in your Python installation. You can use [pyenv](https://github.com/pyenv/pyenv) to install any version of Python you like.
|
|
28
|
+
**IMPORTANT**: Make sure that the `enable-shared` option is enabled in your Python installation. You can use [pyenv](https://github.com/pyenv/pyenv) to install any version of Python you like. spaCy 3.8 supports Python 3.10 and later (wheels are provided for Python 3.10–3.14), so we recommend using one of those versions. Install Python 3.13, for instance, using pyenv with `enable-shared` as follows:
|
|
27
29
|
|
|
28
30
|
```shell
|
|
29
|
-
$ env CONFIGURE_OPTS="--enable-shared" pyenv install 3.
|
|
31
|
+
$ env CONFIGURE_OPTS="--enable-shared" pyenv install 3.13
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
Remember to make it accessible from your working directory. It is recommended that you set `global` to the version of python you just installed.
|
|
33
35
|
|
|
34
36
|
```shell
|
|
35
|
-
$ pyenv global 3.
|
|
37
|
+
$ pyenv global 3.13
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
Then, install [spaCy](https://spacy.io/). If you use `pip`, the following command will do:
|
|
@@ -77,6 +79,18 @@ Or install it yourself as:
|
|
|
77
79
|
|
|
78
80
|
See [Examples](#examples) below.
|
|
79
81
|
|
|
82
|
+
### Using an External Pipeline
|
|
83
|
+
|
|
84
|
+
`Spacy::Language.new` normally loads an installed model by name. To use a language spaCy has no model for, or a pipeline you built yourself, pass an existing Python `Language` object instead:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
py_nlp = PyCall.import_module("spacy_stanza").load_pipeline("ar")
|
|
88
|
+
nlp = Spacy::Language.new(py_nlp: py_nlp)
|
|
89
|
+
nlp.read("...").tokens # works like any other nlp
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Third-party packages such as [spacy-stanza](https://github.com/explosion/spacy-stanza) or [spacy-udpipe](https://github.com/TakeLab/spacy-udpipe) are not ruby-spacy dependencies; install them yourself. `model` and `py_nlp:` are mutually exclusive.
|
|
93
|
+
|
|
80
94
|
## Examples
|
|
81
95
|
|
|
82
96
|
Many of the following examples are Python-to-Ruby translations of code snippets in [spaCy 101](https://spacy.io/usage/spacy-101). For more examples, look inside the `examples` directory.
|
|
@@ -286,6 +300,42 @@ Output:
|
|
|
286
300
|
|
|
287
301
|

|
|
288
302
|
|
|
303
|
+
### Syntax Trees
|
|
304
|
+
|
|
305
|
+
→ [rsyntaxtree](https://github.com/yohasebe/rsyntaxtree)
|
|
306
|
+
|
|
307
|
+
`Doc#syntax_tree` (and `Span#syntax_tree`) converts the parse into rsyntaxtree bracket notation and can render it as an image. rsyntaxtree (>= 2.4.0) is an optional dependency: `gem install rsyntaxtree`.
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
require "ruby-spacy"
|
|
311
|
+
|
|
312
|
+
nlp = Spacy::Language.new("en_core_web_sm")
|
|
313
|
+
doc = nlp.read("The quick brown fox jumped over the lazy dog near the river.")
|
|
314
|
+
|
|
315
|
+
doc.syntax_tree # => "[S [%NP [DET The] [ADJ quick] ...] ...]"
|
|
316
|
+
File.binwrite("tree.png", doc.syntax_tree(format: :png))
|
|
317
|
+
doc.syntax_tree(style: :chunks) # shallow tree with noun chunks
|
|
318
|
+
doc.syntax_tree(morphology: true) # attach morphology tables to the leaves
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
<img src="https://github.com/yohasebe/ruby-spacy/blob/main/examples/rsyntaxtree/outputs/tree_en_projection.png" alt="English projection tree" width="560">
|
|
322
|
+
|
|
323
|
+
<img src="https://github.com/yohasebe/ruby-spacy/blob/main/examples/rsyntaxtree/outputs/tree_en_morphology.png" alt="English projection tree with morphology tables" width="760">
|
|
324
|
+
|
|
325
|
+
<img src="https://github.com/yohasebe/ruby-spacy/blob/main/examples/rsyntaxtree/outputs/tree_ja_projection.png" alt="Japanese projection tree with named entities" width="500">
|
|
326
|
+
|
|
327
|
+
Two styles are available: `:projection` (default; a phrase-structure-like tree projected from the head words) and `:chunks` (a shallow tree with noun chunks). In both, noun chunks are shaded grey, and a chunk that is also a named entity is shaded orange and labeled with the entity type (`entities: false` to disable). The blue and green are rsyntaxtree's default palette, not marking of any kind; only the shading carries meaning. Punctuation is omitted (`punctuation: true` to keep it). The `format:` option accepts `:bracket` (default), `:svg`, `:png`, `:pdf`, `:tikz`, and `:json`; any other keywords are passed through to rsyntaxtree (e.g. `fontsize: 12`).
|
|
328
|
+
|
|
329
|
+
The notation is rsyntaxtree-flavored: it can contain AVMs (`#(...#)`), region backgrounds (`%`), and in-word space joins (`<>`). A doc must hold a single sentence; for a multi-sentence doc, use `doc.sents.map { |s| s.syntax_tree }`.
|
|
330
|
+
|
|
331
|
+
**Annotation schemes differ between models.** spaCy's English models make the preposition the head of its phrase, so projection trees contain `PP` nodes. UD-style models (Japanese, Russian, Chinese, and most others) attach prepositions and particles to the noun instead, so no `PP` appears. Phrase labels come from the head's POS tag and follow whatever scheme the model uses.
|
|
332
|
+
|
|
333
|
+
**Entity highlighting requires noun chunks.** An entity gets its colored background only where it coincides with a noun chunk. In languages whose models have no noun chunk iterator (Russian, Chinese, Korean, Polish, ...) `entities: true` highlights nothing and `style: :chunks` raises `ArgumentError`.
|
|
334
|
+
|
|
335
|
+
**Right-to-left languages.** spaCy ships no pipelines for Arabic, Hebrew, or other RTL languages, so one has to come from outside (see [Using an External Pipeline](#using-an-external-pipeline)). Their trees are drawn mirrored automatically; pass `mirror: "off"` to disable.
|
|
336
|
+
|
|
337
|
+
See the [syntax tree gallery](docs/syntax_trees.md) for rendered trees in six languages, and `examples/rsyntaxtree/` for the scripts that generate them.
|
|
338
|
+
|
|
289
339
|
### Named Entity Recognition
|
|
290
340
|
|
|
291
341
|
→ [spaCy: Named entities](https://spacy.io/usage/spacy-101#annotations-ner)
|
|
@@ -525,6 +575,30 @@ Output:
|
|
|
525
575
|
| 9 | アルザス | 0.5644999742507935 |
|
|
526
576
|
| 10 | 南仏 | 0.5547999739646912 |
|
|
527
577
|
|
|
578
|
+
### Matcher
|
|
579
|
+
|
|
580
|
+
`Matcher` finds token sequences with rule-based patterns.
|
|
581
|
+
|
|
582
|
+
```ruby
|
|
583
|
+
require "ruby-spacy"
|
|
584
|
+
|
|
585
|
+
nlp = Spacy::Language.new("en_core_web_sm")
|
|
586
|
+
|
|
587
|
+
matcher = nlp.matcher
|
|
588
|
+
matcher.add("GREETING", [[{ LOWER: "hello" }, { IS_PUNCT: true }, { LOWER: "world" }]])
|
|
589
|
+
|
|
590
|
+
doc = nlp.read("Hello, world!")
|
|
591
|
+
matcher.match(doc).each do |match|
|
|
592
|
+
span = doc.span(match[:start_index]..match[:end_index])
|
|
593
|
+
puts "#{match[:label]}: #{span.text}"
|
|
594
|
+
end
|
|
595
|
+
# => GREETING: Hello, world
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
`Matcher#match` returns an array of hashes with `:match_id` (the label's numeric id), `:start_index`, `:end_index`, and `:label` (the label string).
|
|
599
|
+
|
|
600
|
+
See `examples/rule_based_matching/` for more examples.
|
|
601
|
+
|
|
528
602
|
### PhraseMatcher
|
|
529
603
|
|
|
530
604
|
`PhraseMatcher` is more efficient than `Matcher` for matching large terminology lists. It's ideal for extracting known entities like product names, company names, or domain-specific terms.
|
|
@@ -974,6 +1048,14 @@ See `examples/llm/` for complete scripts, including a spaCy-vs-LLM NER compariso
|
|
|
974
1048
|
|
|
975
1049
|
## Advanced Usage
|
|
976
1050
|
|
|
1051
|
+
### Thread Safety
|
|
1052
|
+
|
|
1053
|
+
All spaCy calls must be made from the same thread that initialized PyCall (normally the main thread). Calling the spaCy pipeline from another thread — e.g. `nlp.read(text)` inside a `Thread.new` block, a Rails multi-threaded server, or a Sidekiq worker — will hang the entire process.
|
|
1054
|
+
|
|
1055
|
+
Note that attribute access (such as `nlp.pipe_names`) works from other threads, so the failure mode is not obvious: the process only freezes when the pipeline actually runs. The root cause is currently unknown (it is specific to spaCy pipeline execution; other GIL-releasing Python calls work fine from other threads).
|
|
1056
|
+
|
|
1057
|
+
If you need concurrent processing, serialize all Python calls onto a single dedicated thread, for example with a worker thread and a queue.
|
|
1058
|
+
|
|
977
1059
|
### Setting a Timeout
|
|
978
1060
|
|
|
979
1061
|
You can set a timeout for the `Spacy::Language.new` method:
|
|
@@ -982,6 +1064,8 @@ You can set a timeout for the `Spacy::Language.new` method:
|
|
|
982
1064
|
nlp = Spacy::Language.new("en_core_web_sm", timeout: 120) # Set timeout to 120 seconds
|
|
983
1065
|
```
|
|
984
1066
|
|
|
1067
|
+
If the model does not finish loading within the given seconds, a `RuntimeError` is raised. Pass `timeout: nil` to wait indefinitely.
|
|
1068
|
+
|
|
985
1069
|
### Document Serialization
|
|
986
1070
|
|
|
987
1071
|
You can serialize processed documents to binary format for caching or storage. This is useful when you want to avoid re-processing the same text multiple times.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Syntax Tree Gallery
|
|
2
|
+
|
|
3
|
+
Trees drawn by `Doc#syntax_tree` with the [rsyntaxtree](https://github.com/yohasebe/rsyntaxtree) gem. The scripts in `examples/rsyntaxtree/` generate every image here.
|
|
4
|
+
|
|
5
|
+
Noun chunks are shaded grey; a chunk that is also a named entity is shaded orange and labeled with the entity type. The blue and green are rsyntaxtree's default palette, not marking of any kind. Phrase labels come from the head's POS tag, so they follow whatever annotation scheme the model uses. Both are explained in the [README](../README.md#syntax-trees).
|
|
6
|
+
|
|
7
|
+
## English (`en_core_web_sm`)
|
|
8
|
+
|
|
9
|
+
Projection, the default style:
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
Chunks style:
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
With morphology (a shorter sentence — the tables are tall):
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Japanese (`ja_core_news_sm`)
|
|
22
|
+
|
|
23
|
+
Projection, with PERSON and GPE highlighted:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
Chunks style:
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## Russian (`ru_core_news_sm`)
|
|
32
|
+
|
|
33
|
+
No noun chunk iterator, so `style: :chunks` is unavailable.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
With morphology — Russian marks case, gender, animacy, aspect, and voice:
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
## German (`de_core_news_sm`)
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
## Chinese (`zh_core_web_sm`)
|
|
46
|
+
|
|
47
|
+
No noun chunk iterator, so `style: :chunks` is unavailable.
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
## Arabic (right-to-left)
|
|
52
|
+
|
|
53
|
+
spaCy ships no Arabic pipeline, so this tree was parsed with [Stanza](https://stanfordnlp.github.io/stanza/) via [spacy-stanza](https://github.com/explosion/spacy-stanza) and wrapped with `Spacy::Language.new(py_nlp:)`. Right-to-left languages are drawn mirrored automatically. This pipeline has no noun chunks, so nothing is shaded. See `examples/rsyntaxtree/syntax_tree_ar.rb`.
|
|
54
|
+
|
|
55
|
+

|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Arabic via an external pipeline. spaCy ships no trained pipeline for
|
|
4
|
+
# Arabic (or any other right-to-left language), so this example builds one
|
|
5
|
+
# with Stanza through spacy-stanza. These are NOT ruby-spacy dependencies;
|
|
6
|
+
# install them yourself first:
|
|
7
|
+
#
|
|
8
|
+
# pip install spacy-stanza
|
|
9
|
+
# pip install --upgrade "stanza>=1.10" # spacy-stanza's stanza pin is old and conflicts with current torch
|
|
10
|
+
# python -c "import stanza; stanza.download('ar')"
|
|
11
|
+
#
|
|
12
|
+
# Note: at exit, spacy-stanza under PyCall prints a harmless
|
|
13
|
+
# multiprocessing.resource_tracker LoadError to stderr (sys.executable
|
|
14
|
+
# points to ruby, not python); the results are unaffected.
|
|
15
|
+
|
|
16
|
+
# add path to ruby-spacy lib to load path
|
|
17
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
|
18
|
+
|
|
19
|
+
require "ruby-spacy"
|
|
20
|
+
require "fileutils"
|
|
21
|
+
|
|
22
|
+
# Requires the rsyntaxtree gem (>= 2.4.0): gem install rsyntaxtree
|
|
23
|
+
|
|
24
|
+
py_nlp = PyCall.import_module("spacy_stanza")
|
|
25
|
+
.load_pipeline("ar", processors: "tokenize,pos,lemma,depparse", verbose: false)
|
|
26
|
+
nlp = Spacy::Language.new(py_nlp: py_nlp)
|
|
27
|
+
doc = nlp.read("قرأ الطالب الجديد كتابا مثيرا في المكتبة أمس.")
|
|
28
|
+
|
|
29
|
+
puts doc.syntax_tree
|
|
30
|
+
|
|
31
|
+
# Right-to-left languages are drawn mirrored automatically (pass
|
|
32
|
+
# mirror: "off" to disable)
|
|
33
|
+
output_dir = File.join(File.dirname(__FILE__), "outputs")
|
|
34
|
+
FileUtils.mkdir_p(output_dir)
|
|
35
|
+
File.binwrite(File.join(output_dir, "tree_ar_projection.png"),
|
|
36
|
+
doc.syntax_tree(format: :png))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# add path to ruby-spacy lib to load path
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
|
5
|
+
|
|
6
|
+
require "ruby-spacy"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
# Requires the rsyntaxtree gem (>= 2.4.0): gem install rsyntaxtree
|
|
10
|
+
|
|
11
|
+
nlp = Spacy::Language.new("de_core_news_sm")
|
|
12
|
+
doc = nlp.read("Der alte Professor las gestern ein interessantes Buch.")
|
|
13
|
+
|
|
14
|
+
puts doc.syntax_tree
|
|
15
|
+
|
|
16
|
+
output_dir = File.join(File.dirname(__FILE__), "outputs")
|
|
17
|
+
FileUtils.mkdir_p(output_dir)
|
|
18
|
+
File.binwrite(File.join(output_dir, "tree_de_projection.png"),
|
|
19
|
+
doc.syntax_tree(format: :png))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# add path to ruby-spacy lib to load path
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
|
5
|
+
|
|
6
|
+
require "ruby-spacy"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
# Requires the rsyntaxtree gem (>= 2.4.0): gem install rsyntaxtree
|
|
10
|
+
|
|
11
|
+
nlp = Spacy::Language.new("en_core_web_sm")
|
|
12
|
+
doc = nlp.read("The quick brown fox jumped over the lazy dog near the river.")
|
|
13
|
+
|
|
14
|
+
puts doc.syntax_tree
|
|
15
|
+
|
|
16
|
+
output_dir = File.join(File.dirname(__FILE__), "outputs")
|
|
17
|
+
FileUtils.mkdir_p(output_dir)
|
|
18
|
+
File.binwrite(File.join(output_dir, "tree_en_projection.png"),
|
|
19
|
+
doc.syntax_tree(format: :png))
|
|
20
|
+
File.binwrite(File.join(output_dir, "tree_en_chunks.png"),
|
|
21
|
+
doc.syntax_tree(style: :chunks, format: :png))
|
|
22
|
+
# A shorter sentence for the morphology tables: every leaf becomes a box, so a
|
|
23
|
+
# long sentence draws a figure too wide to read
|
|
24
|
+
compact = nlp.read("He was sitting under the old tree.")
|
|
25
|
+
File.binwrite(File.join(output_dir, "tree_en_morphology.png"),
|
|
26
|
+
compact.syntax_tree(format: :png, morphology: true))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# add path to ruby-spacy lib to load path
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
|
5
|
+
|
|
6
|
+
require "ruby-spacy"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
# Requires the rsyntaxtree gem (>= 2.4.0): gem install rsyntaxtree
|
|
10
|
+
|
|
11
|
+
nlp = Spacy::Language.new("ja_core_news_sm")
|
|
12
|
+
doc = nlp.read("太郎は昨日、東京で花子に古い本を渡した。")
|
|
13
|
+
|
|
14
|
+
puts doc.syntax_tree
|
|
15
|
+
|
|
16
|
+
output_dir = File.join(File.dirname(__FILE__), "outputs")
|
|
17
|
+
FileUtils.mkdir_p(output_dir)
|
|
18
|
+
File.binwrite(File.join(output_dir, "tree_ja_projection.png"),
|
|
19
|
+
doc.syntax_tree(format: :png))
|
|
20
|
+
File.binwrite(File.join(output_dir, "tree_ja_chunks.png"),
|
|
21
|
+
doc.syntax_tree(style: :chunks, format: :png))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# add path to ruby-spacy lib to load path
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
|
5
|
+
|
|
6
|
+
require "ruby-spacy"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
# Requires the rsyntaxtree gem (>= 2.4.0): gem install rsyntaxtree
|
|
10
|
+
|
|
11
|
+
nlp = Spacy::Language.new("ru_core_news_sm")
|
|
12
|
+
doc = nlp.read("Старый профессор читал интересную книгу в библиотеке.")
|
|
13
|
+
|
|
14
|
+
puts doc.syntax_tree
|
|
15
|
+
|
|
16
|
+
# ru_core_news_sm has no noun chunk iterator, so style: :chunks raises
|
|
17
|
+
# ArgumentError for this language; Russian morphology is rich, though
|
|
18
|
+
output_dir = File.join(File.dirname(__FILE__), "outputs")
|
|
19
|
+
FileUtils.mkdir_p(output_dir)
|
|
20
|
+
File.binwrite(File.join(output_dir, "tree_ru_projection.png"),
|
|
21
|
+
doc.syntax_tree(format: :png))
|
|
22
|
+
File.binwrite(File.join(output_dir, "tree_ru_morphology.png"),
|
|
23
|
+
doc.syntax_tree(format: :png, morphology: true))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# add path to ruby-spacy lib to load path
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
|
5
|
+
|
|
6
|
+
require "ruby-spacy"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
# Requires the rsyntaxtree gem (>= 2.4.0): gem install rsyntaxtree
|
|
10
|
+
|
|
11
|
+
nlp = Spacy::Language.new("zh_core_web_sm")
|
|
12
|
+
doc = nlp.read("老教授昨天在图书馆读了一本有趣的书。")
|
|
13
|
+
|
|
14
|
+
puts doc.syntax_tree
|
|
15
|
+
|
|
16
|
+
# zh_core_web_sm has no noun chunk iterator, so style: :chunks raises
|
|
17
|
+
# ArgumentError for this language
|
|
18
|
+
output_dir = File.join(File.dirname(__FILE__), "outputs")
|
|
19
|
+
FileUtils.mkdir_p(output_dir)
|
|
20
|
+
File.binwrite(File.join(output_dir, "tree_zh_projection.png"),
|
|
21
|
+
doc.syntax_tree(format: :png))
|
|
@@ -14,7 +14,7 @@ doc = nlp.read("Barack Obama was the 44th president of the United States")
|
|
|
14
14
|
matches = matcher.match(doc)
|
|
15
15
|
|
|
16
16
|
matches.each do |match|
|
|
17
|
-
span = Spacy::Span.new(doc, start_index: match[:start_index], end_index: match[:end_index], options: { label: match[:
|
|
17
|
+
span = Spacy::Span.new(doc, start_index: match[:start_index], end_index: match[:end_index], options: { label: match[:label] })
|
|
18
18
|
puts "#{span.text} / #{span.label}"
|
|
19
19
|
end
|
|
20
20
|
|