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.
Files changed (28) hide show
  1. brainforge_inference_utils-0.0.1a1/LICENSE.md +21 -0
  2. brainforge_inference_utils-0.0.1a1/PKG-INFO +154 -0
  3. brainforge_inference_utils-0.0.1a1/README.md +124 -0
  4. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/__init__.py +82 -0
  5. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/client.py +117 -0
  6. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/config.py +105 -0
  7. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/mcp.py +92 -0
  8. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/pipeline.py +171 -0
  9. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/registry.py +115 -0
  10. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/steps/__init__.py +51 -0
  11. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/steps/base.py +194 -0
  12. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/transaction.py +79 -0
  13. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils/version.py +31 -0
  14. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/PKG-INFO +154 -0
  15. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/SOURCES.txt +26 -0
  16. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/dependency_links.txt +1 -0
  17. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/requires.txt +7 -0
  18. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/top_level.txt +1 -0
  19. brainforge_inference_utils-0.0.1a1/brainforge_inference_utils.egg-info/zip-safe +1 -0
  20. brainforge_inference_utils-0.0.1a1/setup.cfg +4 -0
  21. brainforge_inference_utils-0.0.1a1/setup.py +103 -0
  22. brainforge_inference_utils-0.0.1a1/tests/test_client.py +175 -0
  23. brainforge_inference_utils-0.0.1a1/tests/test_config.py +130 -0
  24. brainforge_inference_utils-0.0.1a1/tests/test_mcp.py +64 -0
  25. brainforge_inference_utils-0.0.1a1/tests/test_pipeline.py +236 -0
  26. brainforge_inference_utils-0.0.1a1/tests/test_registry.py +156 -0
  27. brainforge_inference_utils-0.0.1a1/tests/test_steps.py +80 -0
  28. 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)