Skip to main content

A flexible AI agent library with tool support

Project description

Fury Logo

Fury

Discord Tests

A flexible and powerful AI agent library for Python, designed to build agents with tool support, multimodal capabilities, and streaming responses.

[Breaking Change ⚠️] In 0.2.0 the response deltas have changed to allow for easier tool call persistence in downstream apps.

Features

  • ** Durable Memory**: Persist named memory scopes, bind them to individual agents, and expose an airgapped memory tool.
  • Interruption and early stopping: Agents now use the Runner pattern, allowing them to be interrupted or stopped mid-generation.
  • Tool Support: Define and register custom tools (functions) that the agent can execute and parallel tool execution support.
  • Image and Voice inputs: Support for image and voice inputs, plus standalone speech-to-text via SpeechToText.
  • Text-to-Speech (TTS): Generate audio with NeuTTS via standalone TextToSpeech or Agent.speak().
  • History Management: Use HistoryManager for auto-compaction support or StaticHistoryManager for strict fixed-size context trimming.

Installation

Install with uv:

uv add fury-sdk

Quick Start

from fury import Agent

agent = Agent(
    model="unsloth/GLM-4.6V-Flash-GGUF:Q8_0",
    system_prompt="You are a helpful assistant.",
)

print(agent.ask("Hello!", history=[]))
print(agent.ask("Hello!", history=[], model="another-model"))

Other examples:

Speech-to-Text

If you only need transcription, you do not need to initialize an Agent.

from fury import SpeechToText

stt = SpeechToText()

# Accepts a base64-encoded audio string, bytes, a file-like object, or a path.
transcript = stt.transcribe("...")
print(transcript)

HistoryManager.add_voice(...) still uses the same Faster Whisper path under the hood, but SpeechToText exposes it directly for standalone voice workflows.

Text-to-Speech

If you only need synthesis, you do not need to initialize an Agent.

from fury import TextToSpeech

tts = TextToSpeech()
audio_chunks = tts.speak(
    text="Hello from Fury!",
    ref_text="Welcome home sir.",
    ref_audio_path="./examples/resources/ref.wav",
)

Agent.speak(...) still uses the same NeuTTS path under the hood, but TextToSpeech exposes it directly for standalone audio generation.

History Management

Fury makes managing history limits easy by providing simple, built-in history managers. They are just list managers that monitor context utilization and trim or compact your list accordingly.

The standard HistoryManager will auto-compact your history as you add messages to it (summarise using an Agent) in a similar way to Claude Code, Codex and Pi.

from fury import HistoryManager

history_manager = HistoryManager(agent=agent)

# Add something to history like this:
await history_manager.add({"role": "user", "content": user_input})

# Use the history like this:
transcript = []
runner = agent.runner()
async for event in runner.chat(history_manager.history):
    if event.history_delta:
        transcript.append(event.history_delta.message)
await history_manager.extend(transcript)

See examples/chat.py for a full working example.

To persist the raw transcript as JSONL and reload it on startup:

history_manager = HistoryManager(
    agent,
    persist_to_disk=True,
    session_id="demo-session",
)

This stores the session under .fury/history/ and rehydrates history_manager.history from the existing file if the session already exists. See examples/persistent_chat.py for a runnable example. If auto_compact=True, a reloaded session is compacted in memory on the first async add() or extend() call, while the JSONL file remains a full raw transcript. When compaction runs, Fury prints a short [history] Compacting ... notice by default. Managed image history is lightweight by default: HistoryManager.add_image(...) stores a [The user shared an image] placeholder plus path metadata instead of embedding raw base64 in saved history. Set save_images_to_history=True to keep the full image payload in history.

Managed messages get stable id fields. Use edit_message(), delete_message(), regenerate_message(), and set_variant() to update history and switch between regenerated response variants. See docs/history_manager.md for details.

If you do not want auto-compaction and a hard history limit, use StaticHistoryManager:

from fury import StaticHistoryManager

history_manager = StaticHistoryManager(
    target_context_length=4096,
    history=[{"role": "system", "content": "You are helpful."}],
)

It keeps only the newest messages that fit in the target context length. See docs/example.md for a complete example.

Durable Memory

Fury can persist durable memory outside the current chat history using explicit named scopes. Pass memory_scope when constructing an agent and Fury will:

  • create or reuse a MemoryStore
  • inject the latest memory snapshot for that scope into the system prompt
  • register a memory tool bound only to that scope
from fury import Agent

agent = Agent(
    model="your-model-name",
    system_prompt="You are a helpful assistant.",
    memory_scope="my-project",
)

To share one backing store across multiple airgapped agents:

from fury import Agent, MemoryStore

store = MemoryStore(".fury/memory")

alpha = Agent(
    model="your-model-name",
    system_prompt="You help project alpha.",
    memory_store=store,
    memory_scope="project-alpha",
)

beta = Agent(
    model="your-model-name",
    system_prompt="You help project beta.",
    memory_store=store,
    memory_scope="project-beta",
)

See docs/memory.md for the full API.

Configuration Options

agent = Agent(
    model="your-model-name",
    system_prompt="You are a helpful assistant.",
    parallel_tool_calls=False,
    disable_stt=False,
    disable_tts=False,
    generation_params={
        "temperature": 0.2,
        "max_tokens": 512,
    },
)

# Disable reasoning stream content (default is False)
runner = agent.runner()
async for event in runner.chat(history, reasoning=False):
    ...

Persisting Transcripts

Fury separates UI stream events from model-visible transcript events. Persist event.history_delta.message to save the exact OpenAI-compatible messages Fury used for tool calls, tool results, multimodal follow-ups, and final assistant replies.

transcript = []

async for event in agent.runner().chat(history):
    if event.content:
        print(event.content, end="")
    if event.history_delta:
        transcript.append(event.history_delta.message)

save_messages(transcript)

For non-streaming collection:

result = await agent.runner().complete(history)
save_messages(result.transcript)
print(result.content)

Defining Tools

You can give your agent tools to interact with the world. Tools are defined using the create_tool helper.

Input and output schemas help the model to correctly pass parameters through to the function. Fury will automatically prune any hallucinated parameters not defined in the input schema.

Learn more in the OpenAI guide

from fury import Agent, create_tool


def add(a: int, b: int):
    return {"result": a + b}

# Create the tool
add_tool = create_tool(
    id="add",
    description="Add two numbers together",
    execute=add,
    input_schema={
        "type": "object",
        "properties": {
            "a": {"type": "integer"},
            "b": {"type": "integer"},
        },
        "required": ["a", "b"],
    },
    output_schema={
        "type": "object",
        "properties": {"result": {"type": "integer"}},
        "required": ["result"],
    },
)

# Pass to agent
agent = Agent(..., tools=[add_tool])

If your tool accepts an emit parameter, Fury injects a runtime-only callback during execution so the tool can stream structured UI events during tool execution.

def search(query: str, emit):
    emit({"id": "search-1", "title": f"Searching for {query}", "type": "tool_call"})
    return {"query": query}

These arrive in the chat stream as event.tool_ui, separate from event.tool_call.

Coding Assistant Example

Check out examples/coding-assistant/coding_assistant.py for a full-featured example that includes:

  • Tools: File system operations (read, write, edit, bash).
  • Skills System: Loading specialized capabilities from SKILL.md files.
  • Memory System: Using durable memory plus SOUL.md for project context.
  • History Manager: Uses HistoryManager to summarize long conversations and save context window.

Build something neat.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fury_sdk-0.2.1.tar.gz (781.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fury_sdk-0.2.1-py3-none-any.whl (45.0 kB view details)

Uploaded Python 3

File details

Details for the file fury_sdk-0.2.1.tar.gz.

File metadata

  • Download URL: fury_sdk-0.2.1.tar.gz
  • Upload date:
  • Size: 781.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for fury_sdk-0.2.1.tar.gz
Algorithm Hash digest
SHA256 ad62aba2f95103b67f79f70fac77d69400fc0b7daf8c1939eea0ebb319db33b7
MD5 b0975be001224c49b480615194db563b
BLAKE2b-256 476d0d4a0f93e5bdf86f2780cb924fe8ca187cd7f40fbe24ccb1a036b9b150d8

See more details on using hashes here.

File details

Details for the file fury_sdk-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: fury_sdk-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 45.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for fury_sdk-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 11859820a63ebe2a325125f93dc0aec233e332abd91156b6a69b09532ad70c6b
MD5 d67ff732a1f09f9b530526ca5ac01cbb
BLAKE2b-256 b37c4626f9c7038e47cf81eef09816843c8aa1790e48893e3fe329e7cc9ec154

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page