Skip to main content

python agent for inference handling

Project description

Bottom AI CHANGELOG LICENSE

org.slashlib.py.agent

A highly decoupled, asynchronous framework for building AI agents in Python.

PyPI version PyPI-Test version License: MIT


Core Concept

This package provides a robust infrastructure to connect AI models (Inference Engines) with functional tools. The focus lies on Provider Agnosticism: The agent does not need to know whether it is communicating with Ollama, OpenAI, or a local model—it uses standardized adapters to ensure seamless integration.

Key Features

  • Asynchronous Core: Built on asyncio for non-blocking task execution.
  • Provider Agnostic: Easily swap the AI engine using the Adapter pattern.
  • Automatic Tool Schemas: Automatically transforms Python functions into JSON schemas for LLMs via decorators.
  • Multiton Pattern: Ensures unique agent instances by identifier, preventing redundant resource allocation.
  • Robust Exception Hierarchy: Clearly separates connection, configuration, and tool execution errors.
  • Plugin System: Discover and load inference adapters dynamically via Python entry points.

Installation

Install the package via pip:

pip install org.slashlib.py.agent

Configuration

The framework can automatically ingest default settings from a pyproject.json file located in your project root. This allows you to manage model parameters without changing your code.

pyproject.json:

{
  "name": "My App",
  "version": "0.0.1",
  "paths": {
    "ROOT":   "{ROOT}",
    "assets": "{ROOT}/assets"
  },
  "assets": {
    "logging": "{path.assets}/logging.json"
  },
  "plugins": {
    "my-inference-adapter": {
      "default-setting": "foo"
    }
  }
}

Quick Start

Setting up an agent with a tool and the Ollama adapter is straightforward:

import asyncio
from org.slashlib.py.inference.ollama import OllamaInferenceAdapter
from org.slashlib.py.agent import Agent, tool

# 1. Define a tool
@tool(description="Adds two numbers.")
async def add_numbers(a: int, b: int) -> int:
    return a + b

async def main():
    # 2. Configure Adapter and Agent
    adapter = OllamaInferenceAdapter()
    my_agent = Agent(
        identifier="MathExpert",
        tools=[add_numbers],
        adapter=adapter
    )

    # 3. Start task and retrieve result
    response = await my_agent.run(user_prompt="What is 123 + 456?")
    print(f"Response: {response.get_last_context()}")

if __name__ == "__main__":
    asyncio.run(main())

The @tool Decorator

The @tool decorator is the bridge between standard Python functions and AI logic. It transforms a function into an instance of the Tool class. This class automatically generates the metadata and JSON schemas required by Large Language Models (LLMs) like Gemma or Llama to understand how to interact with your code.

Key Features

  • Schema Generation: Automatically extracts the tool name, description (from docstrings), and parameter types (from type hints).
  • Flexible Invocation: Can be used with or without parentheses and optional parameters.
  • Execution Wrapper: Handles both synchronous and asynchronous functions, ensuring results are formatted as strings or JSON suitable for LLM context.
  • Type Mapping: Maps Python types (int, str, list, etc.) to their corresponding JSON schema types.

Usage Rules

  1. Flexible Decoration: The decorator can be used as @tool or @tool(). If parameters like name or description are omitted, they are automatically derived from the function's own name and its docstring.
  2. Type Hints are Mandatory: Use Python type hints (e.g., a: int, names: list). These are essential to build the "parameters" section of the JSON schema.
  3. Docstrings Matter: Unless an explicit description is provided in the decorator, the function's docstring serves as the tool's description. Be precise, as this is the "manual" the AI reads to decide when and how to use the tool.
  4. Metadata Fallback: If neither a decorator parameter nor a docstring is present, a generic placeholder is used for the description to ensure schema validity.

Examples: A Mathematical Tool

from org.slashlib.py.agent import tool

@tool
def add_numbers(a: int, b: int) -> int:
    """
    Adds two integers together and returns the sum.
    
    Args:
        a (int): The first number.
        b (int): The second number.
        
    Returns:
        int: The sum of a and b.
    """
    return a + b

# The function 'add_numbers' is now a Tool object and can be 
# passed directly to an Agent:
# my_agent = Agent(..., tools=[add_numbers])
from org.slashlib.py.agent import tool

@tool(name="sum_integers", description="Calculates the sum of two whole numbers.")
def add_numbers(a: int, b: int) -> int:
    """
    Adds two integers together and returns the sum.
    
    Args:
        a (int): The first number.
        b (int): The second number.
        
    Returns:
        int: The sum of a and b.
    """
    return a + b

# Explanation of metadata prioritization:
# ----------------------------------------
# In this scenario, the schema values are determined as follows:
#
# 1. Name: "sum_integers" 
#    -> Because the 'name' parameter is explicitly provided in the decorator, 
#       it overrides the actual function name 'add_numbers'.
#
# 2. Description: "Calculates the sum of two whole numbers."
#    -> The 'description' parameter in the decorator takes precedence. 
#       The function's docstring is ignored in this specific case.
from org.slashlib.py.agent import tool

@tool(name="sum_integers", description="Calculates the sum of two whole numbers.")
def add_numbers(a: int, b: int) -> int:
    return a + b

# Explanation of metadata prioritization (No Docstring):
# ------------------------------------------------------
# In this scenario, the schema values are determined as follows:
#
# 1. Name: "sum_integers" 
#    -> The 'name' parameter in the decorator is used, overriding the 
#       function name 'add_numbers'.
#
# 2. Description: "Calculates the sum of two whole numbers."
#    -> The 'description' parameter in the decorator is used. 
#       Since the function has no docstring, this is the only source 
#       of information for the tool's description.
#
# Note: If the 'description' parameter were also omitted, the Tool 
# class would fall back to a generic "No description provided." 
# because func.__doc__ is None.

Async Execution

The framework is built on Python's asyncio. The Agent.run method is an asynchronous coroutine. This allows your application to remain responsive or handle multiple agents concurrently.

Correct Async Flow

import asyncio
from org.slashlib.py.agent import Agent

async def main():
    # 1. Initialize Agent (via plugin or direct)
    my_agent = Agent.from_plugin(
        identifier="MathExpert",
        tools=[add_numbers],
        plugin_name="ollama-inference-adapter"
    )

    # 2. Execute run (awaits the completion of the inference cycle)
    # The method returns an AgentResponse object directly.
    response = await my_agent.run(
        user_prompt="What is 123 + 456?",
        model="gemma4"
    )
    
    # 3. Handle the result
    if not response.has_error:
        print(f"Response: {response.response}")
    else:
        response.raise_errors()

if __name__ == "__main__":
    asyncio.run(main())

Understanding AgentResponse

The Agent.run() method does not return a simple string, but an AgentResponse object. This container manages the execution results, the conversation history (context), and any errors that may have occurred.

Key Properties & Methods

  • .response: Returns the final text response from the assistant. Use this to get the AI's answer.
  • .has_error: A boolean flag indicating if a fatal process error (e.g., connection issues, missing models) occurred.
  • .raise_errors(): A helper method that raises the first recorded process error as an exception. Useful for debugging failed runs.
  • .context_size: Returns the number of messages exchanged in the current run.
  • .get_context(): Returns the full conversation history (messages, tool calls, and results).

Example: Handling the Response

response = await my_agent.run(user_prompt="Calculate 123 + 456", model="gemma4")

if response.has_error:
    print(f"An error occurred!")
    response.raise_errors()
else:
    print(f"Assistant says: {response.response}")
    print(f"Conversation steps: {response.context_size}")
    
#### Process Errors vs. Tool Errors

The AgentResponse distinguishes between two types of issues to give you fine-grained control over error handling:

  • Process Errors (.has_error / ._errors): These are fatal errors that prevented the Agent from completing its task (e.g., connection loss to Ollama, model not found, or authentication issues).
  • Tool Errors (.has_tool_error / .get_tool_errors()): These are non-fatal errors that occurred inside a specific tool execution. The Agent might still be able to provide a final response even if one or more tool calls failed.

Pro-Tip: If you want your application to be extra robust, always check both:

response = await my_agent.run(...)

if response.has_error:
    # Fatal: The agent couldn't finish
    response.raise_errors()

if response.has_tool_error:
    # Non-fatal: One or more tools failed, but the agent still replied
    for err in response.get_tool_errors():
        print(f"Warning: Tool execution failed: {err}")

Plugin Discovery & Dynamic Loading

The framework utilizes Python's entry points to support dynamic discovery and loading of inference adapters. This allows you to extend the agent's capabilities with external adapters without modifying the core package.

Using Agent.from_plugin

To use an external adapter, ensure the corresponding plugin package is installed in your environment. You can then instantiate an agent using the discovery interface:

from org.slashlib.py.agent import Agent

# 1. List all discovered inference adapter plugins
# This scans the environment for registered 'org.slashlib.py.inference.adapter' entry points.
available = Agent.list_plugins()
print(f"Available adapters: {available}")

# 2. Create an agent using a specific plugin
# In this example, we load the Ollama adapter dynamically.
my_agent = Agent.from_plugin(
    identifier="my-dynamic-agent",
    tools=[my_tool],                      # Must be a list of @tool() objects
    plugin_name="ollama-inference-adapter", # The name registered in entry_points
    adapter_kwargs={                      # Arguments passed directly to the Adapter __init__
        "base_url": "http://localhost:11434"
    },
    multi=True                            # Optional: Agent-specific keyword arguments
)

Why use Discovery?

  • Decoupling: The core agent logic remains independent of specific LLM providers (Ollama, OpenAI, etc.).

  • Extensibility: Simply install a new adapter package, and it becomes immediately available via list_plugins().

  • Flexible Config: adapter_kwargs allows for provider-specific configuration (like API keys or base URLs) while keeping the Agent initialization clean.


Documentation & Obsidian

The project root is pre-configured as an Obsidian Vault. If you open this folder directly in Obsidian, all settings and documentation links will be available immediately via the included .obsidian directory.

The following community plugins are pre-configured in the vault to enhance the documentation experience:

More docs


License

This project is licensed under the MIT License - see the LICENSE file for details.


© 2026 org.slashlib

TOP AI CHANGELOG LICENSE

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

org_slashlib_py_agent-0.1.7.tar.gz (22.8 kB view details)

Uploaded Source

Built Distribution

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

org_slashlib_py_agent-0.1.7-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file org_slashlib_py_agent-0.1.7.tar.gz.

File metadata

  • Download URL: org_slashlib_py_agent-0.1.7.tar.gz
  • Upload date:
  • Size: 22.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.6

File hashes

Hashes for org_slashlib_py_agent-0.1.7.tar.gz
Algorithm Hash digest
SHA256 bbbce9ba590a2a6e45e51b3c76f8bab057eac3d2c77b017527b99bcb1778c241
MD5 c90dfc6f76843ab740db4c680d362908
BLAKE2b-256 26a5ccf9b27568269077f298b6d055d591ffbe2f00e4ebf667d842c4bfd0ebab

See more details on using hashes here.

File details

Details for the file org_slashlib_py_agent-0.1.7-py3-none-any.whl.

File metadata

File hashes

Hashes for org_slashlib_py_agent-0.1.7-py3-none-any.whl
Algorithm Hash digest
SHA256 bb1b2cdc6a42838beb3c59d8d54ad23ffbeab333e983c34f21f3a1138862c025
MD5 b7eb4f57851843e7df2a9e76e407bbfd
BLAKE2b-256 4b19e9970730dcf6bc48e4462d2a03b9687b3bdd21dd345f413639bbc51ea12f

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