Metadata-Version: 2.4
Name: AgentToolProtocol
Version: 0.1.5
Summary: A Python SDK for registering, exposing, and serving your own Python functions as tools via the ATP platform. Supports secure OAuth2 flows, dynamic tool registration, and real-time tool invocation via WebSocket or HTTP.
Author-email: Samuel Obinna Chimdi <sammyfirst6@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/agent-tool-protocol/python-sdk
Project-URL: Repository, https://github.com/agent-tool-protocol/python-sdk
Project-URL: Issues,  https://github.com/agent-tool-protocol/python-sdk/issues
Project-URL: Documentation, https://github.com/agent-tool-protocol/python-sdk/blob/main/README.md
Keywords: agent,toolkit,chat,sdk,llm,ChatATP,ChatGPT,AI Agent Frameworks,websocket,http,api,tools,functions,automation,ATP,Agent Tool Protocol,ATP SDK,tool registration,tool invocation,tool serving,toolkit registration,toolkit invocation,toolkit serving,remote tool invocation,remote tool serving,remote tool registration,remote toolkit invocation,Samuel Obinna Chimdi,ChatATP Agent
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31.0
Requires-Dist: websocket-client>=1.6.1
Dynamic: license-file

# Agent Tool Protocol(ATP)

<p align="center">
  <img src="https://github.com/sam-14uel/Agent-Tool-Protocol/raw/main/assets/atp.png" alt="ATP Logo" />
</p>

<p align="center">
  <strong>
  A Python SDK for registering, exposing, and serving your own Python functions as tools via the ATP platform.
  Supports secure OAuth2 flows, dynamic tool registration, and real-time tool invocation via WebSocket.
  </strong>
</p>


---

## Table of Contents

- Installation
- Quick Start
- Class: ToolKitClient
  - Constructor
  - register_tool
  - start
  - stop
- Tool Function Requirements
- WebSocket Events
- Error Handling
- Examples
- Advanced Usage
- License

---

## Installation

```sh
pip install AgentToolProtocol
```

---

## Quick Start

```python
from atp_sdk.clients import ToolKitClient
import requests

client = ToolKitClient(
    api_key="YOUR_ATP_API_KEY",
    app_name="my_app"
)

@client.register_tool(
    function_name="hello_world",
    params=['name'],
    required_params=['name'],
    description="Returns a greeting.",
    auth_provider=None, auth_type=None, auth_with=None
)
def hello_world(**kwargs):
    return {"message": f"Hello, {kwargs.get('name', 'World')}!"}

client.start()
```

---

## Class: ToolKitClient

### Constructor

```python
ToolKitClient(
    api_key: str,
    app_name: str,
    base_url: str = "https://chatatp-backend.onrender.com"
)
```

**Parameters:**
- `api_key` (str): Your ATP API key.
- `app_name` (str): Name of your application.
- `base_url` (str, optional): ATP Server backend URL. Defaults to chatatp-backend.onrender.com.

---

### register_tool

Registers a Python function as a tool with the ATP platform.

```python
@client.register_tool(
    function_name: str,
    params: list[str],
    required_params: list[str],
    description: str,
    auth_provider: Optional[str],
    auth_type: Optional[str],
    auth_with: Optional[str]
)
def my_tool(**kwargs):
    ...
```

**Arguments:**
- `function_name`: Unique name for the tool.
- `params`: List of all parameter names.
- `required_params`: List of required parameter names.
- `description`: Human-readable description.
- `auth_provider`: Name of OAuth2 provider (e.g., "hubspot", "google"), or `None`.
- `auth_type`: Auth type (e.g., "OAuth2", "apiKey"), or `None`.
- `auth_with`: Name of the token parameter (e.g., "access_token", "api_key"), or `None`.

**Returns:**  
A decorator to wrap your function.

**Example:**

```python
@client.register_tool(
    function_name="create_company",
    params=['name', 'domain', 'industry'],
    required_params=['name', 'domain', 'industry'],
    description="Creates a company in HubSpot.",
    auth_provider="hubspot", auth_type="OAuth2", auth_with="access_token"
)
def create_company(**kwargs):
    access_token = kwargs.get('auth_token')
    url = "https://api.hubapi.com/crm/v3/objects/companies"
    headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
    data = {"properties": {
        "name": kwargs.get('name'),
        "domain": kwargs.get('domain'),
        "industry": kwargs.get('industry')
    }}
    response = requests.post(url, json=data, headers=headers)
    return response.json()
```

---

### start

Starts the WebSocket client and begins listening for tool requests.

```python
client.start()
```

- Keeps the main thread alive.
- Handles reconnections automatically.

---

### stop

Stops the WebSocket client and closes the connection.

```python
client.stop()
```

---

## Tool Function Requirements

- Must accept all parameters as `**kwargs`.
- If your tool requires authentication, expect `auth_token` in `kwargs`.
- Return a serializable object (dict, str, etc).

---

## WebSocket Events

### Tool Registration

Upon registration, your tool is announced to the ATP backend and available for invocation.

### Tool Invocation

When a tool request is received, your function is called with the provided parameters and (if needed) `auth_token`.

**Example incoming message:**
```json
{
  "message_type": "atp_tool_request",
  "payload": {
    "request_id": "uuid",
    "tool_name": "create_company",
    "params": {"name": "Acme", "domain": "acme.com", "industry": "Tech"},
    "auth_token": "ACCESS_TOKEN"
  }
}
```

---

## Error Handling

- If your function raises an exception, the error is caught and returned as:
  ```json
  {"error": "Error message"}
  ```
- If required parameters are missing, an error is returned.
- If `auth_token` is required but missing, an error is returned.

---

## Examples

### Minimal Tool

```python
@client.register_tool(
    function_name="echo",
    params=['text'],
    required_params=['text'],
    description="Echoes the input text.",
    auth_provider=None, auth_type=None, auth_with=None
)
def echo(**kwargs):
    return {"echo": kwargs.get('text')}
```

### Tool with OAuth2

```python
@client.register_tool(
    function_name="get_contacts",
    params=[],
    required_params=[],
    description="Fetches contacts from HubSpot.",
    auth_provider="hubspot", auth_type="OAuth2", auth_with="access_token"
)
def get_contacts(**kwargs):
    access_token = kwargs.get('auth_token')
    url = "https://api.hubapi.com/crm/v3/objects/contacts"
    headers = {"Authorization": f"Bearer {access_token}"}
    response = requests.get(url, headers=headers)
    return response.json()
```

### Tool with API Key

```python
@client.register_tool(
    function_name="get_contacts",
    params=[],
    required_params=[],
    description="Fetches contacts from HubSpot.",
    auth_provider="hubspot", auth_type="apiKey", auth_with="api_key"
)
def get_contacts(**kwargs):
    access_token = kwargs.get('auth_token')
    url = "https://api.hubapi.com/crm/v3/objects/contacts"
    headers = {"Authorization": f"Bearer {access_token}"}
    response = requests.get(url, headers=headers)
    return response.json()
```


---

## Class: LLMClient
The `LLMClient` lets you connect to the ATP Agent Server, retrieve toolkit context, and execute tools or workflows using JSON payloads—perfect for LLM-based agents.

### Constructor
```python
from atp_sdk.clients import LLMClient

llm_client = LLMClient(
    api_key="YOUR_ATP_API_KEY",
    protocol="ws",  # or "http"
    base_url="https://chatatp-backend.onrender.com/ws/v1/atp/llm-client/"
)
```
**Parameters:**
- `api_key` (str): Your ATP API key.
- `protocol` (str, optional): Protocol to use ("ws" or "http"). Defaults to "ws".
- `base_url` (str, optional): ATP server URL. Defaults to `https://chatatp-backend.onrender.com/ws/v1/atp/llm-client/`.

---

### get_toolkit_context
Retrieves the toolkit context and system instructions for a given toolkit and user prompt.
```python
context = llm_client.get_toolkit_context(
    toolkit_id="your_toolkit_id",
    provider="openai",  # or "anthropic" or "mistralai"
    user_prompt="What do you want to achieve?"
)
```
**Returns:**
A dictionary containing the toolkit context, including provider-specific tool schemas.

---

### call_tool
Executes a tool or workflow on the ATP server.
```python
response = llm_client.call_tool(
    toolkit_id="your_toolkit_id",
    json_response='{"function": "hello_world", "parameters": {"name": "Alice"}}',
    provider="openai",  # or "anthropic" or "mistralai"
    user_prompt="Say hello to Alice."
)
print(response)
```
**Arguments:**
- `toolkit_id`: Unique ID of the toolkit.
- `json_response`: JSON payload from an LLM containing the tool call.
- `provider`: The LLM provider (e.g., "openai", "anthropic", "mistralai").
- `user_prompt`: Additional user input to include in the execution.

---

## Request/Response Flow

### **1. LLM Requests Toolkit Context**
- The LLM (OpenAI, Anthropic, or Mistral) sends a request to the ATP server to get the toolkit context.
- The ATP server responds with a list of available tools and their schemas.

**Request:**
```json
{
  "type": "get_toolkit_context",
  "toolkit_id": "your_toolkit_id",
  "request_id": "uuid",
  "provider": "openai",
  "user_prompt": "What do you want to achieve?"
}
```

**Response:**
```json
{
  "type": "toolkit_context",
  "request_id": "uuid",
  "payload": {
    "toolkit_id": "your_toolkit_id",
    "toolkit_name": "Example Toolkit",
    "caption": "Example Caption",
    "provider": "openai",
    "tools": [
      {
        "type": "function",
        "name": "hello_world",
        "description": "Returns a greeting.",
        "parameters": {
          "type": "object",
          "properties": {
            "name": {"type": "string", "description": "Name to greet"}
          },
          "required": ["name"]
        }
      }
    ],
    "user_prompt": "What do you want to achieve?"
  }
}
```

---

### **2. LLM Generates Tool Calls**
- The LLM uses the toolkit context to generate tool calls.
- The LLM sends the tool calls to the ATP server for execution.

**Request:**
```json
{
  "type": "task_request",
  "toolkit_id": "your_toolkit_id",
  "request_id": "uuid",
  "payload": {
    "function": "hello_world",
    "parameters": {"name": "Alice"}
  },
  "provider": "openai",
  "user_prompt": "Say hello to Alice."
}
```

---

### **3. ATP Server Executes Tool**
- The ATP server receives the tool call and executes the corresponding tool.
- The ATP server sends the tool's response back to the LLM.

**Response:**
```json
{
  "type": "task_response",
  "request_id": "uuid",
  "payload": {
    "result": {"message": "Hello, Alice!"}
  }
}
```

---

## Using LLMClient with OpenAI, Anthropic, and Mistral AI

### OpenAI
```python
import openai
from atp_sdk.clients import LLMClient

openai_client = openai.OpenAI(api_key="YOUR_OPENAI_API_KEY")
llm_client = LLMClient(api_key="YOUR_ATP_API_KEY")

# Get toolkit context
context = llm_client.get_toolkit_context(
    toolkit_id="your_toolkit_id",
    provider="openai",
    user_prompt="Create a company and then list contacts."
)

# Use OpenAI to generate tool calls
response = openai_client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Create a company and then list contacts."}
    ],
    tools=context["tools"],
    tool_choice="auto"
)

# Extract tool calls
tool_calls = response.choices[0].message.tool_calls

# Loop through tool calls and execute each one
for tool_call in tool_calls:
    tool_call_json = {
        "function": tool_call.function.name,
        "parameters": tool_call.function.arguments
    }

    result = llm_client.call_tool(
        toolkit_id="your_toolkit_id",
        json_response=tool_call_json,
        provider="openai",
        user_prompt="Create a company and then list contacts."
    )

    print(f"Tool call result: {result}")
```

---

### Anthropic
```python
import anthropic
from atp_sdk.clients import LLMClient

anthropic_client = anthropic.Anthropic(api_key="YOUR_ANTHROPIC_API_KEY")
llm_client = LLMClient(api_key="YOUR_ATP_API_KEY")

# Get toolkit context
context = llm_client.get_toolkit_context(
    toolkit_id="your_toolkit_id",
    provider="anthropic",
    user_prompt="Create a company and then list contacts."
)

# Use Anthropic to generate tool calls
response = anthropic_client.messages.create(
    model="claude-3-opus-20240229",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Create a company and then list contacts."}
    ],
    tools=context["tools"]
)

# Extract tool calls
tool_calls = response.content

# Loop through tool calls and execute each one
for tool_call in tool_calls:
    tool_call_json = {
        "function": tool_call.name,
        "parameters": tool_call.input
    }

    result = llm_client.call_tool(
        toolkit_id="your_toolkit_id",
        json_response=tool_call_json,
        provider="anthropic",
        user_prompt="Create a company and then list contacts."
    )

    print(f"Tool call result: {result}")
```

---

### Mistral AI
```python
from mistralai.client import MistralClient
from atp_sdk.clients import LLMClient

mistral_client = MistralClient(api_key="YOUR_MISTRAL_API_KEY")
llm_client = LLMClient(api_key="YOUR_ATP_API_KEY")

# Get toolkit context
context = llm_client.get_toolkit_context(
    toolkit_id="your_toolkit_id",
    provider="mistralai",
    user_prompt="Create a company and then list contacts."
)

# Use Mistral to generate tool calls
response = mistral_client.chat(
    model="mistral-large-latest",
    messages=[{"role": "user", "content": "Create a company and then list contacts."}],
    tools=context["tools"]
)

# Extract tool calls
tool_calls = response.choices[0].message.tool_calls

# Loop through tool calls and execute each one
for tool_call in tool_calls:
    tool_call_json = {
        "function": tool_call.function.name,
        "parameters": tool_call.function.arguments
    }

    result = llm_client.call_tool(
        toolkit_id="your_toolkit_id",
        json_response=tool_call_json,
        provider="mistralai",
        user_prompt="Create a company and then list contacts."
    )

    print(f"Tool call result: {result}")
```

---

## Handling Multi-Step Tool Calls
When the LLM generates multiple tool calls, loop through them and execute each one sequentially:

```python
# Loop through tool calls and execute each one
for tool_call in tool_calls:
    tool_call_json = {
        "function": tool_call.function.name,
        "parameters": tool_call.function.arguments
    }

    result = llm_client.call_tool(
        toolkit_id="your_toolkit_id",
        json_response=tool_call_json,
        provider="openai",  # or "anthropic" or "mistralai"
        user_prompt="Create a company and then list contacts."
    )

    print(f"Tool call result: {result}")
```

---

## Advanced Usage

### Custom Backend

```python
client = ToolKitClient(
    api_key="YOUR_API_KEY",
    app_name="my_app",
    base_url="https://your-backend.example.com"
)
```

### Multiple Tools

```python
@client.register_tool(...)
def tool1(**kwargs): ...

@client.register_tool(...)
def tool2(**kwargs): ...
```

---

## License

MIT License.  
See LICENSE for details.

---

## Feedback & Issues

For bug reports or feature requests, please open an issue on [GitHub](https://github.com/sam-14uel/Agent-Tool-Protocol).

---

**Happy coding! 🚀**
