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
35 changes: 35 additions & 0 deletions .github/scripts/check_examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

# Check that all example Python files are documented in examples/README.md

EXAMPLES_DIR="examples"
README="examples/README.md"

MISSING_README=()

while IFS= read -r -d '' file; do
rel_path="${file#./}"
readme_path="/${rel_path}"

if ! grep -qF "$readme_path" "$README"; then
MISSING_README+=("$readme_path")
fi
done < <(find "$EXAMPLES_DIR" -name "*.py" -print0 | sort -z)

FAILED=0

if [ ${#MISSING_README[@]} -gt 0 ]; then
echo "ERROR: The following example files are not documented in $README:"
for f in "${MISSING_README[@]}"; do
echo " - $f"
done
echo ""
echo "Please add entries for these files to the table in $README."
FAILED=1
fi

if [ $FAILED -eq 1 ]; then
exit 1
fi

echo "All example files are documented in $README."
4 changes: 4 additions & 0 deletions .github/workflows/shared-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ jobs:
run: |
uv run ./check_licenses.sh
- name: Check all examples are documented in README
run: |
bash .github/scripts/check_examples.sh
- name: Check documentation builds correctly
run: |
uv run mkdocs build --strict
Expand Down
125 changes: 88 additions & 37 deletions examples/README.md

Large diffs are not rendered by default.

19 changes: 15 additions & 4 deletions examples/agents/cli_agent.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
"""Simple test agent for CLI testing.
run:
"""
Ragbits Agents Example: CLI agent
uv run ragbits agents run examples.agents.test_cli_agent:test_agent
This example demonstrates how to expose an agent via the ragbits CLI, allowing
it to be run interactively from the command line with conversation history.
To expose the CLI Agent simply run:
```bash
uv run ragbits agents run examples.agents.cli_agent:test_agent
```
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-agents",
# ]
# ///

from ragbits.agents import Agent
from ragbits.core.llms import LiteLLM

Expand Down
20 changes: 20 additions & 0 deletions examples/agents/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
"""
Ragbits Agents Example: Dependencies
This example demonstrates how to use AgentContext and AgentDependencies
To run the script, execute the following command:
```bash
uv run examples/agents/dependencies.py
```
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-core",
# "ragbits-agents",
# ]
# ///

from dataclasses import dataclass

from ragbits.agents import Agent, AgentRunContext
Expand Down
20 changes: 17 additions & 3 deletions examples/agents/hooks/agent_output_logging.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
"""
Example demonstrating a post-tool hook for logging outputs from agent tools.
Ragbits Agents Example: Post-tool hook for agent output logging

This shows how to log the output returned by downstream agent tools.
Setup: 1 parent agent with 2 expert child agents as tools.
This example demonstrates how to use a POST_TOOL hook to log outputs returned
by downstream agent tools. The setup consists of a parent agent with two expert
child agents (diet and fitness) registered as tools.

To execute this script simply run:
```bash
uv run examples/agents/hooks/agent_output_logging.py
```
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-core",
# "ragbits-agents",
# ]
# ///

import asyncio

from ragbits.agents import Agent
Expand Down
22 changes: 16 additions & 6 deletions examples/agents/hooks/guardrails_integration.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
"""
Example demonstrating guardrails integration using pre-run and on-event hooks.
Ragbits Agents Example: Guardrails integration with hooks

This example shows how to use the ragbits Guardrail system with agent hooks
to validate inputs before the agent processes them and how to use ON_EVENT
hooks to transform streaming output in real-time.
This example demonstrates how to use the ragbits Guardrail system with agent hooks
to validate inputs before the agent processes them, and how to use ON_EVENT hooks
to transform streaming output in real-time.

To run this example, execute:
uv run python examples/agents/hooks/guardrails_integration.py
To execute this script simply run:
```bash
uv run examples/agents/hooks/guardrails_integration.py
```
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-agents",
# "ragbits-guardrails",
# ]
# ///

import asyncio

from ragbits.agents import Agent
Expand Down
21 changes: 16 additions & 5 deletions examples/agents/hooks/validation_and_sanitization.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
"""
Example demonstrating hooks for input validation, sanitization, and output masking.
Ragbits Agents Example: Input validation, sanitization, and output masking with hooks

This example shows a customer support agent with hooks that:
- Validate email addresses before sending
- Sanitize email domains to approved list
- Mask sensitive user data in responses
This example demonstrates a customer support agent with PRE_TOOL and POST_TOOL hooks that
validate email addresses before sending, sanitize email domains to an approved list,
and mask sensitive user data in tool responses.

To execute this script simply run:
```bash
uv run examples/agents/hooks/validation_and_sanitization.py
```
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-agents",
# ]
# ///

import asyncio
import re
from typing import Any
Expand Down
8 changes: 8 additions & 0 deletions examples/chat/file_explorer_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
ragbits api run examples.chat.file_explorer_agent:FileExplorerChat
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-chat",
# "ragbits-agents",
# ]
# ///

import json
import os
from collections.abc import AsyncGenerator
Expand Down
7 changes: 7 additions & 0 deletions examples/chat/themed_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
That's it! No manual conversion needed!
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-chat",
# ]
# ///

from collections.abc import AsyncGenerator

from pydantic import BaseModel
Expand Down
7 changes: 7 additions & 0 deletions examples/chat/upload_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
```
"""

# /// script
# requires-python = ">=3.10"
# dependencies = [
# "ragbits-chat",
# ]
# ///

from collections.abc import AsyncGenerator

from fastapi import UploadFile
Expand Down
25 changes: 8 additions & 17 deletions packages/ragbits-agents/src/ragbits/agents/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
from types import ModuleType, SimpleNamespace
from typing import Any, ClassVar, Generic, TypeVar, Union, cast, overload

from pydantic import (
BaseModel,
Field,
)
from pydantic import BaseModel, Field, PrivateAttr
from typing_extensions import Self

from ragbits import agents
Expand Down Expand Up @@ -145,26 +142,20 @@ class AgentDependencies(BaseModel, Generic[DepsT]):

model_config = {"arbitrary_types_allowed": True}

_frozen: bool
_value: DepsT | None
_frozen: bool = PrivateAttr(default=False)
_value: DepsT | None = PrivateAttr(default=None)

def __init__(self, value: DepsT | None = None) -> None:
super().__init__()
self._value = value
self._frozen = False
object.__setattr__(self, "_value", value)
object.__setattr__(self, "_frozen", False)

def __setattr__(self, name: str, value: object) -> None:
is_frozen = False
if name != "_frozen":
try:
is_frozen = object.__getattribute__(self, "_frozen")
except AttributeError:
is_frozen = False

is_frozen = object.__getattribute__(self, "_frozen")
if is_frozen and name not in {"_frozen"}:
raise RuntimeError("Dependencies are immutable after first access")

super().__setattr__(name, value)
object.__setattr__(self, name, value)

@property
def value(self) -> DepsT | None:
Expand All @@ -183,7 +174,7 @@ def _freeze(self) -> None:
def __getattr__(self, name: str) -> object:
value = object.__getattribute__(self, "_value")
if value is None:
raise AttributeError(name)
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
self._freeze()
return getattr(value, name)

Expand Down
Loading