Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/propose_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ jobs:
uses: neongeckocom/.github/.github/workflows/propose_semver_release.yml@master
with:
branch: dev
release_type: ${ inputs.release_type }
release_type: ${{ inputs.release_type }}
update_changelog: True
version_file: "neon_inference_plugin_rag/version.py"
version_file: "brainforge_inference_plugin_rag/version.py"
pull_changes:
uses: neongeckocom/.github/.github/workflows/pull_master.yml@master
needs: update_version
with:
pr_reviewer: neonreviewers
pr_assignee: ${ github.actor }
pr_assignee: ${{ github.actor }}
pr_draft: false
pr_title: ${ needs.update_version.outputs.version }
pr_body: ${ needs.update_version.outputs.changelog }
pr_title: ${{ needs.update_version.outputs.version }}
pr_body: ${{ needs.update_version.outputs.changelog }}
19 changes: 6 additions & 13 deletions .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
name: Publish GitHub Release
# This workflow will generate a release distribution and upload it to PyPI

name: Publish Build and GitHub Release
on:
push:
branches:
- master

jobs:
tag_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get Version
run: |
VERSION=$(python setup.py --version)
echo "VERSION=${VERSION}" >> $GITHUB_ENV
- uses: ncipollo/release-action@v1
with:
token: ${{secrets.GITHUB_TOKEN}}
tag: ${{env.VERSION}}
build_and_publish_pypi_and_release:
uses: neongeckocom/.github/.github/workflows/publish_stable_release.yml@master
secrets: inherit
6 changes: 3 additions & 3 deletions .github/workflows/publish_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ on:
branches:
- dev
paths-ignore:
- 'neon_inference_plugin_rag/version.py'
- 'brainforge_inference_plugin_rag/version.py'

jobs:
publish_alpha_release:
uses: neongeckocom/.github/.github/workflows/publish_alpha_release.yml@master
secrets: inherit
with:
publish_pypi: false
version_file: "neon_inference_plugin_rag/version.py"
publish_pypi: true
version_file: "brainforge_inference_plugin_rag/version.py"
publish_prerelease: true
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Neon Inference Plugin: RAG
# BrainForge Inference Plugin: RAG

Request step that retrieves documents for selected RAG providers, and a
response step that rewrites the generated answer with citations. Both steps
ship together because citations read `request_step_context["neon.rag"]` and
ship together because citations read `request_step_context["brainforge.rag"]` and
share `apply_citation_markup`.

| Pipeline | Entry point group | Step id | Class |
| --- | --- | --- | --- |
| Request | `brainforge.inference.request_steps` | `neon.rag` | `RagStep` |
| Response | `brainforge.inference.response_steps` | `neon.citations` | `ResponseParser` |
| Request | `brainforge.inference.request_steps` | `brainforge.rag` | `RagStep` |
| Response | `brainforge.inference.response_steps` | `brainforge.citations` | `ResponseParser` |

```yaml
pipeline:
request:
- id: neon.rag
- id: brainforge.rag
config:
mcp_url: https://mcp.neonaiservices2.com/mcp
top_k: 10
response:
- id: neon.citations
- id: brainforge.citations
config:
mcp_url: https://mcp.neonaiservices2.com/mcp
openai_url: https://hana.example/brainforge/openai/chat/completions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

"""RAG retrieval and citation steps for BrainForge inference pipelines."""

from neon_inference_plugin_rag.rag import (
from brainforge_inference_plugin_rag.rag import (
RagStep,
ResponseParser,
apply_citation_markup,
)
from neon_inference_plugin_rag.version import __version__
from brainforge_inference_plugin_rag.version import __version__

__all__ = [
"RagStep",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
RAG retrieval and citation rewrite.

`RagStep` lists providers and injects retrieved documents on the request
pipeline. `ResponseParser` (id `neon.citations`) runs on the response pipeline:
pipeline. `ResponseParser` (id `brainforge.citations`) runs on the response pipeline:
it filters those documents, builds a citation completion via MCP, POSTs it
to a configured OpenAI-compatible endpoint, and writes the annotated text
back onto the same `UserTransaction`.
Expand All @@ -30,7 +30,6 @@
from typing import Any, Dict, List, Optional, Tuple

import requests
from neon_data_models.models.api.llm import LLMResponse

from brainforge_inference_utils.mcp import mcp_client, mcp_tool_content_parts
from brainforge_inference_utils.steps.base import (
Expand Down Expand Up @@ -59,7 +58,7 @@ class RagStep(RequestStep):
- `rag_top_k`: Optional per-request override of configured `top_k`.
"""

id = "neon.rag"
id = "brainforge.rag"

def __init__(self, config: Optional[dict] = None) -> None:
resolved = dict(config or {})
Expand Down Expand Up @@ -252,7 +251,7 @@ class ResponseParser(ResponseStep):
- `citations_enabled`: When false, the step is skipped.
"""

id = "neon.citations"
id = "brainforge.citations"
request_context_options = (
RequestContextOption(
"citations_enabled",
Expand Down Expand Up @@ -282,9 +281,7 @@ def applicable(self, transaction: UserTransaction) -> Optional[str]:
return "no rag documents"
return None

async def process(
self, transaction: UserTransaction
) -> Optional[LLMResponse]:
async def process(self, transaction: UserTransaction) -> None:
documents = list(
(transaction.request_step_context.get(RagStep.id) or {}).get("documents") or [])
original = transaction.response.response
Expand All @@ -297,7 +294,7 @@ async def process(
"credits": "",
"formatted_credits": "",
}
return None
return
payload = await self._build_citation_request(
original, filtered, timeout)
raw_cited = await self._complete(
Expand All @@ -310,7 +307,7 @@ async def process(
"documents": documents,
"error": str(exc),
}
return None
return

transaction.response_step_context[self.id] = {
"documents": filtered,
Expand All @@ -321,8 +318,8 @@ async def process(
history = list(transaction.response.history)
if history and history[-1][0] == "llm":
history[-1] = ("llm", rewritten)
return transaction.response.model_copy(
update={"response": rewritten, "history": history})
transaction.response.response = rewritten
transaction.response.history = history

async def _filter_sources(
self, response: str, documents: List[dict], timeout: float
Expand Down
2 changes: 1 addition & 1 deletion requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
brainforge-inference-utils @ git+https://github.com/NeonGeckoCom/brainforge-inference-utils
brainforge-inference-utils~=0.0,>=0.0.1a1
requests
10 changes: 5 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def get_requirements(requirements_filename: str):
long_description = f.read()

with open(
path.join(BASE_PATH, "neon_inference_plugin_rag", "version.py"),
path.join(BASE_PATH, "brainforge_inference_plugin_rag", "version.py"),
"r",
encoding="utf-8",
) as v:
Expand All @@ -65,12 +65,12 @@ def get_requirements(requirements_filename: str):


setup(
name="neon-inference-plugin-rag",
name="brainforge-inference-plugin-rag",
version=version,
description="RAG retrieval and citation steps for BrainForge inference pipelines",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/NeonGeckoCom/neon-inference-plugin-rag",
url="https://github.com/NeonGeckoCom/brainforge-inference-plugin-rag",
author="NeonGecko",
author_email="developers@neon.ai",
license="AGPL-3.0-only",
Expand All @@ -87,10 +87,10 @@ def get_requirements(requirements_filename: str):
],
entry_points={
"brainforge.inference.request_steps": [
"neon.rag = neon_inference_plugin_rag:RagStep",
"brainforge.rag = brainforge_inference_plugin_rag:RagStep",
],
"brainforge.inference.response_steps": [
"neon.citations = neon_inference_plugin_rag:ResponseParser",
"brainforge.citations = brainforge_inference_plugin_rag:ResponseParser",
],
},
)
12 changes: 6 additions & 6 deletions tests/test_entry_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,26 @@

from importlib.metadata import entry_points

from neon_inference_plugin_rag import RagStep
from neon_inference_plugin_rag import ResponseParser
from brainforge_inference_plugin_rag import RagStep
from brainforge_inference_plugin_rag import ResponseParser

def test_entry_point_loads_rag_step():
matches = [
ep for ep in entry_points(group="brainforge.inference.request_steps")
if ep.name == "neon.rag"
if ep.name == "brainforge.rag"
]
assert len(matches) == 1
loaded = matches[0].load()
assert loaded is RagStep
assert loaded.id == "neon.rag"
assert loaded.id == "brainforge.rag"


def test_entry_point_loads_citation_step():
matches = [
ep for ep in entry_points(group="brainforge.inference.response_steps")
if ep.name == "neon.citations"
if ep.name == "brainforge.citations"
]
assert len(matches) == 1
loaded = matches[0].load()
assert loaded is ResponseParser
assert loaded.id == "neon.citations"
assert loaded.id == "brainforge.citations"
10 changes: 5 additions & 5 deletions tests/test_rag.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import unittest
from unittest.mock import AsyncMock, patch

from neon_inference_plugin_rag import RagStep
from brainforge_inference_plugin_rag import RagStep
from brainforge_inference_utils.transaction import UserTransaction
from helpers import make_request

Expand Down Expand Up @@ -59,9 +59,9 @@ async def test_injects_information_and_stores_documents(self):
self.assertIn("https://b: chunk two", transaction.request.history[0][1])
self.assertEqual(transaction.request.history[1], ("user", "hi"))
self.assertEqual(
transaction.request_step_context["neon.rag"]["documents"], DOCUMENTS)
transaction.request_step_context["brainforge.rag"]["documents"], DOCUMENTS)
self.assertEqual(
transaction.request_step_context["neon.rag"]["providers"], ["news"])
transaction.request_step_context["brainforge.rag"]["providers"], ["news"])

async def test_uses_request_top_k_override(self):
step = RagStep(STEP_CONFIG)
Expand All @@ -78,7 +78,7 @@ async def test_uses_request_top_k_override(self):
lookup.assert_awaited_once_with(
transaction.request.query, ["docs"], 3, STEP_CONFIG["timeout"])
self.assertEqual(transaction.request.history, make_request().history)
self.assertEqual(transaction.request_step_context["neon.rag"]["documents"], [])
self.assertEqual(transaction.request_step_context["brainforge.rag"]["documents"], [])

async def test_lookup_failure_leaves_history_unchanged(self):
step = RagStep(STEP_CONFIG)
Expand All @@ -97,7 +97,7 @@ async def test_lookup_failure_leaves_history_unchanged(self):

self.assertEqual(transaction.request.history, original)
self.assertEqual(
transaction.request_step_context["neon.rag"]["error"], "timed out")
transaction.request_step_context["brainforge.rag"]["error"], "timed out")

def test_skips_when_no_providers_selected(self):
step = RagStep(STEP_CONFIG)
Expand Down
21 changes: 10 additions & 11 deletions tests/test_response_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import unittest
from unittest.mock import AsyncMock, patch

from neon_inference_plugin_rag import (
from brainforge_inference_plugin_rag import (
ResponseParser,
apply_citation_markup,
)
Expand Down Expand Up @@ -56,7 +56,7 @@ def _transaction(self, text="See the docs.", **request_context):
request=request,
response=make_response(text, request),
request_context=request_context,
request_step_context={"neon.rag": {"documents": DOCUMENTS}},
request_step_context={"brainforge.rag": {"documents": DOCUMENTS}},
)
return transaction

Expand All @@ -73,16 +73,16 @@ async def test_rewrites_response_via_openai_endpoint(self):
step, "_complete",
new=AsyncMock(return_value="See [1] and [3]."),
) as complete:
revised = await step.process(transaction)
await step.process(transaction)

build.assert_awaited_once()
complete.assert_awaited_once_with(
{"messages": []}, transaction.request.model, 30.0)
self.assertIsNotNone(revised)
self.assertNotEqual(revised.response, "See the docs.")
self.assertEqual(revised.history[-1][0], "llm")
self.assertEqual(revised.history[-1][1], revised.response)
self.assertIn("Doc A", transaction.response_step_context["neon.citations"]["credits"])
self.assertNotEqual(transaction.response.response, "See the docs.")
self.assertEqual(transaction.response.history[-1][0], "llm")
self.assertEqual(
transaction.response.history[-1][1], transaction.response.response)
self.assertIn("Doc A", transaction.response_step_context["brainforge.citations"]["credits"])

async def test_skips_without_rag_documents(self):
step = ResponseParser(STEP_CONFIG)
Expand Down Expand Up @@ -116,12 +116,11 @@ async def test_completion_failure_leaves_response(self):
step, "_complete",
new=AsyncMock(side_effect=RuntimeError("nope")),
):
revised = await step.process(transaction)
await step.process(transaction)

self.assertIsNone(revised)
self.assertEqual(transaction.response.response, original)
self.assertEqual(
transaction.response_step_context["neon.citations"]["error"], "nope")
transaction.response_step_context["brainforge.citations"]["error"], "nope")

def test_appends_chat_completions_to_base_url(self):
step = ResponseParser({
Expand Down
Loading