brainforge-inference-utils 0.0.1a1__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.
- brainforge_inference_utils-0.0.1a1/LICENSE.md +21 -0
- brainforge_inference_utils-0.0.1a1/PKG-INFO +154 -0
- brainforge_inference_utils-0.0.1a1/README.md +124 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/__init__.py +82 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/client.py +117 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/config.py +105 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/mcp.py +92 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/pipeline.py +171 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/registry.py +115 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/steps/__init__.py +51 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/steps/base.py +194 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/transaction.py +79 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/version.py +31 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/PKG-INFO +154 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/SOURCES.txt +26 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/dependency_links.txt +1 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/requires.txt +7 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/top_level.txt +1 -0
- brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/zip-safe +1 -0
- brainforge_inference_utils-0.0.1a1/setup.cfg +4 -0
- brainforge_inference_utils-0.0.1a1/setup.py +103 -0
- brainforge_inference_utils-0.0.1a1/tests/test_client.py +175 -0
- brainforge_inference_utils-0.0.1a1/tests/test_config.py +130 -0
- brainforge_inference_utils-0.0.1a1/tests/test_mcp.py +64 -0
- brainforge_inference_utils-0.0.1a1/tests/test_pipeline.py +236 -0
- brainforge_inference_utils-0.0.1a1/tests/test_registry.py +156 -0
- brainforge_inference_utils-0.0.1a1/tests/test_steps.py +80 -0
- brainforge_inference_utils-0.0.1a1/tests/test_transaction.py +74 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
|
|
2
|
+
# All trademark and other rights reserved by their respective owners
|
|
3
|
+
# Copyright 2008-2026 Neongecko.com Inc.
|
|
4
|
+
# BSD-3 License
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
7
|
+
following conditions are met:
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
|
9
|
+
disclaimer.
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
|
11
|
+
disclaimer in the documentation and/or other materials provided with the distribution.
|
|
12
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
16
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
18
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
19
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
20
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
21
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brainforge-inference-utils
|
|
3
|
+
Version: 0.0.1a1
|
|
4
|
+
Summary: Pluggable request/response pipelines for BrainForge LLM inference
|
|
5
|
+
Home-page: https://github.com/NeonGeckoCom/brainforge-inference-utils
|
|
6
|
+
Author: NeonGecko
|
|
7
|
+
Author-email: developers@neon.ai
|
|
8
|
+
License: AGPL-3.0-only
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE.md
|
|
13
|
+
Requires-Dist: pydantic~=2.0
|
|
14
|
+
Requires-Dist: neon-data-models>=0.0.3a8,~=0.0.2
|
|
15
|
+
Requires-Dist: fastmcp~=2.3
|
|
16
|
+
Requires-Dist: requests
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest~=8.0; extra == "test"
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
Dynamic: provides-extra
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
# BrainForge Inference Utilities
|
|
32
|
+
|
|
33
|
+
Pluggable pipelines for shaping BrainForge LLM requests and responses.
|
|
34
|
+
|
|
35
|
+
This package defines request/response orchestration that attaches to an
|
|
36
|
+
existing inference workflow. Concrete behavior (ambient context, RAG/MCP
|
|
37
|
+
injection, request history validation, citations, response sanitizing,
|
|
38
|
+
safety guardrails, ...) can be implemented as **steps** that plug into one of
|
|
39
|
+
two pipelines:
|
|
40
|
+
|
|
41
|
+
- **Request pipeline** — runs before generation and shapes the request:
|
|
42
|
+
injecting informational context, RAG retrieval for host-selected
|
|
43
|
+
providers, validating and truncating history, etc.
|
|
44
|
+
- **Response pipeline** — runs after generation and shapes the response:
|
|
45
|
+
citation rewrite, sanitization, guardrails, etc.
|
|
46
|
+
|
|
47
|
+
Primary generation stays with the host, between the two pipelines. The host
|
|
48
|
+
keeps the `UserTransaction` from the request pipeline until the response is
|
|
49
|
+
ready. A pipeline step may call an inference endpoint to modify the transaction
|
|
50
|
+
(for example citation rewrite) without the host owning those requests.
|
|
51
|
+
|
|
52
|
+
Both pipelines operate on a `UserTransaction`, which carries the request,
|
|
53
|
+
response, and two per-side context dicts (`request_step_context` and
|
|
54
|
+
`response_step_context`). Requests and responses use the existing
|
|
55
|
+
`neon-data-models` models (`LLMRequest` / `LLMResponse`).
|
|
56
|
+
|
|
57
|
+
## Concepts
|
|
58
|
+
|
|
59
|
+
| Concept | Purpose |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `UserTransaction` | The request/response pair plus `request_step_context` / `response_step_context` dicts. The host holds this object across generation. |
|
|
62
|
+
| `BrainForgeClient` | Builds pipelines from config and exposes `run_request_pipeline` / `run_response_pipeline` / `supported_request_context`. |
|
|
63
|
+
| `RequestStep` | Shapes `transaction.request` before generation. Declares supported `request_context` keys/values. |
|
|
64
|
+
| `ResponseStep` | Annotates/rewrites `transaction.response` after generation. |
|
|
65
|
+
| `RequestContextOption` | One `request_context` key a step reads, with optional allowed values for hosts to present. |
|
|
66
|
+
| `RequestPipeline` / `ResponsePipeline` | Run an ordered list of steps over a transaction. |
|
|
67
|
+
| `PipelineConfig` / `StepConfig` | Declarative, ordered step lists (see below). |
|
|
68
|
+
|
|
69
|
+
## Usage sketch
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
client = BrainForgeClient(pipeline_config)
|
|
73
|
+
|
|
74
|
+
options = await client.supported_request_context()
|
|
75
|
+
# e.g. options["rag_providers"].values, options["conversational"].values
|
|
76
|
+
|
|
77
|
+
transaction = await client.run_request_pipeline(llm_request)
|
|
78
|
+
# host performs generation using transaction.request
|
|
79
|
+
transaction = await client.run_response_pipeline(transaction, llm_response)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Request steps write `transaction.request_step_context[step.id]`. Response steps write
|
|
83
|
+
`transaction.response_step_context[step.id]` and may *read* the request-step context. Those
|
|
84
|
+
namespaces are independent, so the same step id may exist on both sides
|
|
85
|
+
without colliding. Context is available across generation only because the
|
|
86
|
+
same `UserTransaction` is passed back in; this package does not store it
|
|
87
|
+
between calls.
|
|
88
|
+
|
|
89
|
+
## Design notes
|
|
90
|
+
|
|
91
|
+
- **Steps are independently defensive.** There is no dependency graph between
|
|
92
|
+
steps. A step that needs something it does not find (e.g. a citation step with
|
|
93
|
+
no retrieved sources) reports itself inapplicable via `applicable()` and is
|
|
94
|
+
skipped with a logged reason, rather than failing the pipeline.
|
|
95
|
+
- **Ordering is explicit.** Step order is exactly the order declared in
|
|
96
|
+
configuration; the pipeline does not reorder.
|
|
97
|
+
- **Steps may be repeated.** The same step id may appear more than once in a
|
|
98
|
+
pipeline, including with different `config`. Each appearance is a separate
|
|
99
|
+
instance and runs in declared order. The pipeline does not merge or
|
|
100
|
+
de-duplicate entries.
|
|
101
|
+
- **Steps own their context.** Each side has its own dict keyed by step id.
|
|
102
|
+
A repeated step on the *same* side shares that key; a step id on the request
|
|
103
|
+
side does not clash with the same id on the response side. Each step is
|
|
104
|
+
responsible for how it stores and reads its own data across one or more
|
|
105
|
+
calls (for example appending to a list instead of overwriting it).
|
|
106
|
+
Response steps that consume request-step data should read
|
|
107
|
+
`transaction.request_step_context` and report inapplicable when required
|
|
108
|
+
context is absent.
|
|
109
|
+
- **Format contracts live together.** Steps that share a parse format
|
|
110
|
+
(e.g. a citation formatter and the sanitizer that strips it) belong in the
|
|
111
|
+
same module and should derive from a shared constant so they cannot drift.
|
|
112
|
+
|
|
113
|
+
## Step plugins
|
|
114
|
+
|
|
115
|
+
Installed packages advertise steps with setuptools entry points. Request and
|
|
116
|
+
response steps use different groups:
|
|
117
|
+
|
|
118
|
+
| Pipeline | Entry point group |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| Request | `brainforge.inference.request_steps` |
|
|
121
|
+
| Response | `brainforge.inference.response_steps` |
|
|
122
|
+
|
|
123
|
+
Both groups use the step id `author.step_name`:
|
|
124
|
+
|
|
125
|
+
- `author` is the plugin developer's slug (`acme`, `brainforge`).
|
|
126
|
+
- `step_name` names that one step (`guardrails`, `rag`).
|
|
127
|
+
|
|
128
|
+
Each part may contain letters, digits, hyphens, and underscores. The class
|
|
129
|
+
`id`, the config `id`, and the context-dict key are that same string.
|
|
130
|
+
|
|
131
|
+
The same `author.step_name` may be registered once in each group. Each
|
|
132
|
+
pipeline reads only its own group, so the request class and the response class
|
|
133
|
+
stay distinct. Two packages registering the same name in one group is an
|
|
134
|
+
error.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
entry_points={
|
|
138
|
+
"brainforge.inference.request_steps": [
|
|
139
|
+
"acme.guardrails=acme_guardrails:GuardrailRequestStep",
|
|
140
|
+
],
|
|
141
|
+
"brainforge.inference.response_steps": [
|
|
142
|
+
"acme.guardrails=acme_guardrails:GuardrailResponseStep",
|
|
143
|
+
],
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
pipeline:
|
|
149
|
+
request:
|
|
150
|
+
- id: acme.guardrails
|
|
151
|
+
response:
|
|
152
|
+
- id: acme.guardrails
|
|
153
|
+
```
|
|
154
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# BrainForge Inference Utilities
|
|
2
|
+
|
|
3
|
+
Pluggable pipelines for shaping BrainForge LLM requests and responses.
|
|
4
|
+
|
|
5
|
+
This package defines request/response orchestration that attaches to an
|
|
6
|
+
existing inference workflow. Concrete behavior (ambient context, RAG/MCP
|
|
7
|
+
injection, request history validation, citations, response sanitizing,
|
|
8
|
+
safety guardrails, ...) can be implemented as **steps** that plug into one of
|
|
9
|
+
two pipelines:
|
|
10
|
+
|
|
11
|
+
- **Request pipeline** — runs before generation and shapes the request:
|
|
12
|
+
injecting informational context, RAG retrieval for host-selected
|
|
13
|
+
providers, validating and truncating history, etc.
|
|
14
|
+
- **Response pipeline** — runs after generation and shapes the response:
|
|
15
|
+
citation rewrite, sanitization, guardrails, etc.
|
|
16
|
+
|
|
17
|
+
Primary generation stays with the host, between the two pipelines. The host
|
|
18
|
+
keeps the `UserTransaction` from the request pipeline until the response is
|
|
19
|
+
ready. A pipeline step may call an inference endpoint to modify the transaction
|
|
20
|
+
(for example citation rewrite) without the host owning those requests.
|
|
21
|
+
|
|
22
|
+
Both pipelines operate on a `UserTransaction`, which carries the request,
|
|
23
|
+
response, and two per-side context dicts (`request_step_context` and
|
|
24
|
+
`response_step_context`). Requests and responses use the existing
|
|
25
|
+
`neon-data-models` models (`LLMRequest` / `LLMResponse`).
|
|
26
|
+
|
|
27
|
+
## Concepts
|
|
28
|
+
|
|
29
|
+
| Concept | Purpose |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `UserTransaction` | The request/response pair plus `request_step_context` / `response_step_context` dicts. The host holds this object across generation. |
|
|
32
|
+
| `BrainForgeClient` | Builds pipelines from config and exposes `run_request_pipeline` / `run_response_pipeline` / `supported_request_context`. |
|
|
33
|
+
| `RequestStep` | Shapes `transaction.request` before generation. Declares supported `request_context` keys/values. |
|
|
34
|
+
| `ResponseStep` | Annotates/rewrites `transaction.response` after generation. |
|
|
35
|
+
| `RequestContextOption` | One `request_context` key a step reads, with optional allowed values for hosts to present. |
|
|
36
|
+
| `RequestPipeline` / `ResponsePipeline` | Run an ordered list of steps over a transaction. |
|
|
37
|
+
| `PipelineConfig` / `StepConfig` | Declarative, ordered step lists (see below). |
|
|
38
|
+
|
|
39
|
+
## Usage sketch
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
client = BrainForgeClient(pipeline_config)
|
|
43
|
+
|
|
44
|
+
options = await client.supported_request_context()
|
|
45
|
+
# e.g. options["rag_providers"].values, options["conversational"].values
|
|
46
|
+
|
|
47
|
+
transaction = await client.run_request_pipeline(llm_request)
|
|
48
|
+
# host performs generation using transaction.request
|
|
49
|
+
transaction = await client.run_response_pipeline(transaction, llm_response)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Request steps write `transaction.request_step_context[step.id]`. Response steps write
|
|
53
|
+
`transaction.response_step_context[step.id]` and may *read* the request-step context. Those
|
|
54
|
+
namespaces are independent, so the same step id may exist on both sides
|
|
55
|
+
without colliding. Context is available across generation only because the
|
|
56
|
+
same `UserTransaction` is passed back in; this package does not store it
|
|
57
|
+
between calls.
|
|
58
|
+
|
|
59
|
+
## Design notes
|
|
60
|
+
|
|
61
|
+
- **Steps are independently defensive.** There is no dependency graph between
|
|
62
|
+
steps. A step that needs something it does not find (e.g. a citation step with
|
|
63
|
+
no retrieved sources) reports itself inapplicable via `applicable()` and is
|
|
64
|
+
skipped with a logged reason, rather than failing the pipeline.
|
|
65
|
+
- **Ordering is explicit.** Step order is exactly the order declared in
|
|
66
|
+
configuration; the pipeline does not reorder.
|
|
67
|
+
- **Steps may be repeated.** The same step id may appear more than once in a
|
|
68
|
+
pipeline, including with different `config`. Each appearance is a separate
|
|
69
|
+
instance and runs in declared order. The pipeline does not merge or
|
|
70
|
+
de-duplicate entries.
|
|
71
|
+
- **Steps own their context.** Each side has its own dict keyed by step id.
|
|
72
|
+
A repeated step on the *same* side shares that key; a step id on the request
|
|
73
|
+
side does not clash with the same id on the response side. Each step is
|
|
74
|
+
responsible for how it stores and reads its own data across one or more
|
|
75
|
+
calls (for example appending to a list instead of overwriting it).
|
|
76
|
+
Response steps that consume request-step data should read
|
|
77
|
+
`transaction.request_step_context` and report inapplicable when required
|
|
78
|
+
context is absent.
|
|
79
|
+
- **Format contracts live together.** Steps that share a parse format
|
|
80
|
+
(e.g. a citation formatter and the sanitizer that strips it) belong in the
|
|
81
|
+
same module and should derive from a shared constant so they cannot drift.
|
|
82
|
+
|
|
83
|
+
## Step plugins
|
|
84
|
+
|
|
85
|
+
Installed packages advertise steps with setuptools entry points. Request and
|
|
86
|
+
response steps use different groups:
|
|
87
|
+
|
|
88
|
+
| Pipeline | Entry point group |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| Request | `brainforge.inference.request_steps` |
|
|
91
|
+
| Response | `brainforge.inference.response_steps` |
|
|
92
|
+
|
|
93
|
+
Both groups use the step id `author.step_name`:
|
|
94
|
+
|
|
95
|
+
- `author` is the plugin developer's slug (`acme`, `brainforge`).
|
|
96
|
+
- `step_name` names that one step (`guardrails`, `rag`).
|
|
97
|
+
|
|
98
|
+
Each part may contain letters, digits, hyphens, and underscores. The class
|
|
99
|
+
`id`, the config `id`, and the context-dict key are that same string.
|
|
100
|
+
|
|
101
|
+
The same `author.step_name` may be registered once in each group. Each
|
|
102
|
+
pipeline reads only its own group, so the request class and the response class
|
|
103
|
+
stay distinct. Two packages registering the same name in one group is an
|
|
104
|
+
error.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
entry_points={
|
|
108
|
+
"brainforge.inference.request_steps": [
|
|
109
|
+
"acme.guardrails=acme_guardrails:GuardrailRequestStep",
|
|
110
|
+
],
|
|
111
|
+
"brainforge.inference.response_steps": [
|
|
112
|
+
"acme.guardrails=acme_guardrails:GuardrailResponseStep",
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
pipeline:
|
|
119
|
+
request:
|
|
120
|
+
- id: acme.guardrails
|
|
121
|
+
response:
|
|
122
|
+
- id: acme.guardrails
|
|
123
|
+
```
|
|
124
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework
|
|
2
|
+
# All trademark and other rights reserved by their respective owners
|
|
3
|
+
# Copyright 2008-2026 Neongecko.com Inc.
|
|
4
|
+
# Contributors: Daniel McKnight, Richard Leeds, Bohdan Mykhailenko,
|
|
5
|
+
# Dmitry Honcharenko, Mike Gray, Clary Gasper, Charlie Bailey, Ryan Venturi,
|
|
6
|
+
# Roger Wiebe, Pierce Seigne, Sathvika Nadipalli, Guy Daniels, Elon Gasper,
|
|
7
|
+
# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo
|
|
8
|
+
# BSD-3 License
|
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
|
10
|
+
# modification, are permitted provided that the following conditions are met:
|
|
11
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer.
|
|
13
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
# and/or other materials provided with the distribution.
|
|
16
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
# contributors may be used to endorse or promote products derived from this
|
|
18
|
+
# software without specific prior written permission.
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
21
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
22
|
+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
23
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
24
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
25
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
26
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
27
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
28
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
29
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
"""BrainForge inference pipeline utilities."""
|
|
31
|
+
|
|
32
|
+
from neon_data_models.models.api.llm import (
|
|
33
|
+
LLMPersona,
|
|
34
|
+
LLMRequest,
|
|
35
|
+
LLMResponse,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from brainforge_inference_utils.version import __version__
|
|
39
|
+
|
|
40
|
+
from brainforge_inference_utils.client import BrainForgeClient
|
|
41
|
+
from brainforge_inference_utils.config import PipelineConfig, StepConfig
|
|
42
|
+
from brainforge_inference_utils.pipeline import (
|
|
43
|
+
RequestPipeline,
|
|
44
|
+
ResponsePipeline,
|
|
45
|
+
build_pipelines,
|
|
46
|
+
)
|
|
47
|
+
from brainforge_inference_utils.registry import (
|
|
48
|
+
REQUEST_STEP_ENTRY_POINT,
|
|
49
|
+
RESPONSE_STEP_ENTRY_POINT,
|
|
50
|
+
)
|
|
51
|
+
from brainforge_inference_utils.steps.base import (
|
|
52
|
+
RequestStep,
|
|
53
|
+
RequestContextOption,
|
|
54
|
+
ResponseStep,
|
|
55
|
+
Step,
|
|
56
|
+
)
|
|
57
|
+
from brainforge_inference_utils.transaction import UserTransaction
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"__version__",
|
|
61
|
+
"BrainForgeClient",
|
|
62
|
+
# Request/response models (re-exported from neon-data-models)
|
|
63
|
+
"LLMRequest",
|
|
64
|
+
"LLMResponse",
|
|
65
|
+
"LLMPersona",
|
|
66
|
+
# Transaction
|
|
67
|
+
"UserTransaction",
|
|
68
|
+
# Steps
|
|
69
|
+
"Step",
|
|
70
|
+
"RequestStep",
|
|
71
|
+
"ResponseStep",
|
|
72
|
+
"RequestContextOption",
|
|
73
|
+
# Pipelines
|
|
74
|
+
"RequestPipeline",
|
|
75
|
+
"ResponsePipeline",
|
|
76
|
+
"build_pipelines",
|
|
77
|
+
"REQUEST_STEP_ENTRY_POINT",
|
|
78
|
+
"RESPONSE_STEP_ENTRY_POINT",
|
|
79
|
+
# Config
|
|
80
|
+
"PipelineConfig",
|
|
81
|
+
"StepConfig",
|
|
82
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework
|
|
2
|
+
# All trademark and other rights reserved by their respective owners
|
|
3
|
+
# Copyright 2008-2026 Neongecko.com Inc.
|
|
4
|
+
# Contributors: Daniel McKnight, Richard Leeds, Bohdan Mykhailenko,
|
|
5
|
+
# Dmitry Honcharenko, Mike Gray, Clary Gasper, Charlie Bailey, Ryan Venturi,
|
|
6
|
+
# Roger Wiebe, Pierce Seigne, Sathvika Nadipalli, Guy Daniels, Elon Gasper,
|
|
7
|
+
# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo
|
|
8
|
+
# BSD-3 License
|
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
|
10
|
+
# modification, are permitted provided that the following conditions are met:
|
|
11
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer.
|
|
13
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
# and/or other materials provided with the distribution.
|
|
16
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
# contributors may be used to endorse or promote products derived from this
|
|
18
|
+
# software without specific prior written permission.
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
21
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
22
|
+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
23
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
24
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
25
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
26
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
27
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
28
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
29
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
"""Configured runner for the request and response pipelines.
|
|
31
|
+
|
|
32
|
+
`BrainForgeClient` does not call an LLM. The host runs inference between
|
|
33
|
+
`run_request_pipeline` and `run_response_pipeline`, and is responsible for keeping
|
|
34
|
+
the returned `UserTransaction` until the response is ready.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from typing import Dict
|
|
40
|
+
|
|
41
|
+
from neon_data_models.models.api.llm import LLMRequest, LLMResponse
|
|
42
|
+
|
|
43
|
+
from brainforge_inference_utils.config import PipelineConfig
|
|
44
|
+
from brainforge_inference_utils.pipeline import build_pipelines
|
|
45
|
+
from brainforge_inference_utils.steps.base import RequestContextOption
|
|
46
|
+
from brainforge_inference_utils.transaction import UserTransaction
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BrainForgeClient:
|
|
50
|
+
"""Build pipelines from config and run them around host-owned inference."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, config: PipelineConfig):
|
|
53
|
+
self.config = config
|
|
54
|
+
self.request_pipeline, self.response_pipeline = build_pipelines(config)
|
|
55
|
+
|
|
56
|
+
async def supported_request_context(
|
|
57
|
+
self, timeout: float = 10.0
|
|
58
|
+
) -> Dict[str, RequestContextOption]:
|
|
59
|
+
"""
|
|
60
|
+
Collect `request_context` keys and allowed values from the pipeline.
|
|
61
|
+
|
|
62
|
+
The host uses this to populate UI; it does not call MCP itself.
|
|
63
|
+
Duplicate keys across steps are merged: enumerated values are unioned
|
|
64
|
+
in first-seen order, and if any declaration is unconstrained
|
|
65
|
+
(`values is None`) the merged option is unconstrained.
|
|
66
|
+
"""
|
|
67
|
+
merged: Dict[str, RequestContextOption] = {}
|
|
68
|
+
for step in (*self.request_pipeline.steps, *self.response_pipeline.steps):
|
|
69
|
+
for option in await step.supported_request_context(timeout=timeout):
|
|
70
|
+
_merge_request_context_option(merged, option)
|
|
71
|
+
return merged
|
|
72
|
+
|
|
73
|
+
async def run_request_pipeline(
|
|
74
|
+
self, request: LLMRequest
|
|
75
|
+
) -> UserTransaction:
|
|
76
|
+
"""
|
|
77
|
+
Shape `request` and collect step context on a new transaction.
|
|
78
|
+
|
|
79
|
+
The caller must hold the returned transaction until
|
|
80
|
+
`run_response_pipeline` (or drop it if the response pipeline is unused).
|
|
81
|
+
"""
|
|
82
|
+
transaction = UserTransaction(request=request)
|
|
83
|
+
return await self.request_pipeline.run(transaction)
|
|
84
|
+
|
|
85
|
+
async def run_response_pipeline(
|
|
86
|
+
self, transaction: UserTransaction, response: LLMResponse
|
|
87
|
+
) -> UserTransaction:
|
|
88
|
+
"""
|
|
89
|
+
Attach `response` to `transaction` and run the response pipeline.
|
|
90
|
+
|
|
91
|
+
Request-step context is on `transaction.request_step_context`; response steps
|
|
92
|
+
write to `transaction.response_step_context`. The caller must pass through
|
|
93
|
+
the transaction from `run_request_pipeline`.
|
|
94
|
+
"""
|
|
95
|
+
transaction.response = response
|
|
96
|
+
return await self.response_pipeline.run(transaction)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _merge_request_context_option(
|
|
100
|
+
merged: Dict[str, RequestContextOption],
|
|
101
|
+
option: RequestContextOption,
|
|
102
|
+
) -> None:
|
|
103
|
+
prior = merged.get(option.key)
|
|
104
|
+
if prior is None:
|
|
105
|
+
merged[option.key] = option
|
|
106
|
+
return
|
|
107
|
+
description = prior.description or option.description
|
|
108
|
+
if prior.values is None or option.values is None:
|
|
109
|
+
merged[option.key] = RequestContextOption(
|
|
110
|
+
option.key, None, description)
|
|
111
|
+
return
|
|
112
|
+
values = list(prior.values)
|
|
113
|
+
for value in option.values:
|
|
114
|
+
if value not in values:
|
|
115
|
+
values.append(value)
|
|
116
|
+
merged[option.key] = RequestContextOption(
|
|
117
|
+
option.key, values, description)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework
|
|
2
|
+
# All trademark and other rights reserved by their respective owners
|
|
3
|
+
# Copyright 2008-2026 Neongecko.com Inc.
|
|
4
|
+
# Contributors: Daniel McKnight, Richard Leeds, Bohdan Mykhailenko,
|
|
5
|
+
# Dmitry Honcharenko, Mike Gray, Clary Gasper, Charlie Bailey, Ryan Venturi,
|
|
6
|
+
# Roger Wiebe, Pierce Seigne, Sathvika Nadipalli, Guy Daniels, Elon Gasper,
|
|
7
|
+
# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo
|
|
8
|
+
# BSD-3 License
|
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
|
10
|
+
# modification, are permitted provided that the following conditions are met:
|
|
11
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer.
|
|
13
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
# and/or other materials provided with the distribution.
|
|
16
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
# contributors may be used to endorse or promote products derived from this
|
|
18
|
+
# software without specific prior written permission.
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
21
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
22
|
+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
23
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
24
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
25
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
26
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
27
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
28
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
29
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
"""
|
|
31
|
+
Pipeline configuration schema.
|
|
32
|
+
|
|
33
|
+
Configuration is two explicitly ordered lists of step entries: `request` and
|
|
34
|
+
`response`. Order is authoritative; the pipeline runs steps in exactly the
|
|
35
|
+
order given and does not reorder. Each entry names a step `id`
|
|
36
|
+
(`author.step_name`, the entry point name) and optionally carries
|
|
37
|
+
step-specific `config`.
|
|
38
|
+
|
|
39
|
+
Example (YAML)::
|
|
40
|
+
|
|
41
|
+
pipeline:
|
|
42
|
+
request:
|
|
43
|
+
- id: brainforge.locale
|
|
44
|
+
config:
|
|
45
|
+
mcp_url: https://mcp.neonaiservices2.com/mcp
|
|
46
|
+
default_location: Renton, WA, USA
|
|
47
|
+
timeout: 5.0
|
|
48
|
+
- {id: brainforge.rag, config: {mcp_url: https://mcp.neonaiservices2.com/mcp, top_k: 10}}
|
|
49
|
+
- brainforge.history # last: valid, truncated history
|
|
50
|
+
response:
|
|
51
|
+
- id: brainforge.citations
|
|
52
|
+
config:
|
|
53
|
+
mcp_url: https://mcp.neonaiservices2.com/mcp
|
|
54
|
+
openai_url: https://hana.example/brainforge/openai/chat/completions
|
|
55
|
+
|
|
56
|
+
A host typically loads its own config and hands the `pipeline` section here::
|
|
57
|
+
|
|
58
|
+
pipeline_config = PipelineConfig.model_validate(config["pipeline"])
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
from __future__ import annotations
|
|
62
|
+
|
|
63
|
+
from typing import Any, Dict, List
|
|
64
|
+
|
|
65
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class StepConfig(BaseModel):
|
|
69
|
+
"""
|
|
70
|
+
A single step entry within a pipeline.
|
|
71
|
+
|
|
72
|
+
:param id: Step id to resolve and instantiate. This is the entry point
|
|
73
|
+
name, `author.step_name`.
|
|
74
|
+
:param enabled: When false, the entry is skipped at build time (a
|
|
75
|
+
convenient way to turn a step off without deleting its config).
|
|
76
|
+
:param config: Step-specific configuration passed to the step constructor.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
model_config = ConfigDict(extra="forbid")
|
|
80
|
+
|
|
81
|
+
id: str
|
|
82
|
+
enabled: bool = True
|
|
83
|
+
config: Dict[str, Any] = Field(default_factory=dict)
|
|
84
|
+
|
|
85
|
+
@model_validator(mode="before")
|
|
86
|
+
@classmethod
|
|
87
|
+
def _coerce_shorthand(cls, data: Any) -> Any:
|
|
88
|
+
"""Allow a bare string entry as shorthand for `{id: <string>}`."""
|
|
89
|
+
if isinstance(data, str):
|
|
90
|
+
return {"id": data}
|
|
91
|
+
return data
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class PipelineConfig(BaseModel):
|
|
95
|
+
"""
|
|
96
|
+
Ordered step lists for the request and response pipelines.
|
|
97
|
+
|
|
98
|
+
:param request: Ordered `RequestStep` entries, run before generation.
|
|
99
|
+
:param response: Ordered `ResponseStep` entries, run after generation.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
model_config = ConfigDict(extra="forbid")
|
|
103
|
+
|
|
104
|
+
request: List[StepConfig] = Field(default_factory=list)
|
|
105
|
+
response: List[StepConfig] = Field(default_factory=list)
|