urml-llm-bridge 0.1.0__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.
- urml_llm_bridge-0.1.0/.gitignore +104 -0
- urml_llm_bridge-0.1.0/PKG-INFO +223 -0
- urml_llm_bridge-0.1.0/README.md +182 -0
- urml_llm_bridge-0.1.0/pyproject.toml +83 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/__init__.py +56 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/_version.py +5 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/bridge.py +242 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/errors.py +48 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/few_shot.py +400 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/grammar.py +290 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/prompt.py +253 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/__init__.py +10 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/anthropic.py +124 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/base.py +56 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/echo.py +82 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/llama_cpp.py +172 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/ollama.py +157 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/providers/openai.py +104 -0
- urml_llm_bridge-0.1.0/src/urml_llm_bridge/py.typed +0 -0
- urml_llm_bridge-0.1.0/tests/__init__.py +0 -0
- urml_llm_bridge-0.1.0/tests/test_bridge.py +511 -0
- urml_llm_bridge-0.1.0/tests/test_emit_prompt_cli.py +184 -0
- urml_llm_bridge-0.1.0/tests/test_few_shot_library.py +278 -0
- urml_llm_bridge-0.1.0/tests/test_grammar.py +194 -0
- urml_llm_bridge-0.1.0/tests/test_providers_anthropic.py +114 -0
- urml_llm_bridge-0.1.0/tests/test_providers_llama_cpp.py +135 -0
- urml_llm_bridge-0.1.0/tests/test_providers_ollama.py +114 -0
- urml_llm_bridge-0.1.0/tests/test_providers_openai.py +77 -0
- urml_llm_bridge-0.1.0/tests/test_translate_cli.py +289 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# --- Python ---
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Virtual envs
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
env/
|
|
28
|
+
ENV/
|
|
29
|
+
.python-version
|
|
30
|
+
|
|
31
|
+
# Test / coverage / mypy / pyright
|
|
32
|
+
.coverage
|
|
33
|
+
.coverage.*
|
|
34
|
+
.cache
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
.dmypy.json
|
|
38
|
+
.pyre/
|
|
39
|
+
.pyright/
|
|
40
|
+
htmlcov/
|
|
41
|
+
nosetests.xml
|
|
42
|
+
coverage.xml
|
|
43
|
+
*.cover
|
|
44
|
+
.hypothesis/
|
|
45
|
+
.tox/
|
|
46
|
+
|
|
47
|
+
# --- Node / web tooling (kept out of core repo; here for safety) ---
|
|
48
|
+
node_modules/
|
|
49
|
+
.pnp.*
|
|
50
|
+
.yarn/cache/
|
|
51
|
+
.yarn/unplugged/
|
|
52
|
+
.yarn/build-state.yml
|
|
53
|
+
.yarn/install-state.gz
|
|
54
|
+
npm-debug.log*
|
|
55
|
+
yarn-debug.log*
|
|
56
|
+
yarn-error.log*
|
|
57
|
+
|
|
58
|
+
# --- C++ / Rust build artifacts (in case reference runtimes land here in early Phase 1) ---
|
|
59
|
+
target/
|
|
60
|
+
*.o
|
|
61
|
+
*.obj
|
|
62
|
+
*.a
|
|
63
|
+
*.lib
|
|
64
|
+
*.dll
|
|
65
|
+
*.dylib
|
|
66
|
+
*.exe
|
|
67
|
+
*.pdb
|
|
68
|
+
cmake-build-*/
|
|
69
|
+
CMakeCache.txt
|
|
70
|
+
CMakeFiles/
|
|
71
|
+
CMakeScripts/
|
|
72
|
+
Testing/
|
|
73
|
+
cmake_install.cmake
|
|
74
|
+
install_manifest.txt
|
|
75
|
+
compile_commands.json
|
|
76
|
+
|
|
77
|
+
# --- ROS 2 build dirs ---
|
|
78
|
+
build/
|
|
79
|
+
install/
|
|
80
|
+
log/
|
|
81
|
+
|
|
82
|
+
# --- Editor and OS junk ---
|
|
83
|
+
.vscode/
|
|
84
|
+
.idea/
|
|
85
|
+
*.swp
|
|
86
|
+
*.swo
|
|
87
|
+
*~
|
|
88
|
+
.DS_Store
|
|
89
|
+
Thumbs.db
|
|
90
|
+
desktop.ini
|
|
91
|
+
|
|
92
|
+
# --- Local-only artifacts ---
|
|
93
|
+
.env
|
|
94
|
+
.env.*
|
|
95
|
+
*.log
|
|
96
|
+
*.tmp
|
|
97
|
+
.local/
|
|
98
|
+
.scratch/
|
|
99
|
+
|
|
100
|
+
# --- Outreach campaign artifacts (operational, not part of the open standard) ---
|
|
101
|
+
# Target lists, message drafts, tracking data, and the launch-approval record
|
|
102
|
+
# stay local. They are private to the maintainer; committing them would
|
|
103
|
+
# telegraph the campaign before launch and could embarrass listed projects.
|
|
104
|
+
outreach/
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: urml-llm-bridge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provider-agnostic glue between natural language and validated URML programs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/URML-MARS/URML
|
|
6
|
+
Project-URL: Repository, https://github.com/URML-MARS/URML
|
|
7
|
+
Project-URL: Issues, https://github.com/URML-MARS/URML/issues
|
|
8
|
+
Author: URML Maintainers
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Keywords: llm,robotics,specification,urml
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: pydantic<3,>=2.6
|
|
21
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
22
|
+
Requires-Dist: urml-validator>=0.1.0
|
|
23
|
+
Provides-Extra: anthropic
|
|
24
|
+
Requires-Dist: anthropic>=0.34; extra == 'anthropic'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: anthropic>=0.34; extra == 'dev'
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
29
|
+
Requires-Dist: openai>=1.40; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
33
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
34
|
+
Provides-Extra: llama-cpp
|
|
35
|
+
Requires-Dist: httpx>=0.27; extra == 'llama-cpp'
|
|
36
|
+
Provides-Extra: ollama
|
|
37
|
+
Requires-Dist: httpx>=0.27; extra == 'ollama'
|
|
38
|
+
Provides-Extra: openai
|
|
39
|
+
Requires-Dist: openai>=1.40; extra == 'openai'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://urml.dev"><img src="https://urml.dev/favicon.svg" alt="URML" width="72" height="72"></a>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
A small, opinionated, human-readable language for describing robot intent.
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<a href="https://urml.dev"><b>urml.dev</b></a>
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
# LLM Bridge
|
|
57
|
+
|
|
58
|
+
**Status:** `0.1.0`, aligned with the other four packages. Shipped: the provider-agnostic `Bridge` + bounded validator-feedback revision loop, real Anthropic / OpenAI adapters (lazy-imported) + the hermetic `EchoProvider`, the `urml translate` / `urml emit-prompt` CLI, and profile-scoped few-shot libraries (home / industrial / drone). The normative contract is [`spec/layer-4-nl-grammar/v0.1.0.md`](../../spec/layer-4-nl-grammar/); a hermetic end-to-end walkthrough is [`docs/demos/bridge-roundtrip.md`](../../docs/demos/bridge-roundtrip.md).
|
|
59
|
+
|
|
60
|
+
## What this is
|
|
61
|
+
|
|
62
|
+
The **provider-agnostic glue** between natural-language input and a validated URML program. The LLM bridge:
|
|
63
|
+
|
|
64
|
+
1. Takes a natural-language request from a user (or another system).
|
|
65
|
+
2. Prompts a configured LLM with the [Layer-4 prompt contract](../../spec/layer-4-nl-grammar/), the connected robot's [Layer-1 capability manifest](../../spec/layer-1-hal/), and the active safety envelope.
|
|
66
|
+
3. Receives the LLM's emission (a URML program).
|
|
67
|
+
4. Calls the [validator](../validator/) to statically verify the emission.
|
|
68
|
+
5. On rejection, surfaces the structured error back to the LLM and requests a revision. Repeats up to a configured bound.
|
|
69
|
+
6. On acceptance, hands the validated program to the runtime for execution.
|
|
70
|
+
7. When the request needs a capability the manifest does not declare, the model emits a `report(status: failure)` naming what is missing rather than fabricating capability. (An *interactive* clarifying-question protocol is **not** in v0.1 — see [`spec/layer-4-nl-grammar/v0.1.0.md`](../../spec/layer-4-nl-grammar/) §5.)
|
|
71
|
+
|
|
72
|
+
## Provider neutrality is non-negotiable
|
|
73
|
+
|
|
74
|
+
URML's value as a standard depends on Layer 4 being **provider-neutral**. The LLM bridge must support, as first-class citizens:
|
|
75
|
+
|
|
76
|
+
- **Anthropic** (Claude family).
|
|
77
|
+
- **OpenAI** (GPT family).
|
|
78
|
+
- **Open-weights models** (Llama, Mistral, Qwen, and their successors), via local serving (vLLM, llama.cpp, Ollama) or hosted inference providers.
|
|
79
|
+
- **On-device models** for offline-capable deployments.
|
|
80
|
+
|
|
81
|
+
Adding a new provider must be a small adapter in `providers/`, not a structural change to the bridge. If a provider's particular feature would let URML produce better URML, the bridge surfaces the feature behind a profile-neutral abstraction — never by privileging that provider.
|
|
82
|
+
|
|
83
|
+
Vendor lock-in here is explicitly prohibited by [`CLAUDE.md`](../../CLAUDE.md) §What Claude Should Never Do.
|
|
84
|
+
|
|
85
|
+
## What the bridge does NOT do
|
|
86
|
+
|
|
87
|
+
- It does **not** include or embed any specific LLM provider's API client as a hard dependency. Provider clients are optional and pluggable.
|
|
88
|
+
- It does **not** require cloud connectivity. A deployment using a local open-weights model runs offline end to end.
|
|
89
|
+
- It does **not** persist user inputs, model outputs, or any other data without explicit, opt-in, documented purpose. Trust is the most valuable asset of this project; the bridge will not be the place it leaks.
|
|
90
|
+
- It does **not** execute URML. That is the runtime's job.
|
|
91
|
+
- It does **not** make safety decisions. The validator is the safety boundary; the bridge only relays.
|
|
92
|
+
|
|
93
|
+
## Architecture (planned)
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
┌────────────────────────┐
|
|
97
|
+
user / NL │ LLM Bridge │ validated URML
|
|
98
|
+
─────────────▶ • build prompt ├──────────────────▶ runtime
|
|
99
|
+
│ • call provider │
|
|
100
|
+
│ • validate emission │
|
|
101
|
+
│ • revise loop │
|
|
102
|
+
└──┬──────────────────▲──┘
|
|
103
|
+
│ │
|
|
104
|
+
▼ │
|
|
105
|
+
providers/ validator
|
|
106
|
+
(anthropic.py, (separate
|
|
107
|
+
openai.py, process)
|
|
108
|
+
local_vllm.py,
|
|
109
|
+
on_device.py)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The bridge is small. The intelligence lives in the LLM (which is configured, not built here) and in the validator (which is a separate process). The bridge orchestrates.
|
|
113
|
+
|
|
114
|
+
## Language
|
|
115
|
+
|
|
116
|
+
- **Python**. `mypy --strict`. Public API fully type-annotated.
|
|
117
|
+
|
|
118
|
+
## API (sketch)
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from urml.llm_bridge import Bridge
|
|
122
|
+
|
|
123
|
+
bridge = Bridge(
|
|
124
|
+
provider="anthropic", # or "openai", "vllm", "ollama", ...
|
|
125
|
+
spec_versions={...},
|
|
126
|
+
manifest=manifest,
|
|
127
|
+
envelope=envelope,
|
|
128
|
+
profiles=("home",),
|
|
129
|
+
max_revisions=3,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
result = bridge.translate("Bring me the red mug from the kitchen.")
|
|
133
|
+
|
|
134
|
+
if result.accepted:
|
|
135
|
+
runtime.execute(result.program)
|
|
136
|
+
else:
|
|
137
|
+
# After max_revisions, structured errors surface to the caller.
|
|
138
|
+
show_user(result.user_message, result.errors)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Conformance contract
|
|
142
|
+
|
|
143
|
+
The bridge has its own conformance bar: for the published few-shot example library, the bridge produces accepted URML at or above a stated success rate (per-provider, declared in the bridge's release notes). The conformance suite includes these end-to-end fixtures.
|
|
144
|
+
|
|
145
|
+
## Core Commitment
|
|
146
|
+
|
|
147
|
+
The LLM bridge — the *bridge logic and the prompt contract*, not any specific provider's API — is part of the [Core Commitment](../../CORE_COMMITMENT.md). It will always be Apache 2.0 and provider-agnostic.
|
|
148
|
+
|
|
149
|
+
## Quickstart (current pre-alpha — hermetic, no provider needed)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd reference/llm-bridge
|
|
153
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
154
|
+
pip install -e ../validator # bridge depends on validator
|
|
155
|
+
pip install -e ".[dev]"
|
|
156
|
+
pytest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Use it in code with the bundled `EchoProvider` (for tests / hermetic CI):
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import json
|
|
163
|
+
from urml_llm_bridge import Bridge, EchoProvider
|
|
164
|
+
|
|
165
|
+
red_mug_program = { # the URML the LLM is expected to emit
|
|
166
|
+
"profile": "home",
|
|
167
|
+
"behavior": {"type": "sequence", "steps": [...]},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
provider = EchoProvider(scripted=[json.dumps(red_mug_program)])
|
|
171
|
+
bridge = Bridge(provider=provider, manifest=manifest, envelope=envelope, profiles=("home",))
|
|
172
|
+
result = bridge.translate("Bring me the red mug from the kitchen.")
|
|
173
|
+
|
|
174
|
+
if result.accepted:
|
|
175
|
+
runtime.execute(result.program)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The revision loop runs automatically when the validator rejects the LLM's emission: the bridge feeds the structured errors back to the LLM and asks for a corrected version, up to `max_revisions` times (default 3).
|
|
179
|
+
|
|
180
|
+
### Using a real provider
|
|
181
|
+
|
|
182
|
+
Install the extra for the provider you want — the bridge package itself has no SDK dependencies:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
pip install urml-llm-bridge[anthropic] # adds the `anthropic` SDK
|
|
186
|
+
pip install urml-llm-bridge[openai] # adds the `openai` SDK
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from urml_llm_bridge import Bridge
|
|
193
|
+
from urml_llm_bridge.providers.anthropic import AnthropicProvider
|
|
194
|
+
|
|
195
|
+
provider = AnthropicProvider(model="claude-sonnet-4-6") # reads ANTHROPIC_API_KEY env var
|
|
196
|
+
bridge = Bridge(provider=provider, manifest=manifest, envelope=envelope, profiles=("home",))
|
|
197
|
+
result = bridge.translate("Bring me the red mug from the kitchen.")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Or OpenAI:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from urml_llm_bridge.providers.openai import OpenAIProvider
|
|
204
|
+
|
|
205
|
+
provider = OpenAIProvider(model="gpt-4o") # reads OPENAI_API_KEY env var
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Both adapters surface their native structured-output mechanism — Anthropic via tool use (with the URML schema as the `emit_urml` tool's `input_schema`), OpenAI via `response_format={"type": "json_object"}` with the schema conveyed in the system prompt. Either way, conformance to the schema is validated downstream by `urml_validator.validate()` as part of the bridge's revision loop.
|
|
209
|
+
|
|
210
|
+
## What's not in this pre-alpha (lands next)
|
|
211
|
+
|
|
212
|
+
- **CLI integration** — `urml translate` subcommand on top of `urml-validator`'s CLI.
|
|
213
|
+
- **Profile-specific few-shot libraries** (drone scenarios, industrial scenarios).
|
|
214
|
+
- **Multilingual few-shot variants** (Hebrew, Spanish, Japanese, Mandarin).
|
|
215
|
+
- **Conversation memory** for follow-up requests within a session.
|
|
216
|
+
- **OpenAI strict JSON-schema mode** — needs schema preprocessing to satisfy the strict-mode constraints (every property required, no `oneOf`, etc.). The current adapter uses `json_object` mode plus the schema in the system prompt for portability.
|
|
217
|
+
|
|
218
|
+
## Related documents
|
|
219
|
+
|
|
220
|
+
- [`/spec/layer-4-nl-grammar/`](../../spec/layer-4-nl-grammar/) — the prompt contract this bridge implements.
|
|
221
|
+
- [`/reference/validator/`](../validator/) — the safety boundary this bridge feeds.
|
|
222
|
+
- [`/examples/`](../../examples/) — the paired natural-language / URML scenarios used as fixtures.
|
|
223
|
+
- [`CLAUDE.md`](../../CLAUDE.md) §What Claude Should Never Do — the provider-neutrality requirement, in writing.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://urml.dev"><img src="https://urml.dev/favicon.svg" alt="URML" width="72" height="72"></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
A small, opinionated, human-readable language for describing robot intent.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://urml.dev"><b>urml.dev</b></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# LLM Bridge
|
|
16
|
+
|
|
17
|
+
**Status:** `0.1.0`, aligned with the other four packages. Shipped: the provider-agnostic `Bridge` + bounded validator-feedback revision loop, real Anthropic / OpenAI adapters (lazy-imported) + the hermetic `EchoProvider`, the `urml translate` / `urml emit-prompt` CLI, and profile-scoped few-shot libraries (home / industrial / drone). The normative contract is [`spec/layer-4-nl-grammar/v0.1.0.md`](../../spec/layer-4-nl-grammar/); a hermetic end-to-end walkthrough is [`docs/demos/bridge-roundtrip.md`](../../docs/demos/bridge-roundtrip.md).
|
|
18
|
+
|
|
19
|
+
## What this is
|
|
20
|
+
|
|
21
|
+
The **provider-agnostic glue** between natural-language input and a validated URML program. The LLM bridge:
|
|
22
|
+
|
|
23
|
+
1. Takes a natural-language request from a user (or another system).
|
|
24
|
+
2. Prompts a configured LLM with the [Layer-4 prompt contract](../../spec/layer-4-nl-grammar/), the connected robot's [Layer-1 capability manifest](../../spec/layer-1-hal/), and the active safety envelope.
|
|
25
|
+
3. Receives the LLM's emission (a URML program).
|
|
26
|
+
4. Calls the [validator](../validator/) to statically verify the emission.
|
|
27
|
+
5. On rejection, surfaces the structured error back to the LLM and requests a revision. Repeats up to a configured bound.
|
|
28
|
+
6. On acceptance, hands the validated program to the runtime for execution.
|
|
29
|
+
7. When the request needs a capability the manifest does not declare, the model emits a `report(status: failure)` naming what is missing rather than fabricating capability. (An *interactive* clarifying-question protocol is **not** in v0.1 — see [`spec/layer-4-nl-grammar/v0.1.0.md`](../../spec/layer-4-nl-grammar/) §5.)
|
|
30
|
+
|
|
31
|
+
## Provider neutrality is non-negotiable
|
|
32
|
+
|
|
33
|
+
URML's value as a standard depends on Layer 4 being **provider-neutral**. The LLM bridge must support, as first-class citizens:
|
|
34
|
+
|
|
35
|
+
- **Anthropic** (Claude family).
|
|
36
|
+
- **OpenAI** (GPT family).
|
|
37
|
+
- **Open-weights models** (Llama, Mistral, Qwen, and their successors), via local serving (vLLM, llama.cpp, Ollama) or hosted inference providers.
|
|
38
|
+
- **On-device models** for offline-capable deployments.
|
|
39
|
+
|
|
40
|
+
Adding a new provider must be a small adapter in `providers/`, not a structural change to the bridge. If a provider's particular feature would let URML produce better URML, the bridge surfaces the feature behind a profile-neutral abstraction — never by privileging that provider.
|
|
41
|
+
|
|
42
|
+
Vendor lock-in here is explicitly prohibited by [`CLAUDE.md`](../../CLAUDE.md) §What Claude Should Never Do.
|
|
43
|
+
|
|
44
|
+
## What the bridge does NOT do
|
|
45
|
+
|
|
46
|
+
- It does **not** include or embed any specific LLM provider's API client as a hard dependency. Provider clients are optional and pluggable.
|
|
47
|
+
- It does **not** require cloud connectivity. A deployment using a local open-weights model runs offline end to end.
|
|
48
|
+
- It does **not** persist user inputs, model outputs, or any other data without explicit, opt-in, documented purpose. Trust is the most valuable asset of this project; the bridge will not be the place it leaks.
|
|
49
|
+
- It does **not** execute URML. That is the runtime's job.
|
|
50
|
+
- It does **not** make safety decisions. The validator is the safety boundary; the bridge only relays.
|
|
51
|
+
|
|
52
|
+
## Architecture (planned)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
┌────────────────────────┐
|
|
56
|
+
user / NL │ LLM Bridge │ validated URML
|
|
57
|
+
─────────────▶ • build prompt ├──────────────────▶ runtime
|
|
58
|
+
│ • call provider │
|
|
59
|
+
│ • validate emission │
|
|
60
|
+
│ • revise loop │
|
|
61
|
+
└──┬──────────────────▲──┘
|
|
62
|
+
│ │
|
|
63
|
+
▼ │
|
|
64
|
+
providers/ validator
|
|
65
|
+
(anthropic.py, (separate
|
|
66
|
+
openai.py, process)
|
|
67
|
+
local_vllm.py,
|
|
68
|
+
on_device.py)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The bridge is small. The intelligence lives in the LLM (which is configured, not built here) and in the validator (which is a separate process). The bridge orchestrates.
|
|
72
|
+
|
|
73
|
+
## Language
|
|
74
|
+
|
|
75
|
+
- **Python**. `mypy --strict`. Public API fully type-annotated.
|
|
76
|
+
|
|
77
|
+
## API (sketch)
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from urml.llm_bridge import Bridge
|
|
81
|
+
|
|
82
|
+
bridge = Bridge(
|
|
83
|
+
provider="anthropic", # or "openai", "vllm", "ollama", ...
|
|
84
|
+
spec_versions={...},
|
|
85
|
+
manifest=manifest,
|
|
86
|
+
envelope=envelope,
|
|
87
|
+
profiles=("home",),
|
|
88
|
+
max_revisions=3,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
result = bridge.translate("Bring me the red mug from the kitchen.")
|
|
92
|
+
|
|
93
|
+
if result.accepted:
|
|
94
|
+
runtime.execute(result.program)
|
|
95
|
+
else:
|
|
96
|
+
# After max_revisions, structured errors surface to the caller.
|
|
97
|
+
show_user(result.user_message, result.errors)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Conformance contract
|
|
101
|
+
|
|
102
|
+
The bridge has its own conformance bar: for the published few-shot example library, the bridge produces accepted URML at or above a stated success rate (per-provider, declared in the bridge's release notes). The conformance suite includes these end-to-end fixtures.
|
|
103
|
+
|
|
104
|
+
## Core Commitment
|
|
105
|
+
|
|
106
|
+
The LLM bridge — the *bridge logic and the prompt contract*, not any specific provider's API — is part of the [Core Commitment](../../CORE_COMMITMENT.md). It will always be Apache 2.0 and provider-agnostic.
|
|
107
|
+
|
|
108
|
+
## Quickstart (current pre-alpha — hermetic, no provider needed)
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cd reference/llm-bridge
|
|
112
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
113
|
+
pip install -e ../validator # bridge depends on validator
|
|
114
|
+
pip install -e ".[dev]"
|
|
115
|
+
pytest
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use it in code with the bundled `EchoProvider` (for tests / hermetic CI):
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import json
|
|
122
|
+
from urml_llm_bridge import Bridge, EchoProvider
|
|
123
|
+
|
|
124
|
+
red_mug_program = { # the URML the LLM is expected to emit
|
|
125
|
+
"profile": "home",
|
|
126
|
+
"behavior": {"type": "sequence", "steps": [...]},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
provider = EchoProvider(scripted=[json.dumps(red_mug_program)])
|
|
130
|
+
bridge = Bridge(provider=provider, manifest=manifest, envelope=envelope, profiles=("home",))
|
|
131
|
+
result = bridge.translate("Bring me the red mug from the kitchen.")
|
|
132
|
+
|
|
133
|
+
if result.accepted:
|
|
134
|
+
runtime.execute(result.program)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The revision loop runs automatically when the validator rejects the LLM's emission: the bridge feeds the structured errors back to the LLM and asks for a corrected version, up to `max_revisions` times (default 3).
|
|
138
|
+
|
|
139
|
+
### Using a real provider
|
|
140
|
+
|
|
141
|
+
Install the extra for the provider you want — the bridge package itself has no SDK dependencies:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
pip install urml-llm-bridge[anthropic] # adds the `anthropic` SDK
|
|
145
|
+
pip install urml-llm-bridge[openai] # adds the `openai` SDK
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Then:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from urml_llm_bridge import Bridge
|
|
152
|
+
from urml_llm_bridge.providers.anthropic import AnthropicProvider
|
|
153
|
+
|
|
154
|
+
provider = AnthropicProvider(model="claude-sonnet-4-6") # reads ANTHROPIC_API_KEY env var
|
|
155
|
+
bridge = Bridge(provider=provider, manifest=manifest, envelope=envelope, profiles=("home",))
|
|
156
|
+
result = bridge.translate("Bring me the red mug from the kitchen.")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Or OpenAI:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from urml_llm_bridge.providers.openai import OpenAIProvider
|
|
163
|
+
|
|
164
|
+
provider = OpenAIProvider(model="gpt-4o") # reads OPENAI_API_KEY env var
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Both adapters surface their native structured-output mechanism — Anthropic via tool use (with the URML schema as the `emit_urml` tool's `input_schema`), OpenAI via `response_format={"type": "json_object"}` with the schema conveyed in the system prompt. Either way, conformance to the schema is validated downstream by `urml_validator.validate()` as part of the bridge's revision loop.
|
|
168
|
+
|
|
169
|
+
## What's not in this pre-alpha (lands next)
|
|
170
|
+
|
|
171
|
+
- **CLI integration** — `urml translate` subcommand on top of `urml-validator`'s CLI.
|
|
172
|
+
- **Profile-specific few-shot libraries** (drone scenarios, industrial scenarios).
|
|
173
|
+
- **Multilingual few-shot variants** (Hebrew, Spanish, Japanese, Mandarin).
|
|
174
|
+
- **Conversation memory** for follow-up requests within a session.
|
|
175
|
+
- **OpenAI strict JSON-schema mode** — needs schema preprocessing to satisfy the strict-mode constraints (every property required, no `oneOf`, etc.). The current adapter uses `json_object` mode plus the schema in the system prompt for portability.
|
|
176
|
+
|
|
177
|
+
## Related documents
|
|
178
|
+
|
|
179
|
+
- [`/spec/layer-4-nl-grammar/`](../../spec/layer-4-nl-grammar/) — the prompt contract this bridge implements.
|
|
180
|
+
- [`/reference/validator/`](../validator/) — the safety boundary this bridge feeds.
|
|
181
|
+
- [`/examples/`](../../examples/) — the paired natural-language / URML scenarios used as fixtures.
|
|
182
|
+
- [`CLAUDE.md`](../../CLAUDE.md) §What Claude Should Never Do — the provider-neutrality requirement, in writing.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "urml-llm-bridge"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Provider-agnostic glue between natural language and validated URML programs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Apache-2.0" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "URML Maintainers" }]
|
|
13
|
+
keywords = ["robotics", "urml", "llm", "specification"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pydantic>=2.6,<3",
|
|
26
|
+
"PyYAML>=6.0,<7",
|
|
27
|
+
"urml-validator>=0.1.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
# Provider adapters are opt-in. Install the ones you need.
|
|
32
|
+
anthropic = ["anthropic>=0.34"]
|
|
33
|
+
openai = ["openai>=1.40"]
|
|
34
|
+
# On-device adapters (RFC-0021) talk HTTP to a local inference server.
|
|
35
|
+
# Both depend on httpx and require a separately-running server (llama-server
|
|
36
|
+
# or ollama serve); neither bundles model weights.
|
|
37
|
+
llama_cpp = ["httpx>=0.27"]
|
|
38
|
+
ollama = ["httpx>=0.27"]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=8",
|
|
41
|
+
"pytest-cov>=5",
|
|
42
|
+
"ruff>=0.5",
|
|
43
|
+
"mypy>=1.10",
|
|
44
|
+
"types-PyYAML>=6.0",
|
|
45
|
+
# Provider SDKs included in dev so mypy can type-check the adapters
|
|
46
|
+
# and the test suite can exercise them with mocked clients. End users
|
|
47
|
+
# install just the extras they need (e.g., pip install urml-llm-bridge[anthropic]).
|
|
48
|
+
"anthropic>=0.34",
|
|
49
|
+
"openai>=1.40",
|
|
50
|
+
"httpx>=0.27",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://github.com/URML-MARS/URML"
|
|
55
|
+
Repository = "https://github.com/URML-MARS/URML"
|
|
56
|
+
Issues = "https://github.com/URML-MARS/URML/issues"
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/urml_llm_bridge"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
line-length = 120
|
|
63
|
+
target-version = "py311"
|
|
64
|
+
src = ["src", "tests"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "W", "I", "B", "UP", "N", "ANN", "RUF"]
|
|
68
|
+
ignore = ["ANN401"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.per-file-ignores]
|
|
71
|
+
"tests/*" = ["ANN"]
|
|
72
|
+
|
|
73
|
+
[tool.mypy]
|
|
74
|
+
strict = true
|
|
75
|
+
python_version = "3.11"
|
|
76
|
+
plugins = ["pydantic.mypy"]
|
|
77
|
+
mypy_path = "src"
|
|
78
|
+
packages = ["urml_llm_bridge"]
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
minversion = "8.0"
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
addopts = ["-q", "--strict-markers"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""urml_llm_bridge — provider-agnostic glue from natural language to validated URML.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
|
|
5
|
+
Bridge(provider, manifest, envelope=None, profiles=(), max_revisions=3)
|
|
6
|
+
.translate(user_request: str) -> TranslateResult
|
|
7
|
+
|
|
8
|
+
EchoProvider(responses: dict[str, str])
|
|
9
|
+
A hermetic provider for testing, with no network.
|
|
10
|
+
|
|
11
|
+
Bring your own provider by implementing LLMProvider.complete(). Real
|
|
12
|
+
adapters for Anthropic and OpenAI live under `urml_llm_bridge.providers`
|
|
13
|
+
and are imported lazily so the bridge has no hard dependency on either
|
|
14
|
+
SDK.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from urml_llm_bridge._version import __version__
|
|
20
|
+
from urml_llm_bridge.bridge import Bridge, TranslateResult
|
|
21
|
+
from urml_llm_bridge.errors import (
|
|
22
|
+
BridgeError,
|
|
23
|
+
BridgePolicyViolation,
|
|
24
|
+
BridgeRevisionExhausted,
|
|
25
|
+
ProviderError,
|
|
26
|
+
)
|
|
27
|
+
from urml_llm_bridge.few_shot import (
|
|
28
|
+
FewShot,
|
|
29
|
+
default_few_shots,
|
|
30
|
+
drone_few_shots,
|
|
31
|
+
few_shots_for,
|
|
32
|
+
home_few_shots,
|
|
33
|
+
industrial_few_shots,
|
|
34
|
+
)
|
|
35
|
+
from urml_llm_bridge.prompt import build_system_prompt
|
|
36
|
+
from urml_llm_bridge.providers.base import LLMProvider
|
|
37
|
+
from urml_llm_bridge.providers.echo import EchoProvider
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"Bridge",
|
|
41
|
+
"BridgeError",
|
|
42
|
+
"BridgePolicyViolation",
|
|
43
|
+
"BridgeRevisionExhausted",
|
|
44
|
+
"EchoProvider",
|
|
45
|
+
"FewShot",
|
|
46
|
+
"LLMProvider",
|
|
47
|
+
"ProviderError",
|
|
48
|
+
"TranslateResult",
|
|
49
|
+
"__version__",
|
|
50
|
+
"build_system_prompt",
|
|
51
|
+
"default_few_shots",
|
|
52
|
+
"drone_few_shots",
|
|
53
|
+
"few_shots_for",
|
|
54
|
+
"home_few_shots",
|
|
55
|
+
"industrial_few_shots",
|
|
56
|
+
]
|