Skip to content

Add File Loading Utilities for Agent Instructions #565

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
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,7 @@ cython_debug/
# PyPI configuration file
.pypirc
.aider*

# AI Assistant Ignores
CLAUDE.md
.codex/
41 changes: 41 additions & 0 deletions examples/basic/file_instructions_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Example: Loading agent instructions from a text file using load_instructions_from_file.
"""
import asyncio
from pathlib import Path

from pydantic import BaseModel

from agents import Agent, Runner
from agents.extensions.file_utils import load_instructions_from_file


# Define expected output schema
class Greeting(BaseModel):
greeting: str
greeting_spanish: str


async def main():
# Locate and load instructions from file
inst_path = Path(__file__).parent / "greet_instructions.txt"
instructions = load_instructions_from_file(str(inst_path))

# Create agent with file-based instructions
greeter = Agent(
name="Greeting Agent",
instructions=instructions,
output_type=Greeting,
)

# Prompt user for name and run
name = input("Enter your name: ")
result = await Runner.run(greeter, name)

# result.final_output is parsed into Greeting model
print("JSON output:", result.final_output.model_dump_json())
print("Greeting message:", result.final_output)


if __name__ == "__main__":
asyncio.run(main())
8 changes: 8 additions & 0 deletions examples/basic/greet_instructions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Instructions for GreetingAgent

Given an input string that represents a person's name, generate a JSON object with keys "greeting" and "greeting_spanish" whose value is a polite greeting message, formatted as:

{
"greeting": "Hello, <name>!",
"greeting_spanish": "Hola, <name>!"
}
61 changes: 61 additions & 0 deletions src/agents/extensions/file_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Helper utilities for file-based operations, e.g. loading instruction text files.
"""

from collections.abc import Sequence
from pathlib import Path


class InstructionFileError(Exception):
"""Base exception for load_instructions_from_file errors."""

class InstructionFileNotFoundError(InstructionFileError):
"""Raised when the file does not exist or is not a file."""

class InvalidFileTypeError(InstructionFileError):
"""Raised when the file extension is not allowed."""

class FileTooLargeError(InstructionFileError):
"""Raised when the file size exceeds the maximum allowed."""

def load_instructions_from_file(
path: str,
encoding: str = "utf-8",
allowed_extensions: Sequence[str] = (".txt", ".md"),
max_size_bytes: int = 1 * 1024 * 1024,
) -> str:
"""Load a text file with strict validations and return its contents.

Args:
path: Path to the instruction file.
encoding: File encoding (defaults to 'utf-8').
allowed_extensions: Tuple of allowed file extensions.
max_size_bytes: Maximum allowed file size in bytes.

Returns:
The file contents as a string.

Raises:
InstructionFileNotFoundError: if the file does not exist or is not a file.
InvalidFileTypeError: if the file extension is not in allowed_extensions.
FileTooLargeError: if the file size exceeds max_size_bytes.
InstructionFileError: for IO or decoding errors.
"""
file_path = Path(path)
if not file_path.is_file():
raise InstructionFileNotFoundError(f"File not found or is not a file: {file_path}")
if file_path.suffix.lower() not in allowed_extensions:
raise InvalidFileTypeError(
f"Invalid file extension {file_path.suffix!r}, allowed: {allowed_extensions}"
)
size = file_path.stat().st_size
if size > max_size_bytes:
raise FileTooLargeError(
f"File size {size} exceeds maximum {max_size_bytes} bytes"
)
try:
return file_path.read_text(encoding=encoding)
except UnicodeDecodeError as e:
raise InstructionFileError(f"Could not decode file {file_path}: {e}") from e
except OSError as e:
raise InstructionFileError(f"Error reading file {file_path}: {e}") from e
49 changes: 49 additions & 0 deletions tests/test_file_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

import pytest

from agents.extensions.file_utils import (
FileTooLargeError,
InstructionFileError,
InstructionFileNotFoundError,
InvalidFileTypeError,
load_instructions_from_file,
)


def test_successful_read(tmp_path):
file = tmp_path / "example.txt"
content = "This is a test."
file.write_text(content, encoding="utf-8")
result = load_instructions_from_file(str(file))
assert result == content


def test_file_not_found(tmp_path):
file = tmp_path / "nonexistent.txt"
with pytest.raises(InstructionFileNotFoundError):
load_instructions_from_file(str(file))


def test_invalid_extension(tmp_path):
file = tmp_path / "example.bin"
file.write_text("data", encoding="utf-8")
with pytest.raises(InvalidFileTypeError) as exc:
load_instructions_from_file(str(file))
assert ".bin" in str(exc.value)


def test_file_too_large(tmp_path):
file = tmp_path / "example.txt"
content = "a" * 20
file.write_text(content, encoding="utf-8")
with pytest.raises(FileTooLargeError) as exc:
load_instructions_from_file(str(file), max_size_bytes=10)
assert "exceeds maximum" in str(exc.value)


def test_decode_error(tmp_path):
file = tmp_path / "example.txt"
file.write_bytes(b'\xff\xfe\xfd')
with pytest.raises(InstructionFileError) as exc:
load_instructions_from_file(str(file), encoding="utf-8")
assert "Could not decode file" in str(exc.value)