-
Notifications
You must be signed in to change notification settings - Fork 122
feat: introduce post processors #821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mackurzawa
wants to merge
22
commits into
develop
Choose a base branch
from
mk/introduce-post-processors
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
31474e7
feat: introduce post processors
mackurzawa e52daa2
Merge branch 'main' into mk/introduce-post-processors
mackurzawa 671d6d9
fix
mackurzawa b25e4f2
fix: update changelog
mackurzawa 30921b0
Merge branch 'develop' into mk/introduce-post-processors
mackurzawa c7bb863
rebase
mackurzawa 3d7a5c5
test: add tests for post processors
mackurzawa a0e2c85
docs: add post-processor example and update examples readme
mackurzawa 5fea55e
lint
mackurzawa 3f35fb6
minor fixes
mackurzawa a4f1efe
fix anext
mackurzawa 8ac13d0
merge develop
mackurzawa 51fe3da
use already existing generic types
mackurzawa b46e2bc
separate streaming and non-streaming to two classes
mackurzawa f90a408
add docs about postprocessors
mackurzawa 54b2cbb
add info to mkdocs
mackurzawa 327e063
change NonStreamingPostProcessor to PostProcessor
mackurzawa fcbdbe3
add links to class definitions in docs
mackurzawa 3765c4b
change model to 4.1-mini
mackurzawa 97756d3
update example file
mackurzawa a1bfd27
update types
mackurzawa 1809e65
stream_with_processing in post_processors
mackurzawa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# How-To: Use Post-Processors with Ragbits Agents | ||
|
||
Ragbits Agents can be enhanced with post-processors to intercept, log, filter, and modify their outputs. This guide explains how to implement and use post-processors to customize agent responses. | ||
|
||
## Post-Processors Overview | ||
|
||
Ragbits provides two types of post-processors: | ||
|
||
- **StreamingPostProcessor**: Processes outputs as they are generated, suitable for real-time applications. | ||
- **NonStreamingPostProcessor**: Processes the final output after generation, ideal for batch processing. | ||
|
||
### Implementing a Post-Processor | ||
|
||
To create a post-processor, inherit from the appropriate base class (`StreamingPostProcessor` or `NonStreamingPostProcessor`) and implement the required method. | ||
mackurzawa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### Streaming Post-Processor Example | ||
|
||
A streaming post-processor can manipulate all information returned during generation, including text, tool calls, etc. | ||
|
||
```python | ||
class UpperCaseStreamingProcessor(StreamingPostProcessor): | ||
async def process_streaming(self, chunk, agent): | ||
if isinstance(chunk, str): | ||
return chunk.upper() | ||
return chunk | ||
``` | ||
|
||
#### Non-Streaming Post-Processor Example | ||
|
||
A non-streaming post-processor applies transformations after the entire content is generated. | ||
|
||
```python | ||
class TruncateNonStreamingProcessor(NonStreamingPostProcessor): | ||
def __init__(self, max_length: int = 50) -> None: | ||
self.max_length = max_length | ||
|
||
async def process(self, result, agent): | ||
content = result.content | ||
if len(content) > self.max_length: | ||
content = content[:self.max_length] + "... [TRUNCATED]" | ||
result.content = content | ||
return result | ||
``` | ||
|
||
## Using Post-Processors | ||
|
||
To use post-processors, pass them to the `run` or `run_streaming` methods of the `Agent` class. If you pass a non-streaming processor to `run_streaming`, set `allow_non_streaming=True`. This allows streaming processors to handle content piece by piece during generation, while non-streaming processors apply transformations after the entire output is generated. | ||
|
||
```python | ||
async def main() -> None: | ||
llm = LiteLLM("gpt-3.5-turbo") | ||
agent = Agent(llm=llm, prompt="You are a helpful assistant.") | ||
post_processors = [ | ||
UpperCaseStreamingProcessor(), | ||
TruncateNonStreamingProcessor(max_length=50), | ||
] | ||
stream_result = agent.run_streaming("Tell me about the history of AI.", post_processors=post_processors, allow_non_streaming=True) | ||
async for chunk in stream_result: | ||
if isinstance(chunk, str): | ||
print(chunk, end="") | ||
print(f"\nFinal answer:\n{stream_result.content}") | ||
``` | ||
|
||
Post-processors offer a flexible way to tailor agent outputs, whether filtering content in real-time or transforming final outputs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
""" | ||
Ragbits Agents Example: Post-Processors | ||
|
||
This example demonstrates how to use post-processors with Agent.run() and Agent.run_streaming() methods. | ||
|
||
To run the script, execute the following command: | ||
|
||
```bash | ||
uv run examples/agents/post_processors.py | ||
``` | ||
""" | ||
|
||
# /// script | ||
# requires-python = ">=3.10" | ||
# dependencies = [ | ||
# "ragbits-core", | ||
# "ragbits-agents", | ||
# ] | ||
# /// | ||
|
||
import asyncio | ||
from types import SimpleNamespace | ||
|
||
from ragbits.agents import Agent, AgentResult, NonStreamingPostProcessor, StreamingPostProcessor, ToolCallResult | ||
from ragbits.core.llms.base import BasePrompt, ToolCall, Usage | ||
from ragbits.core.llms.litellm import LiteLLM | ||
|
||
|
||
class CustomStreamingProcessor(StreamingPostProcessor): | ||
""" | ||
Streaming post-processor that checks for forbidden words. | ||
""" | ||
|
||
def __init__(self, forbidden_words: list[str]) -> None: | ||
self.forbidden_words = forbidden_words | ||
|
||
async def process_streaming( | ||
self, chunk: str | ToolCall | ToolCallResult | SimpleNamespace | BasePrompt | Usage, agent: "Agent" | ||
) -> str | ToolCall | ToolCallResult | SimpleNamespace | BasePrompt | Usage: | ||
""" | ||
Process chunks during streaming. | ||
""" | ||
if isinstance(chunk, str) and chunk.lower().strip() in self.forbidden_words: | ||
return "[FORBIDDEN_WORD]" | ||
return chunk | ||
|
||
|
||
class CustomNonStreamingProcessor(NonStreamingPostProcessor): | ||
""" | ||
Non-streaming post-processor that truncates the content. | ||
""" | ||
|
||
def __init__(self, max_length: int = 200) -> None: | ||
self.max_length = max_length | ||
|
||
async def process(self, result: "AgentResult", agent: "Agent") -> "AgentResult": | ||
mackurzawa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Process the agent result. | ||
""" | ||
content = result.content | ||
content_length = len(content) | ||
|
||
if content_length > self.max_length: | ||
content = content[: self.max_length] | ||
content += f"... [TRUNCATED] ({content_length} > {self.max_length} chars)" | ||
|
||
return AgentResult( | ||
content=content, | ||
metadata=result.metadata, | ||
tool_calls=result.tool_calls, | ||
history=result.history, | ||
usage=result.usage, | ||
) | ||
|
||
|
||
async def main() -> None: | ||
""" | ||
Run the example. | ||
""" | ||
llm = LiteLLM("gpt-3.5-turbo") | ||
mackurzawa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
agent = Agent(llm=llm, prompt="You are a helpful assistant.") | ||
post_processors = [ | ||
CustomStreamingProcessor(forbidden_words=["python"]), | ||
CustomNonStreamingProcessor(max_length=200), | ||
] | ||
stream_result = agent.run_streaming("What is Python?", post_processors=post_processors, allow_non_streaming=True) | ||
mackurzawa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
async for chunk in stream_result: | ||
if isinstance(chunk, str): | ||
print(chunk, end="") | ||
print(f"\nFinal answer:\n{stream_result.content}") | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.