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.4.tar.gz (21.6 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.4-py3-none-any.whl (20.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: agentkit_gf-0.1.4.tar.gz
  • Upload date:
  • Size: 21.6 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.4.tar.gz
Algorithm Hash digest
SHA256 75497794c6156b374d918d7309e45fd6b208df8af46d42ce4f058596d8c17e45
MD5 a469a84a65bed3976117c3beb16a4243
BLAKE2b-256 c44d1463a563afd5008c366c5f77d57a3ac85f87022dd0687cd4d00ae42695ad

See more details on using hashes here.

File details

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

File metadata

  • Download URL: agentkit_gf-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 20.1 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.4-py3-none-any.whl
Algorithm Hash digest
SHA256 0c941b225675fadd90d718d38531cf103cc8032edb3b7b915a5e3ad69968f39d
MD5 2942d28ef08890ce83641f9472466d58
BLAKE2b-256 885fd4ebdaeb39f36c5c01688107109fb9b1ed57b3201e52c7dd1a8f9bdc38bb

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