groundworkers 0.2.0__tar.gz → 0.3.3__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 (111) hide show
  1. groundworkers-0.3.3/PKG-INFO +179 -0
  2. groundworkers-0.3.3/README.md +133 -0
  3. {groundworkers-0.2.0 → groundworkers-0.3.3}/pyproject.toml +35 -5
  4. groundworkers-0.3.3/src/groundworkers/adapters/cdm.py +43 -0
  5. groundworkers-0.3.3/src/groundworkers/adapters/llm.py +177 -0
  6. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/adapters/omop_emb.py +52 -7
  7. groundworkers-0.3.3/src/groundworkers/adapters/omop_graph.py +508 -0
  8. groundworkers-0.3.3/src/groundworkers/app.py +213 -0
  9. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/base/__init__.py +0 -3
  10. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/base/errors.py +3 -0
  11. groundworkers-0.3.3/src/groundworkers/base/server.py +125 -0
  12. groundworkers-0.3.3/src/groundworkers/bootstrap.py +120 -0
  13. groundworkers-0.3.3/src/groundworkers/config.py +276 -0
  14. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/criteria-gate-yesno/guidance.md +57 -0
  15. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/criteria-gate-yesno/manifest.yaml +22 -0
  16. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/diagnosis-role-modifier/guidance.md +90 -0
  17. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/diagnosis-role-modifier/manifest.yaml +28 -0
  18. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/domain-routing/guidance.md +45 -0
  19. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/domain-routing/manifest.yaml +20 -0
  20. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/domain-routing/rules.yaml +17 -0
  21. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/drug-concept-granularity/guidance.md +52 -0
  22. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/drug-concept-granularity/manifest.yaml +22 -0
  23. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/drug-concept-granularity/rules.yaml +22 -0
  24. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/history-of-event/guidance.md +58 -0
  25. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/history-of-event/manifest.yaml +18 -0
  26. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/standard-concept-preference/guidance.md +55 -0
  27. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/standard-concept-preference/manifest.yaml +16 -0
  28. groundworkers-0.3.3/src/groundworkers/knowledge/packs/core/standard-concept-preference/rules.yaml +50 -0
  29. groundworkers-0.3.3/src/groundworkers/server.py +133 -0
  30. groundworkers-0.3.3/src/groundworkers/services/__init__.py +9 -0
  31. groundworkers-0.3.3/src/groundworkers/services/domain.py +116 -0
  32. groundworkers-0.3.3/src/groundworkers/services/graph.py +584 -0
  33. groundworkers-0.3.3/src/groundworkers/services/grounding.py +118 -0
  34. groundworkers-0.3.3/src/groundworkers/services/knowledge/__init__.py +15 -0
  35. groundworkers-0.3.3/src/groundworkers/services/knowledge/catalogue.py +176 -0
  36. groundworkers-0.3.3/src/groundworkers/services/knowledge/models.py +100 -0
  37. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/services/mapping.py +74 -37
  38. groundworkers-0.3.3/src/groundworkers/services/source_planning/__init__.py +79 -0
  39. groundworkers-0.3.3/src/groundworkers/services/source_planning/assisted.py +202 -0
  40. groundworkers-0.3.3/src/groundworkers/services/source_planning/canonical_headers.py +153 -0
  41. groundworkers-0.3.3/src/groundworkers/services/source_planning/classifier.py +413 -0
  42. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposer.py +67 -0
  43. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/__init__.py +1 -0
  44. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/csv_.py +61 -0
  45. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/ddl_.py +217 -0
  46. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/docx_.py +149 -0
  47. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/json_.py +172 -0
  48. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/pdf_.py +113 -0
  49. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/xlsx_.py +112 -0
  50. groundworkers-0.3.3/src/groundworkers/services/source_planning/decomposers/xml_.py +181 -0
  51. groundworkers-0.3.3/src/groundworkers/services/source_planning/detector.py +85 -0
  52. groundworkers-0.3.3/src/groundworkers/services/source_planning/models.py +308 -0
  53. groundworkers-0.3.3/src/groundworkers/services/source_planning/normalisation.py +264 -0
  54. groundworkers-0.3.3/src/groundworkers/services/source_planning/provenance.py +29 -0
  55. groundworkers-0.3.3/src/groundworkers/services/source_planning/router.py +194 -0
  56. groundworkers-0.3.3/src/groundworkers/services/source_planning/serialisation.py +59 -0
  57. groundworkers-0.3.3/src/groundworkers/services/source_planning/service.py +394 -0
  58. groundworkers-0.3.3/src/groundworkers/services/source_planning/source_profiles/__init__.py +11 -0
  59. groundworkers-0.3.3/src/groundworkers/services/source_planning/source_profiles/base.py +51 -0
  60. groundworkers-0.3.3/src/groundworkers/services/source_planning/source_profiles/redcap.py +41 -0
  61. groundworkers-0.3.3/src/groundworkers/services/source_planning/source_profiles/registry.py +40 -0
  62. groundworkers-0.3.3/src/groundworkers/services/source_planning/warnings.py +29 -0
  63. groundworkers-0.3.3/src/groundworkers/services/text/__init__.py +29 -0
  64. groundworkers-0.3.3/src/groundworkers/services/text/models.py +54 -0
  65. groundworkers-0.3.3/src/groundworkers/services/text/prompts.py +80 -0
  66. groundworkers-0.3.3/src/groundworkers/services/text/service.py +112 -0
  67. groundworkers-0.2.0/src/groundworkers/adapters/omop_vocab.py → groundworkers-0.3.3/src/groundworkers/services/vocab.py +131 -218
  68. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/tools/concept_tools.py +106 -11
  69. groundworkers-0.3.3/src/groundworkers/tools/domain_tools.py +45 -0
  70. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/tools/embedding_tools.py +18 -0
  71. groundworkers-0.3.3/src/groundworkers/tools/knowledge_tools.py +195 -0
  72. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/tools/mapping_tools.py +48 -22
  73. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/tools/resolver_tools.py +50 -6
  74. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/tools/search_tools.py +21 -46
  75. groundworkers-0.3.3/src/groundworkers/tools/source_planning_tools.py +141 -0
  76. groundworkers-0.3.3/src/groundworkers/tools/system_tools.py +164 -0
  77. groundworkers-0.3.3/src/groundworkers/tools/text_tools.py +209 -0
  78. groundworkers-0.3.3/src/groundworkers/transports/__init__.py +1 -0
  79. groundworkers-0.3.3/src/groundworkers/transports/rest/__init__.py +5 -0
  80. groundworkers-0.3.3/src/groundworkers/transports/rest/api.py +135 -0
  81. groundworkers-0.3.3/src/groundworkers/transports/rest/models.py +95 -0
  82. groundworkers-0.3.3/src/groundworkers.egg-info/PKG-INFO +179 -0
  83. groundworkers-0.3.3/src/groundworkers.egg-info/SOURCES.txt +93 -0
  84. groundworkers-0.3.3/src/groundworkers.egg-info/entry_points.txt +5 -0
  85. groundworkers-0.3.3/src/groundworkers.egg-info/requires.txt +47 -0
  86. groundworkers-0.3.3/tests/test_bootstrap_config.py +151 -0
  87. groundworkers-0.3.3/tests/test_rest_api.py +210 -0
  88. groundworkers-0.3.3/tests/test_server_registry.py +419 -0
  89. groundworkers-0.2.0/PKG-INFO +0 -208
  90. groundworkers-0.2.0/README.md +0 -178
  91. groundworkers-0.2.0/src/groundworkers/adapters/omop_graph.py +0 -780
  92. groundworkers-0.2.0/src/groundworkers/app.py +0 -122
  93. groundworkers-0.2.0/src/groundworkers/base/server.py +0 -52
  94. groundworkers-0.2.0/src/groundworkers/base/sql.py +0 -109
  95. groundworkers-0.2.0/src/groundworkers/config.py +0 -153
  96. groundworkers-0.2.0/src/groundworkers/server.py +0 -62
  97. groundworkers-0.2.0/src/groundworkers/services/__init__.py +0 -3
  98. groundworkers-0.2.0/src/groundworkers/tools/system_tools.py +0 -67
  99. groundworkers-0.2.0/src/groundworkers.egg-info/PKG-INFO +0 -208
  100. groundworkers-0.2.0/src/groundworkers.egg-info/SOURCES.txt +0 -32
  101. groundworkers-0.2.0/src/groundworkers.egg-info/entry_points.txt +0 -2
  102. groundworkers-0.2.0/src/groundworkers.egg-info/requires.txt +0 -26
  103. groundworkers-0.2.0/tests/test_server_registry.py +0 -97
  104. groundworkers-0.2.0/tests/test_sql_resource.py +0 -61
  105. {groundworkers-0.2.0 → groundworkers-0.3.3}/setup.cfg +0 -0
  106. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/__init__.py +0 -0
  107. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/adapters/__init__.py +0 -0
  108. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/base/results.py +0 -0
  109. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers/tools/__init__.py +0 -0
  110. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers.egg-info/dependency_links.txt +0 -0
  111. {groundworkers-0.2.0 → groundworkers-0.3.3}/src/groundworkers.egg-info/top_level.txt +0 -0
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: groundworkers
3
+ Version: 0.3.3
4
+ Summary: Groundworkers MCP server — read-only agentive access to OMOP vocabularies, concept graphs, and embeddings.
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: fastapi<1.0,>=0.110
8
+ Requires-Dist: mcp[cli]<2,>=1
9
+ Requires-Dist: pydantic<3,>=2
10
+ Requires-Dist: pyyaml<7,>=6
11
+ Requires-Dist: SQLAlchemy<3,>=2
12
+ Requires-Dist: psycopg[binary]<4,>=3.1
13
+ Requires-Dist: oa-configurator>=0.1.2
14
+ Requires-Dist: omop-graph>=1.3.0
15
+ Requires-Dist: omop-emb>=1.1.1
16
+ Requires-Dist: uvicorn[standard]<1.0,>=0.29
17
+ Provides-Extra: llm
18
+ Requires-Dist: openai>=1.0; extra == "llm"
19
+ Provides-Extra: xlsx
20
+ Requires-Dist: openpyxl<4,>=3.1; extra == "xlsx"
21
+ Provides-Extra: pdf
22
+ Requires-Dist: pdfplumber>=0.10; extra == "pdf"
23
+ Provides-Extra: docx
24
+ Requires-Dist: python-docx>=1.0; extra == "docx"
25
+ Provides-Extra: all-source
26
+ Requires-Dist: openpyxl<4,>=3.1; extra == "all-source"
27
+ Requires-Dist: pdfplumber>=0.10; extra == "all-source"
28
+ Requires-Dist: python-docx>=1.0; extra == "all-source"
29
+ Provides-Extra: embedding-pgvector
30
+ Requires-Dist: omop-emb[pgvector]>=1.1.1; extra == "embedding-pgvector"
31
+ Provides-Extra: embedding-faiss
32
+ Requires-Dist: omop-emb[faiss-cpu]>=1.1.1; extra == "embedding-faiss"
33
+ Provides-Extra: dev
34
+ Requires-Dist: httpx<1,>=0.27; extra == "dev"
35
+ Requires-Dist: ipython>=8.0; extra == "dev"
36
+ Requires-Dist: tornado>=6.5.5; extra == "dev"
37
+ Requires-Dist: pytest>=9.0.3; extra == "dev"
38
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.8; extra == "dev"
40
+ Requires-Dist: ruff>=0.4; extra == "dev"
41
+ Requires-Dist: mkdocs-material>=9.7.1; extra == "dev"
42
+ Requires-Dist: mkdocstrings-python>=2.0.1; extra == "dev"
43
+ Requires-Dist: mkdocs>=1.6.1; extra == "dev"
44
+ Requires-Dist: requests>=2.33.0; extra == "dev"
45
+ Requires-Dist: mkdocs-mermaid2-plugin; extra == "dev"
46
+
47
+ # groundworkers
48
+
49
+ `groundworkers` is the reusable capability layer for OMOP-grounded lookup,
50
+ mapping, source planning, and knowledge-pack discovery.
51
+
52
+ You can use it in three ways:
53
+
54
+ - as an **MCP service** for agentic clients and tool discovery
55
+ - as a **REST service** for fixed workflow applications
56
+ - as a **direct Python library** for in-process orchestration
57
+
58
+ No patient-level writes. No session state. No transport-specific business logic.
59
+
60
+ ## What it provides
61
+
62
+ - OMOP concept lookup and hierarchy navigation
63
+ - exact, normalized, full-text, and embedding-backed retrieval
64
+ - mapping-oriented candidate bundles and context assembly
65
+ - stateless source-planning workflows
66
+ - bundled baseline knowledge packs for reusable mapping and planning context
67
+ - LLM-backed text normalization and domain classification
68
+
69
+ ## Knowledge Packs
70
+
71
+ `groundworkers` ships with bundled baseline knowledge packs inside the package.
72
+ These packs are available by default and provide reusable guidance and rules
73
+ that apply broadly across deployments.
74
+
75
+ Site-specific or localisation packs are added through the shared stack config
76
+ via `tools.groundworkers.knowledge.packs_root`. When a configured pack has the
77
+ same `layer` and `name` as a bundled baseline pack, the configured copy wins.
78
+
79
+ ## Runtime model
80
+
81
+ ```mermaid
82
+ flowchart TD
83
+ STACK[shared stack config] --> BOOT[build_app_config]
84
+ BOOT --> CFG[AppConfig]
85
+ CFG --> APP[build_application]
86
+ APP --> GW[GroundworkersApp]
87
+ GW --> SVC[services]
88
+ GW --> ADP[adapters]
89
+ MCP[MCP client] --> TOOLS[MCP tools]
90
+ REST[REST client] --> API[REST transport]
91
+ PY[Python caller] --> SVC
92
+ TOOLS --> SVC
93
+ TOOLS -. adapter-backed primitives .-> ADP
94
+ API --> SVC
95
+ ```
96
+
97
+ `build_application(...)` is the composition root. It builds one reusable
98
+ runtime container with transport-agnostic services plus dependency-facing
99
+ adapters. Most caller-facing workflows go through services; some MCP tools are
100
+ intentionally adapter-backed when the capability is closer to a backend
101
+ primitive than a domain service.
102
+
103
+ ## Quick start
104
+
105
+ ### Install
106
+
107
+ ```bash
108
+ pip install groundworkers
109
+ ```
110
+
111
+ Optional extras:
112
+
113
+ ```bash
114
+ pip install "groundworkers[llm,embedding-pgvector]"
115
+ ```
116
+
117
+ ### Configure the shared stack
118
+
119
+ ```bash
120
+ omop-config configure omop_alchemy
121
+ omop-config configure omop_graph
122
+ omop-config configure groundworkers
123
+ # optional if you want embedding-backed capabilities
124
+ omop-config configure omop_emb
125
+ ```
126
+
127
+ ### Start MCP
128
+
129
+ ```bash
130
+ groundworkers --describe
131
+ groundworkers --transport streamable-http --host 0.0.0.0 --port 8000
132
+ ```
133
+
134
+ ### Start REST
135
+
136
+ ```bash
137
+ groundworkers --transport rest --host 0.0.0.0 --port 8080
138
+ ```
139
+
140
+ ### Use from Python
141
+
142
+ ```python
143
+ from groundworkers.app import build_application
144
+ from groundworkers.bootstrap import build_app_config
145
+
146
+ config = build_app_config()
147
+ app = build_application(config)
148
+
149
+ mapping = app.services.mapping
150
+ bundle = mapping.concept_candidate_bundle(
151
+ "type 2 diabetes",
152
+ domain="Condition",
153
+ include_normalized=True,
154
+ include_fulltext=True,
155
+ include_embedding=True,
156
+ )
157
+ ```
158
+
159
+ ## Main surfaces
160
+
161
+ | Surface | Best for |
162
+ |---|---|
163
+ | MCP tools | Tool discovery, agent interoperability, shared capability services |
164
+ | REST routes | Typed HTTP workflows such as candidate bundles and assisted source planning |
165
+ | `app.services.*` | In-process Python applications and batch workflows |
166
+ | `app.adapters.*` | Backend wrappers used when you intentionally need dependency-shaped primitives |
167
+
168
+ ## Learn more
169
+
170
+ - Docs home: `docs/index.md`
171
+ - Configuration: `docs/usage/configuration.md`
172
+ - Integrations: `docs/usage/integrations.md`
173
+ - Architecture: `docs/architecture.md`
174
+
175
+ ## Companion repos
176
+
177
+ - [groundcrew](https://github.com/AustralianCancerDataNetwork/groundcrew)
178
+ - [omop-graph](https://australiancancerdatanetwork.github.io/omop-graph/)
179
+ - [omop-emb](https://australiancancerdatanetwork.github.io/omop-emb/)
@@ -0,0 +1,133 @@
1
+ # groundworkers
2
+
3
+ `groundworkers` is the reusable capability layer for OMOP-grounded lookup,
4
+ mapping, source planning, and knowledge-pack discovery.
5
+
6
+ You can use it in three ways:
7
+
8
+ - as an **MCP service** for agentic clients and tool discovery
9
+ - as a **REST service** for fixed workflow applications
10
+ - as a **direct Python library** for in-process orchestration
11
+
12
+ No patient-level writes. No session state. No transport-specific business logic.
13
+
14
+ ## What it provides
15
+
16
+ - OMOP concept lookup and hierarchy navigation
17
+ - exact, normalized, full-text, and embedding-backed retrieval
18
+ - mapping-oriented candidate bundles and context assembly
19
+ - stateless source-planning workflows
20
+ - bundled baseline knowledge packs for reusable mapping and planning context
21
+ - LLM-backed text normalization and domain classification
22
+
23
+ ## Knowledge Packs
24
+
25
+ `groundworkers` ships with bundled baseline knowledge packs inside the package.
26
+ These packs are available by default and provide reusable guidance and rules
27
+ that apply broadly across deployments.
28
+
29
+ Site-specific or localisation packs are added through the shared stack config
30
+ via `tools.groundworkers.knowledge.packs_root`. When a configured pack has the
31
+ same `layer` and `name` as a bundled baseline pack, the configured copy wins.
32
+
33
+ ## Runtime model
34
+
35
+ ```mermaid
36
+ flowchart TD
37
+ STACK[shared stack config] --> BOOT[build_app_config]
38
+ BOOT --> CFG[AppConfig]
39
+ CFG --> APP[build_application]
40
+ APP --> GW[GroundworkersApp]
41
+ GW --> SVC[services]
42
+ GW --> ADP[adapters]
43
+ MCP[MCP client] --> TOOLS[MCP tools]
44
+ REST[REST client] --> API[REST transport]
45
+ PY[Python caller] --> SVC
46
+ TOOLS --> SVC
47
+ TOOLS -. adapter-backed primitives .-> ADP
48
+ API --> SVC
49
+ ```
50
+
51
+ `build_application(...)` is the composition root. It builds one reusable
52
+ runtime container with transport-agnostic services plus dependency-facing
53
+ adapters. Most caller-facing workflows go through services; some MCP tools are
54
+ intentionally adapter-backed when the capability is closer to a backend
55
+ primitive than a domain service.
56
+
57
+ ## Quick start
58
+
59
+ ### Install
60
+
61
+ ```bash
62
+ pip install groundworkers
63
+ ```
64
+
65
+ Optional extras:
66
+
67
+ ```bash
68
+ pip install "groundworkers[llm,embedding-pgvector]"
69
+ ```
70
+
71
+ ### Configure the shared stack
72
+
73
+ ```bash
74
+ omop-config configure omop_alchemy
75
+ omop-config configure omop_graph
76
+ omop-config configure groundworkers
77
+ # optional if you want embedding-backed capabilities
78
+ omop-config configure omop_emb
79
+ ```
80
+
81
+ ### Start MCP
82
+
83
+ ```bash
84
+ groundworkers --describe
85
+ groundworkers --transport streamable-http --host 0.0.0.0 --port 8000
86
+ ```
87
+
88
+ ### Start REST
89
+
90
+ ```bash
91
+ groundworkers --transport rest --host 0.0.0.0 --port 8080
92
+ ```
93
+
94
+ ### Use from Python
95
+
96
+ ```python
97
+ from groundworkers.app import build_application
98
+ from groundworkers.bootstrap import build_app_config
99
+
100
+ config = build_app_config()
101
+ app = build_application(config)
102
+
103
+ mapping = app.services.mapping
104
+ bundle = mapping.concept_candidate_bundle(
105
+ "type 2 diabetes",
106
+ domain="Condition",
107
+ include_normalized=True,
108
+ include_fulltext=True,
109
+ include_embedding=True,
110
+ )
111
+ ```
112
+
113
+ ## Main surfaces
114
+
115
+ | Surface | Best for |
116
+ |---|---|
117
+ | MCP tools | Tool discovery, agent interoperability, shared capability services |
118
+ | REST routes | Typed HTTP workflows such as candidate bundles and assisted source planning |
119
+ | `app.services.*` | In-process Python applications and batch workflows |
120
+ | `app.adapters.*` | Backend wrappers used when you intentionally need dependency-shaped primitives |
121
+
122
+ ## Learn more
123
+
124
+ - Docs home: `docs/index.md`
125
+ - Configuration: `docs/usage/configuration.md`
126
+ - Integrations: `docs/usage/integrations.md`
127
+ - Architecture: `docs/architecture.md`
128
+
129
+ ## Companion repos
130
+
131
+ - [groundcrew](https://github.com/AustralianCancerDataNetwork/groundcrew)
132
+ - [omop-graph](https://australiancancerdatanetwork.github.io/omop-graph/)
133
+ - [omop-emb](https://australiancancerdatanetwork.github.io/omop-emb/)
@@ -1,27 +1,48 @@
1
1
  [project]
2
2
  name = "groundworkers"
3
- version = "0.2.0"
3
+ version = "0.3.3"
4
4
  description = "Groundworkers MCP server — read-only agentive access to OMOP vocabularies, concept graphs, and embeddings."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  dependencies = [
8
+ "fastapi>=0.110,<1.0",
8
9
  "mcp[cli]>=1,<2",
9
10
  "pydantic>=2,<3",
10
11
  "pyyaml>=6,<7",
11
12
  "SQLAlchemy>=2,<3",
12
13
  "psycopg[binary]>=3.1,<4",
13
- "omop-graph>=1.1.0",
14
- "omop-emb>=1.0.0",
14
+ "oa-configurator>=0.1.2",
15
+ "omop-graph>=1.3.0",
16
+ "omop-emb>=1.1.1",
17
+ "uvicorn[standard]>=0.29,<1.0",
15
18
  ]
16
19
 
17
20
  [project.optional-dependencies]
21
+ llm = [
22
+ "openai>=1.0",
23
+ ]
24
+ xlsx = [
25
+ "openpyxl>=3.1,<4",
26
+ ]
27
+ pdf = [
28
+ "pdfplumber>=0.10",
29
+ ]
30
+ docx = [
31
+ "python-docx>=1.0",
32
+ ]
33
+ all_source = [
34
+ "openpyxl>=3.1,<4",
35
+ "pdfplumber>=0.10",
36
+ "python-docx>=1.0",
37
+ ]
18
38
  embedding-pgvector = [
19
- "omop-emb[pgvector]>=1.0.0",
39
+ "omop-emb[pgvector]>=1.1.1",
20
40
  ]
21
41
  embedding-faiss = [
22
- "omop-emb[faiss-cpu]>=1.0.0",
42
+ "omop-emb[faiss-cpu]>=1.1.1",
23
43
  ]
24
44
  dev = [
45
+ "httpx>=0.27,<1",
25
46
  "ipython>=8.0",
26
47
  "tornado>=6.5.5",
27
48
  "pytest>=9.0.3",
@@ -38,6 +59,9 @@ dev = [
38
59
  [project.scripts]
39
60
  groundworkers = "groundworkers.server:main"
40
61
 
62
+ [project.entry-points."omop.config"]
63
+ groundworkers = "groundworkers.config:GroundworkersConfig"
64
+
41
65
  [build-system]
42
66
  requires = ["setuptools>=68", "wheel"]
43
67
  build-backend = "setuptools.build_meta"
@@ -48,6 +72,12 @@ package-dir = {"" = "src"}
48
72
  [tool.setuptools.packages.find]
49
73
  where = ["src"]
50
74
 
75
+ [tool.setuptools.package-data]
76
+ groundworkers = [
77
+ "knowledge/packs/*/*/*.md",
78
+ "knowledge/packs/*/*/*.yaml",
79
+ ]
80
+
51
81
  [tool.pytest.ini_options]
52
82
  testpaths = ["tests"]
53
83
  markers = [
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from sqlalchemy import text
4
+ from sqlalchemy.engine import Engine
5
+ from sqlalchemy.orm import sessionmaker
6
+
7
+
8
+ class CDMAdapter:
9
+ """Adapter for a CDM (Common Data Model) database connection.
10
+
11
+ Holds the SQLAlchemy engine and session factory for an OMOP CDM database.
12
+ Shared by services that need to query the CDM directly (VocabService,
13
+ OmopGraphAdapter).
14
+
15
+ Pass engine to adapters that wrap their own session management.
16
+ Use session() for services that need a scoped session context manager.
17
+ """
18
+
19
+ def __init__(self, engine: Engine) -> None:
20
+ self._engine = engine
21
+ self._session_factory = sessionmaker(engine)
22
+
23
+ @property
24
+ def engine(self) -> Engine:
25
+ """The underlying SQLAlchemy engine."""
26
+ return self._engine
27
+
28
+ def session(self):
29
+ """Return a session context manager."""
30
+ return self._session_factory()
31
+
32
+ def is_available(self) -> bool:
33
+ """Return True if the CDM database is reachable (SELECT 1 probe)."""
34
+ try:
35
+ with self._engine.connect() as conn:
36
+ conn.execute(text("SELECT 1"))
37
+ return True
38
+ except Exception:
39
+ return False
40
+
41
+ def close(self) -> None:
42
+ """Dispose the engine and release the connection pool."""
43
+ self._engine.dispose()
@@ -0,0 +1,177 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+ from groundworkers.base.errors import GroundworkersError
8
+
9
+ _STATUS_TIMEOUT_SECONDS = 2.0
10
+ _COMPLETION_TIMEOUT_SECONDS = 180.0
11
+
12
+
13
+ class LLMAdapter:
14
+ """Adapter for OpenAI-compatible LLM chat completion APIs.
15
+
16
+ Works with any provider that implements the OpenAI chat completions API:
17
+ local deployments (Ollama, vLLM, LM Studio) and remote services (OpenAI,
18
+ Azure OpenAI, and compatible cloud APIs). Configure ``api_base`` to point
19
+ at the correct endpoint.
20
+
21
+ Two completion modes are available:
22
+
23
+ - **Text completion** (``complete_text``): returns a raw text response.
24
+ - **Structured completion** (``complete_structured``): requests a JSON
25
+ response matching a caller-supplied schema. Preferred for MCP-facing
26
+ tools where downstream agents need to parse the output reliably.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ *,
32
+ provider: str,
33
+ default_model_name: str | None = None,
34
+ client_factory: Callable[[], Any],
35
+ ) -> None:
36
+ self._provider = provider
37
+ self._default_model_name = default_model_name
38
+ self._client_factory = client_factory
39
+ self._client: Any = None
40
+
41
+ def is_available(self) -> bool:
42
+ """Return True if the LLM API is reachable."""
43
+ return self.status()["available"]
44
+
45
+ def close(self) -> None:
46
+ """Release the cached client."""
47
+ self._client = None
48
+
49
+ def status(self) -> dict[str, Any]:
50
+ """Return availability and configuration details. Never raises.
51
+
52
+ Probes the API with a short timeout. On failure returns
53
+ ``{"available": False, ..., "detail": "<reason>"}``.
54
+ """
55
+ try:
56
+ client = self._get_client()
57
+ client.models.list(timeout=_STATUS_TIMEOUT_SECONDS)
58
+ return {
59
+ "available": True,
60
+ "provider": self._provider,
61
+ "default_model": self._default_model_name,
62
+ "structured_output_supported": True,
63
+ "detail": None,
64
+ }
65
+ except Exception as exc:
66
+ return {
67
+ "available": False,
68
+ "provider": self._provider,
69
+ "default_model": self._default_model_name,
70
+ "structured_output_supported": None,
71
+ "detail": repr(exc),
72
+ }
73
+
74
+ def complete_text(
75
+ self,
76
+ prompt: str,
77
+ *,
78
+ system_prompt: str | None = None,
79
+ model_name: str | None = None,
80
+ temperature: float = 0.0,
81
+ ) -> dict[str, Any]:
82
+ """Complete a prompt and return the response text.
83
+
84
+ Raises ``INVALID_INPUT`` if no model is resolvable.
85
+ Raises ``BACKEND_UNAVAIL`` if the API call fails.
86
+ """
87
+ client = self._get_client()
88
+ resolved_model = self._resolve_model(model_name)
89
+ messages = _build_messages(prompt, system_prompt)
90
+ try:
91
+ response = client.chat.completions.create(
92
+ model=resolved_model,
93
+ messages=messages,
94
+ temperature=temperature,
95
+ timeout=_COMPLETION_TIMEOUT_SECONDS,
96
+ )
97
+ except Exception as exc:
98
+ raise GroundworkersError("BACKEND_UNAVAIL", f"LLM call failed: {exc}") from exc
99
+ return {
100
+ "text": response.choices[0].message.content,
101
+ "model": response.model,
102
+ "provider": self._provider,
103
+ }
104
+
105
+ def complete_structured(
106
+ self,
107
+ prompt: str,
108
+ response_schema: dict[str, Any],
109
+ *,
110
+ system_prompt: str | None = None,
111
+ model_name: str | None = None,
112
+ temperature: float = 0.0,
113
+ ) -> dict[str, Any]:
114
+ """Complete a prompt and return a parsed JSON dict guided by response_schema.
115
+
116
+ The schema is injected into the system prompt and JSON mode is requested
117
+ from the API. This is compatible with Ollama, vLLM, and OpenAI endpoints.
118
+
119
+ The response is parsed but not validated against the schema — callers are
120
+ responsible for validating the returned dict (e.g. with Pydantic).
121
+
122
+ Raises ``INVALID_INPUT`` if no model is resolvable or if response_schema
123
+ is not JSON-serializable.
124
+ Raises ``BACKEND_UNAVAIL`` if the API call fails.
125
+ Raises ``QUERY_ERROR`` if the response is not valid JSON.
126
+ """
127
+ client = self._get_client()
128
+ resolved_model = self._resolve_model(model_name)
129
+ try:
130
+ schema_json = json.dumps(response_schema, indent=2)
131
+ except (TypeError, ValueError) as exc:
132
+ raise GroundworkersError(
133
+ "INVALID_INPUT", f"response_schema is not JSON-serializable: {exc}"
134
+ ) from exc
135
+ schema_directive = f"Respond with a JSON object matching this schema:\n{schema_json}"
136
+ augmented_system = f"{system_prompt}\n\n{schema_directive}" if system_prompt else schema_directive
137
+ messages = _build_messages(prompt, augmented_system)
138
+ try:
139
+ response = client.chat.completions.create(
140
+ model=resolved_model,
141
+ messages=messages,
142
+ temperature=temperature,
143
+ response_format={"type": "json_object"},
144
+ timeout=_COMPLETION_TIMEOUT_SECONDS,
145
+ )
146
+ except Exception as exc:
147
+ raise GroundworkersError("BACKEND_UNAVAIL", f"LLM call failed: {exc}") from exc
148
+ content = response.choices[0].message.content or ""
149
+ try:
150
+ return json.loads(content)
151
+ except json.JSONDecodeError as exc:
152
+ raise GroundworkersError("QUERY_ERROR", f"LLM response was not valid JSON: {exc}") from exc
153
+
154
+ def _get_client(self) -> Any:
155
+ if self._client is None:
156
+ try:
157
+ self._client = self._client_factory()
158
+ except Exception as exc:
159
+ raise GroundworkersError("BACKEND_UNAVAIL", f"LLM client could not be initialised: {exc}") from exc
160
+ return self._client
161
+
162
+ def _resolve_model(self, model_name: str | None) -> str:
163
+ resolved = model_name or self._default_model_name
164
+ if resolved is None:
165
+ raise GroundworkersError(
166
+ "INVALID_INPUT",
167
+ "No model specified and no default model is configured",
168
+ )
169
+ return resolved
170
+
171
+
172
+ def _build_messages(prompt: str, system_prompt: str | None) -> list[dict[str, str]]:
173
+ messages: list[dict[str, str]] = []
174
+ if system_prompt:
175
+ messages.append({"role": "system", "content": system_prompt})
176
+ messages.append({"role": "user", "content": prompt})
177
+ return messages