Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e3578c2
Add initial pyproject.toml
mart-r Aug 27, 2026
54d89d3
Add initial LLM component code
mart-r Aug 27, 2026
6e928c7
Add pytest as test-time dependency
mart-r Aug 27, 2026
a61e967
Add creation method to linker
mart-r Aug 27, 2026
016d5c1
Fix forward reference
mart-r Aug 27, 2026
024452f
Minor linting tweaks
mart-r Aug 27, 2026
6c7b581
Add creation method to ner component
mart-r Aug 27, 2026
c40d0fc
Fix issue with component types
mart-r Aug 27, 2026
98667d7
Fix typo
mart-r Aug 27, 2026
85cfb62
Add simple registration tests
mart-r Aug 27, 2026
7cc65cd
Add workflow for LLM components
mart-r Aug 27, 2026
095dbd5
Make sure NER component sets dected name
mart-r Aug 27, 2026
2e4aacc
Update tests to include new arguments
mart-r Aug 27, 2026
19970e6
Separate linked entities from NER entities in LLM implementation
mart-r Aug 27, 2026
3806f2b
Add a few tests with mock server as well
mart-r Aug 27, 2026
c4910ac
Fix small linting issue
mart-r Aug 27, 2026
d203cc8
Fix small linting issue - for real
mart-r Aug 27, 2026
d693f67
Add spacy/meta-cat optional extras to deps
mart-r Aug 28, 2026
6cf05eb
Make linker set similairity and accuracy to 1
mart-r Aug 28, 2026
f189af8
Add fix for incoming markdown table
mart-r Aug 28, 2026
67955b7
Add link candidates to entities
mart-r Aug 28, 2026
b55fc22
Fix minor issue with confidence/accuracy setting for linker
mart-r Aug 28, 2026
193be14
Minor linting (ruff) fix
mart-r Aug 28, 2026
9e3a1ba
Add names to linker and ner component (to show up in pipe description)
mart-r Aug 28, 2026
9ec2d57
Fix minor linting issue
mart-r Aug 28, 2026
c1550a2
Added README with examples
mart-r Aug 28, 2026
4affc7b
Merge branch 'main' into feat/medcat-llm-components/CPDT-108-add-LLM-…
mart-r Aug 28, 2026
480961c
Fix local install reference in workflow
mart-r Aug 28, 2026
8a2e1f5
Fix doc string r-prefix
mart-r Aug 28, 2026
b45197c
Add missing import in README example code
mart-r Sep 10, 2026
ee11945
Remove unnecessary comment from example in README
mart-r Sep 10, 2026
28a576c
Add a few save-load tests
mart-r Sep 10, 2026
5fd0dfe
Fix model save issue due to required init args for component-specific…
mart-r Sep 10, 2026
6a3ec76
Add a few extra asserts in terms of save/load and config options
mart-r Sep 10, 2026
bc8e2b8
Add new section to README regarding loading model off disk
mart-r Sep 10, 2026
6a157ed
Add a README comment regarding when config dict is needed
mart-r Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/medcat-llm-components_ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: medcat-llm-components - CI (test | publish)

on:
push:
branches: [ main ]
tags:
- 'medcat-llm-components/v*.*.*'
pull_request:
paths:
- 'medcat-plugins/llm-comps/**'
- '.github/workflows/medcat-llm-components**'

permissions:
id-token: write

defaults:
run:
working-directory: ./medcat-plugins/llm-comps

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.10', '3.11', '3.12' ]
max-parallel: 4
steps:
- uses: actions/checkout@v7
- name: Install uv for Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
- name: Install the project
run: |
uv sync --all-extras --dev
uv run python -m ensurepip
uv run python -m pip install --upgrade pip
uv run python -m pip install "../../medcat[spacy]"
- name: Check types
run: |
uv run python -m mypy --follow-imports=normal src/medcat_llm_components
- name: Ruff linting
run: |
uv run ruff check src/medcat_llm_components --preview
- name: Test
run: |
uv run python -m pytest tests

publish-to-test-PyPI:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout main
uses: actions/checkout@v7
with:
fetch-depth: 0 # fetch all history
fetch-tags: true # fetch tags explicitly

- name: Install uv for Python 3.10
uses: astral-sh/setup-uv@v7
with:
python-version: '3.10'
enable-cache: true

- name: Install dependencies
run: |
uv run python -m ensurepip

- name: Set timestamp-based dev version
run: |
TS=$(date -u +"%Y%m%d%H%M%S")
echo "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MEDCAT_LLM_COMPONENTS=0.2.2.dev${TS}" >> $GITHUB_ENV

- name: Build package
run: |
uv build

- name: Publish distribution to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository_url: https://test.pypi.org/legacy/
packages_dir: medcat-plugins/llm-comps/dist

publish-to-PyPI:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
needs: build
steps:
- name: Checkout main
uses: actions/checkout@v7

- name: Install uv for Python 3.10
uses: astral-sh/setup-uv@v7
with:
python-version: '3.10'
enable-cache: true

- name: Install dependencies
run: |
uv run python -m ensurepip

- name: Build client package
run: |
uv build

- name: Publish production distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages_dir: medcat-plugins/llm-comps/dist
163 changes: 163 additions & 0 deletions medcat-plugins/llm-comps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
## MedCAT LLM component examples

This project aims to provide a few simple examples for LLM based components (for NER and/or linking).

**These are not designed to be the best implementation. They are designed as an example.**

### How to install

```
pip install medcat-llm-components
```

### How to use

#### Add to existing model

This way you can use the CDB already tied to the model.

```python
from medcat.cat import CAT
from medcat_llm_components.ner import LLMNERConfig
from medcat_llm_components.linker import LLMLinkConfig
### INPUT ###
# existing model
model_path = ""
# the URL to the (e.g) ollama instance
base_url = "my_ollama_ip:port/whatever"
# the model to use
llm_model = "gemma:2b"

### AUTOMATION ###
# NOTE: don't need to use both NER and linker
# we just have one example for both
cat = CAT.load_model_pack(model_path)
# create configs
# ner
ner_cnf = LLMNERConfig(
base_url=base_url,
model=llm_model,
# for other optional arguments such as prompt
# refer to code or IDE inspection
)
# linker
linking_cnf = LLMLinkConfig(
base_url=base_url,
model=llm_model,
# for other optional arguments such as prompt
# refer to code or IDE inspection
)
# update model pack
# ner
cat.config.components.ner.comp_name = "llm_ner"
# NOTE: different path for NER and linker
cat.config.components.ner.custom_cnf = ner_cnf
# linking
cat.config.components.linking.comp_name = "llm_linker"
# NOTE: different path for NER and linker
cat.config.components.linking.additional = linking_cnf
# recreate pipe
cat._recreate_pipe()

print(cat.describe_pipeline())
# ready to use!
print(cat.get_entities("Anhedonia"))
```

### Create as part of a new model pack

You can create a new model pack yourself.
This will almost certainly be more involved.

```python
from medcat.cat import CAT
from medcat.cdb import CDB
from medcat.vocab import Vocab
from medcat.config import Config

from medcat_llm_components.ner import LLMNERConfig
from medcat_llm_components.linker import LLMLinkConfig

# the URL to the (e.g) ollama instance
base_url = "my_ollama_ip:port/whatever"
# the model to use
llm_model = "gemma:2b"

# create configs
# ner
ner_cnf = LLMNERConfig(
base_url=base_url,
model=llm_model,
# for other optional arguments such as prompt
# refer to code or IDE inspection
)
# linker
linking_cnf = LLMLinkConfig(
base_url=base_url,
model=llm_model,
# for other optional arguments such as prompt
# refer to code or IDE inspection
)

config = Config()
config.components.ner.comp_name = "llm_ner"
config.components.ner.custom_cnf = ner_cnf
config.components.linking.comp_name = "llm_linker"
config.components.linking.additional = linking_cnf

vocab = Vocab()
# or load a CDB on its own with CDB.load("my_cdb.zip")
cdb = CDB(config)

cat = CAT(cdb, vocab)
print(cat.describe_pipeline())
# ready to use!
```

### Instruct an existing LLM-component-saved model:

If you've already got a model that's saved with the config specific to your
use case (e.g URL and model and the like) then you can just load it up and use
it like any other model.

However, if you've got a model saved with the right components, but the wrong
URL or model (or you just wish to change it) you can load and change these in
one go as follows:

```python
from medcat.cat import CAT

MODEL_PATH = "my_model_path"

LLM_URL = "api_endpoint_url:80/v1"
LLM_MODEL = "gemma:2b"

# NOTE: this is only necessary if the URL or other
# info has changed between save and load
config_dict = {
"components": {
# for NER
"ner": {
"custom_cnf": {
"base_url": LLM_URL,
"model": LLM_MODEL,
}
},
# for linking
"linking": {
"additional": {
"base_url": LLM_URL,
"model": LLM_MODEL,
}
}
}
}

cat = CAT.load_model_pack(
MODEL_PATH, config_dict=config_dict,
)
print(
"CAT.get_entities",
cat.get_entities("Patient had kidney disease")['entities']
)
```
63 changes: 63 additions & 0 deletions medcat-plugins/llm-comps/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[build-system]
requires = ["setuptools>=61.0", "wheel", "setuptools_scm>=8"]
build-backend = "setuptools.build_meta"

[project]
name = "medcat_llm_components"
dynamic = ["version"]
description = ""
readme = "README.md"
license = { text = "Apache-2.0" }
authors = [
{ name="Mart Ratas", email="mart.ratas@kcl.ac.uk" }
]
requires-python = ">=3.10"

keywords = ["MedCAT"]

classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"License :: OSI Approved :: Apache Software License"
]

dependencies = [
"medcat[spacy,meta-cat]>=2.5",
"pydantic",
"openai~=3.3.1",
]

[project.optional-dependencies]
dev = [
"ruff",
"mypy",
"pytest",
]

# entry-points to add onto medcat
[project.entry-points."medcat.plugins"]
llm_components = "medcat_llm_components"

[project.urls]
Homepage = "https://github.com/CogStack/cogstack-nlp/tree/main/medcat-plugins/llm-comps"
Repository = "https://github.com/CogStack/cogstack-nlp/tree/main/medcat-plugins/llm-comps"
Issues = "https://github.com/CogStack/cogstack-nlp/issues"

[tool.setuptools_scm]
root = "../.."
tag_regex = "^medcat-llm-components/v(?P<version>\\d+(?:\\.\\d+)*)(?:[ab]\\d+|rc\\d+)?$"
version_scheme = "post-release"
local_scheme = "no-local-version"
git_describe_command = "git describe --dirty --tags --long --match 'medcat-llm-components/v*'"

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools.package-data]
"medcat_llm_components" = ["py.typed"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .registration import do_registration as __register

__register()
Loading
Loading