A backend-agnostic, modular AI orchestration framework for building LLM-powered pipelines with functional building blocks
Project description
🤖 Functional AI
A backend-agnostic, modular AI orchestration framework for building LLM-powered pipelines with functional building blocks.
Features
- 🧩 Composable Agents – Every unit of logic is a callable class with clearly defined inputs and outputs
- 🔁 Looping, Caching, Sequencing – Functional-style orchestration with built-in blocks
- 🎯 Guardrails and Validation – Input/output validation, retry logic, and error handling
- 🚀 Parallel Execution – Run independent steps concurrently to speed up pipelines
- 🧪 Isolated Testing – Each step is easy to test with predictable inputs, no side effects
- 🛠 Tool Support – Built-in support for tools and MCP extensions with no boilerplate
- 📦 Backend-agnostic – Compatible with any LLM backend supporting agent-style execution
Example Usage
Using the library requires only a minimal setup. You define your pipeline using simple functional components, then invoke it with a request:
import src.fai as fai
def cat_file(file_path: str, page: int) -> dict:
...
def query_wiki(query: str) -> dict:
...
def sub_agent_template(question: str, chat_history: List[str]) -> str:
return (PromptBuilder()
.file('static/agent_prompt').dash()
.text("Available files in ../operators").tab()
.text(', '.join(file_list_operators)).back().dash()
.text("Available backends in ../backends").tab()
.text(', '.join(file_list_backends)).back().dash()
.chat(chat_history).dash()
.text(f"Question: {question}").dash()
.prompt)
def main_agent_template(chat_history: List[str],
file_agent: str, backend_agent: str, general_agent: str) -> str:
return (PromptBuilder()
.file('static/agent_prompt').dash()
.text("Available files in ../operators").tab()
.text(', '.join(file_list_operators)).back().dash()
.text("Available backends in ../backends").tab()
.text(', '.join(file_list_backends)).back().dash()
.text(f"File agent response:\n{file_agent}").dash()
.text(f"Backend agent response:\n{backend_agent}").dash()
.text(f"General agent response:\n{general_agent}").dash()
.chat(chat_history)
.prompt)
def create_sub_agent(question: str, key: str) -> fai.Agent:
return fai.retry( # Retry on failure up to 3 times by default
fai.ai_agent( # Create a Re-Act style AI agent with tools
template=lambda chat_history: sub_agent_template(question, chat_history),
tools=[cat_file, query_wiki],
key=key))
fai_agent = fai.ai_parallel( # Run parallel research with multiple agents
template=main_agent_template, # Combine the results from subagents
agents=[
create_sub_agent("Find the file related to users' question", key="file_agent"),
create_sub_agent("Find the backend related to users' question", key="backend_agent"),
create_sub_agent("Find general information related to user's question", key="general_agent")
])
fai_chat = fai.cache( # Cache and reuse the result of this chat
fai.ai_summarize( # Summarize the chat at the end
fai.ai_chat( # Chat with the user until they say '!done'
agent=fai_agent, output_llm=print_llm_blue, input_user=input)))
fai_german = fai.ai_transform( # Transform the cached result
template=lambda it: f"Translate to German:\n\n{it}", agent=fai_chat)
fai_chinese = fai.ai_transform(
template=lambda it: f"Translate to Chinese:\n\n{it}", agent=fai_chat)
fai_russian = fai.ai_transform(
template=lambda it: f"Translate to Russian:\n\n{it}", agent=fai_chat)
if __name__ == '__main__':
print_success_green(f"German: {fai_german()}")
print_success_green(f"Chinese: {fai_chinese()}")
print_success_green(f"Russian: {fai_russian()}")
Architecture Overview
At the core of the library is the concept of an Agent: a minimal ReAct-style component that defines a callable unit of computation. Every block — from single prompt calls to multi-step chains — extends this base. It provides a consistent interface for execution while remaining lightweight and composable.
Each Agent has a unique key, which determines where its result is stored in the pipeline’s shared state. When a component runs, its output is inserted into the kwargs dictionary under this key. This mechanism allows downstream targets to reference the output of upstream ones by name, keeping state passing explicit and traceable.
State is passed between all these components through keyword arguments (**kwargs). Each step reads what it needs and adds its own result, keeping everything side-effect-free and easy to follow.
Rather than relying on a graph or node-based model, the architecture favors flat, functional composition. This choice makes it easier to reason about flow, debug intermediate outputs, and write unit tests. Since targets are pure functions, they can be reused freely across pipelines without worrying about shared state or lifecycle side effects.
The result is a system where each block does one thing well, and all parts — from prompt execution to synthesis — are modular, testable, and extensible.
Backend Support
A backend in this library provides the runtime environment for LLM execution. It is responsible for setting up sessions, managing tool access, configuring the model, and executing prompts.
The default backend is GoogleAdkBackend, which wraps a ReAct-style agent using the Google Assistant Developer Kit (ADK). It supports streaming output, tool usage, and agent configuration (e.g., instructions, description, tool restrictions).
However, the system is backend-agnostic by design. You can support OpenAI, Anthropic, or even local LLMs by implementing your own backend class and swapping it in:
def get_backend():
return MyCustomBackend()
As long as the backend exposes create_runner() and call_agent(), it can be plugged into the rest of the system without modifying any functional components. This makes it easy to port the entire agent pipeline across platforms or environments.
Contributions Welcome
Community contributions are welcome — especially around tool extensions, backend integrations, and improving developer ergonomics. Pull requests, feature suggestions, and experiments are all encouraged. If you're building your own research or agent pipelines, feel free to fork and adapt.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fun_ai_lib-0.2.0.tar.gz.
File metadata
- Download URL: fun_ai_lib-0.2.0.tar.gz
- Upload date:
- Size: 17.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
699c03d371e4bb4de804ba25a8fb14ccf0b66068fd6b357927b5f1ce68cffc18
|
|
| MD5 |
f84ee55ba731b5e0f1dab981e6c4b7fa
|
|
| BLAKE2b-256 |
cbb8ed971a5b246569ffe1c0b0ce6670220b0439ff449043a99e5bb0ed220f34
|
File details
Details for the file fun_ai_lib-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fun_ai_lib-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0ba31fa0b66fa789c3aeca2005fde90f0835b7c1ea8513cbcdd4ed3fc3ad79a
|
|
| MD5 |
0102b56e15191b773eed2a5f7ce6d90e
|
|
| BLAKE2b-256 |
3d56a0d97d2e456ecace6749b57bcdccd6dd2da8a54a684d1e7b48dfc6735119
|