diff --git a/.github/workflows/propose_release.yml b/.github/workflows/propose_release.yml index 1ba741b..96a11dc 100644 --- a/.github/workflows/propose_release.yml +++ b/.github/workflows/propose_release.yml @@ -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 }} diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 4901b63..e4b3fa3 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -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 diff --git a/.github/workflows/publish_test_build.yml b/.github/workflows/publish_test_build.yml index 354dc3a..7f30b69 100644 --- a/.github/workflows/publish_test_build.yml +++ b/.github/workflows/publish_test_build.yml @@ -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 diff --git a/README.md b/README.md index d89e7f4..bfc7329 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/neon_inference_plugin_rag/__init__.py b/brainforge_inference_plugin_rag/__init__.py similarity index 89% rename from neon_inference_plugin_rag/__init__.py rename to brainforge_inference_plugin_rag/__init__.py index 3047588..e42fc6b 100644 --- a/neon_inference_plugin_rag/__init__.py +++ b/brainforge_inference_plugin_rag/__init__.py @@ -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", diff --git a/neon_inference_plugin_rag/rag.py b/brainforge_inference_plugin_rag/rag.py similarity index 96% rename from neon_inference_plugin_rag/rag.py rename to brainforge_inference_plugin_rag/rag.py index dcd4400..648032a 100644 --- a/neon_inference_plugin_rag/rag.py +++ b/brainforge_inference_plugin_rag/rag.py @@ -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`. @@ -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 ( @@ -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 {}) @@ -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", @@ -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 @@ -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( @@ -310,7 +307,7 @@ async def process( "documents": documents, "error": str(exc), } - return None + return transaction.response_step_context[self.id] = { "documents": filtered, @@ -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 diff --git a/neon_inference_plugin_rag/version.py b/brainforge_inference_plugin_rag/version.py similarity index 100% rename from neon_inference_plugin_rag/version.py rename to brainforge_inference_plugin_rag/version.py diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 934998b..dc4e5ae 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,2 +1,2 @@ -brainforge-inference-utils @ git+https://github.com/NeonGeckoCom/brainforge-inference-utils +brainforge-inference-utils~=0.0,>=0.0.1a1 requests diff --git a/setup.py b/setup.py index 899e7e3..65d9abb 100644 --- a/setup.py +++ b/setup.py @@ -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: @@ -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", @@ -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", ], }, ) diff --git a/tests/test_entry_point.py b/tests/test_entry_point.py index 63de3b7..8000e78 100644 --- a/tests/test_entry_point.py +++ b/tests/test_entry_point.py @@ -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" diff --git a/tests/test_rag.py b/tests/test_rag.py index b6e07bc..52b5db0 100644 --- a/tests/test_rag.py +++ b/tests/test_rag.py @@ -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 @@ -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) @@ -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) @@ -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) diff --git a/tests/test_response_parser.py b/tests/test_response_parser.py index 07c3052..f7a24e9 100644 --- a/tests/test_response_parser.py +++ b/tests/test_response_parser.py @@ -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, ) @@ -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 @@ -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) @@ -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({