Skip to main content

Lightweight agents on top of Pydantic AI with true soft tools and conversation history

Project description

GF AgentKit

Lightweight agents on top of Pydantic AI with:

  • True soft tools (reasoning-first; tools are optional, not mandatory).
  • A single “gateway” tool that delegates to a separate executor (to reduce tool gravity).
  • Built-in conversation history (persistent transcript managed by the agent).
  • A few ready-to-use tools (file I/O, safe process execution, agent delegation).

⚠️ This is a work-in-progress. The API may evolve.


Why?

Many agent frameworks push models to use tools aggressively (“hard tools”), which can be great for workflow graphs but not for reasoning-first tasks. GF AgentKit makes tool use opt-in:

  • SoftToolAgent: the model decides when to ask for a tool using a schema-guided envelope (OPEN_RESPONSE / TOOL_CALL / TOOL_RESULT). The host runs tools and feeds results back as context.
  • DelegatingToolsAgent: the model sees one gateway tool (delegate_ops). That tool delegates to an internal executor which owns all the real tools. This reduces tool gravity and lets you keep a clean reasoning loop.

Both agents keep a persistent transcript so follow-ups naturally refer back to earlier turns.


Installation

pip install agentkit-gf

Requires: Python 3.11+.

You’ll also need an OpenAI key to use gpt-5-nano:

# macOS/Linux
export OPENAI_API_KEY=sk-...

# Windows PowerShell
$env:OPENAI_API_KEY = "sk-..."

At a Glance

agentkit_gf/
  ├─ _base_agent.py               # shared transcript + constructor glue
  ├─ soft_tool_agent.py           # true soft tools (Envelope schema)
  ├─ delegating_tools_agent.py    # single gateway tool; delegates to executor
  └─ tools/
       ├─ fs.py                   # FileTools: read/write/stat/hash/list (sandboxable)
       ├─ os.py                   # ProcessTools: run_process/run_shell (policy controlled)
       ├─ agent.py                # create_agent_delegation_tool(...) factory
       └─ builtin_tools_matrix.py # BuiltinTool enums + provider validation

Agents

1) SoftToolAgent (true soft tool)

  • The model returns a single Envelope JSON object each hop:

    • OPEN_RESPONSE { text, confidence? }
    • TOOL_CALL { tool, args_json, reason }
    • TOOL_RESULT { tool, args_json, result_json, success, note? } (normally emitted by the host)
  • You provide a registry of Python callables (tool_name -> callable(**kwargs)).

  • The agent executes tools in host code and feeds a TOOL_RESULT back to the model.

  • Maintains an internal transcript across turns.

Minimal example (read a file):

from agentkit_gf.soft_tool_agent import SoftToolAgent
from agentkit_gf.tools.fs import FileTools

file_tools = FileTools(root_dir=".")   # restrict if you like
registry = {"read_text": file_tools.read_text}

agent = SoftToolAgent(model="openai:gpt-5-nano")

prompt = (
    "Read ./notes.txt and tell me the first line.\n"
    "If you need the file, return a TOOL_CALL Envelope for tool 'read_text' with args_json "
    '{"path": "./notes.txt", "max_bytes": 10000}. Then, after TOOL_RESULT, respond with OPEN_RESPONSE.'
)

result = agent.run_soft_sync(prompt, registry, max_steps=5)
print(result.final_text)

Transcript helpers:

print(agent.export_history_text())
agent.reset_history()

2) DelegatingToolsAgent (single gateway tool)

  • Presents one tool (delegate_ops) to the model.
  • Internally spins up a private executor agent that owns all real tools (including optional provider built-ins like WebSearch).
  • You pass in objects or callables; public methods are automatically exposed as tools (optionally prefixed).

Example:

from agentkit_gf.delegating_tools_agent import DelegatingToolsAgent
from agentkit_gf.tools.fs import FileTools
from agentkit_gf.tools.os import ProcessTools
from agentkit_gf.tools.builtin_tools_matrix import BuiltinTool

agent = DelegatingToolsAgent(
    model="openai:gpt-5-nano",
    builtin_enums=[BuiltinTool.WEB_SEARCH],  # optional provider built-ins
    tool_sources=[
        FileTools(root_dir="."),
        ProcessTools(root_cwd=".", allowed_basenames=["python", "bash", "ls"])
    ],
    class_prefix="fs",  # public tool names become "fs_read_text", etc.
    system_prompt=(
        "Answer-first. Use delegate_ops only if a specific missing fact requires it."
    ),
    ops_system_prompt="Execute exactly one tool and return only its result.",
)

reply = agent.run_sync(
    "Read ./notes.txt (use delegate_ops/tool 'fs_read_text' if needed) and summarize the first line."
).output

print(reply)

Included Tools

FileTools (agentkit_gf.tools.fs)

  • read_text(path, max_bytes=200_000, encoding="utf-8")
  • read_bytes_base64(path, max_bytes=200_000)
  • write_text(path, content, overwrite=False, encoding="utf-8")
  • stat(path) / list_dir(path, include_hidden=False, max_entries=1000)
  • hash_file(path, algorithm=HashAlgorithm.SHA256)

All enforce fail-fast validation and can be sandboxed with root_dir.

ProcessTools (agentkit_gf.tools.os)

  • run_process(argv: Sequence[str], timeout_sec=10, cwd=None) (no shell; recommended)
  • run_shell(command: str, timeout_sec=10, cwd=None) (flexible; riskier)

Policy controls:

  • root_cwd (path sandbox)
  • allowed_basenames (allowlist executables)
  • max_output_bytes (clip stdout/stderr)

Agent Delegation Tool (agentkit_gf.tools.agent)

  • create_agent_delegation_tool(agent_factory: Callable[[str], Agent]) -> Callable[..., dict]
  • Produces a registry callable: delegate_agent(agent_name: str, prompt: str) -> {"output": ...}
  • Handy if your soft tool needs to spin up or discover another agent on demand.

Soft vs. Hard Tools

  • Soft tools (this library):

    • The model asks to call a tool via a schema; the host decides and executes.
    • Great for reasoning-first flows where the model should prefer answering from context.
  • Hard tools:

    • Registered with the provider; models are often biased to call them.
    • Better for rigid flows or “do X with Y, then Z” pipelines.

You can mix: use SoftToolAgent for reasoning, and DelegatingToolsAgent when you need a single, auditable gateway to real tools (including provider built-ins).


Extending with Your Own Tools

You can pass objects or callables:

class MyDataOps:
    def summarize_csv(self, path: str, top_n: int = 5) -> dict:
        # ... return JSON-serializable result ...
        return {"summary": "...", "top_n": top_n}

agent = DelegatingToolsAgent(
    model="openai:gpt-5-nano",
    builtin_enums=[],
    tool_sources=[MyDataOps()],
    class_prefix="data",  # exposes "data_summarize_csv"
)

For SoftToolAgent, add to the registry:

registry = {"summarize_csv": MyDataOps().summarize_csv}

API Reference (quick)

SoftToolAgent

SoftToolAgent(
    model: str,
    system_prompt: str | None = None,
    allow_llm_tool_result: bool = False,
)

run_soft_sync(
    prompt: str,
    tool_registry: Mapping[str, Callable[..., Any]],
    max_steps: int = 4,
) -> SoftRunResult

# history helpers
export_history_text() -> str
export_history_blocks() -> Sequence[str]
reset_history() -> None

Envelope schema: the model returns exactly one JSON object with message.kind in:

  • OPEN_RESPONSE { text, confidence? }
  • TOOL_CALL { tool, args_json, reason } (args_json must be an object string)
  • TOOL_RESULT { tool, args_json, result_json, success, note? }

DelegatingToolsAgent

DelegatingToolsAgent(
    model: str,
    builtin_enums: Sequence[BuiltinTool],
    tool_sources: Sequence[Callable | object | AbstractToolset] = (),
    class_prefix: str | None = None,
    system_prompt: str | None = None,
    ops_system_prompt: str | None = None,
)

# single-step run (history-aware)
run_sync(prompt: str) -> RunResult

This agent exposes only one public tool to the model: delegate_ops(tool, args_json, why) (the executor runs exactly one real tool; results are recorded to the transcript).


License

See LICENSE - MIT


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

agentkit_gf-0.1.3.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

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

agentkit_gf-0.1.3-py3-none-any.whl (19.6 kB view details)

Uploaded Python 3

File details

Details for the file agentkit_gf-0.1.3.tar.gz.

File metadata

  • Download URL: agentkit_gf-0.1.3.tar.gz
  • Upload date:
  • Size: 21.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for agentkit_gf-0.1.3.tar.gz
Algorithm Hash digest
SHA256 490d78ed7ce9ee1bc6ab05809caf57ab83048e4fc0fe2f180e1d7ee057af8320
MD5 976c37b8a373091355364d24217bc572
BLAKE2b-256 a5e1c7bbe62cfb30454a31eab85a16ced6ee8d2b10bef1dd7d117a5776a51fb4

See more details on using hashes here.

File details

Details for the file agentkit_gf-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: agentkit_gf-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 19.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for agentkit_gf-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 bb5e4b0e9acbbecee4a64d711221a6c33dd809424378252aca1e6c34344259d4
MD5 70192f5a4f2d6350854be86efeed7c02
BLAKE2b-256 3b2b70de94c03dc0c07da45416377e8fadb6c7903dd78d10ff0681c1a1ad7522

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