diff --git a/sdk/.gitignore b/sdk/.gitignore new file mode 100644 index 00000000..1a3c6aec --- /dev/null +++ b/sdk/.gitignore @@ -0,0 +1 @@ +.kvstore.json diff --git a/sdk/README.md b/sdk/README.md index e69de29b..b1fb817b 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -0,0 +1,72 @@ +# Kortix SDK + +[![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://python.org) + +A Python SDK that enables you to create, manage, and interact with AI agents on [Suna](https://suna.so). + +## ๐Ÿ“ฆ Installation + +Install directly from the GitHub repository: + +```bash +pip install "kortix @ git+https://github.com/kortix/suna.git@main#subdirectory=sdk" +``` + +Or using uv: + +```bash +uv add "kortix @ git+https://github.com/kortix/suna.git@main#subdirectory=sdk" +``` + +## ๐Ÿ”ง Quick Start + +```python +import asyncio +from kortix import kortix + +async def main(): + mcp_tools = kortix.MCPTools( + "http://localhost:4000/mcp/", # Point to any HTTP MCP server + "Kortix", + ) + await mcp_tools.initialize() + + # Initialize the client + client = kortix.Kortix(api_key="your-api-key") + + # Create an agent + agent = await client.Agent.create( + name="My Assistant", + system_prompt="You are a helpful AI assistant.", + mcp_tools=[mcp_tools], + allowed_tools=["get_wind_direction"], + ) + + # Create a conversation thread + thread = await client.Thread.create() + + # Run the agent + run = await agent.run("Hello, how are you?", thread) + + # Stream the response + stream = await run.get_stream() + async for chunk in stream: + print(chunk, end="") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## ๐Ÿ”‘ Environment Setup + +Get your API key from [https://suna.so/settings/api-keys](https://suna.so/settings/api-keys) + +## ๐Ÿงช Running Examples + +```bash +# Install dependencies +uv sync + +# Run the main example +PYTHONPATH=$(pwd) uv run example/example.py +``` diff --git a/sdk/example/example.py b/sdk/example/example.py new file mode 100644 index 00000000..fc6a0757 --- /dev/null +++ b/sdk/example/example.py @@ -0,0 +1,69 @@ +# Run with `PYTHONPATH=$(pwd) uv run example/example.py` + +import asyncio +import os + +from kortix import kortix +from kortix.utils import print_stream + + +from .kv import kv +from .mcp_server import mcp + + +async def main(): + """ + Please ignore the asyncio.exceptions.CancelledError that is thrown when the MCP server is stopped. I couldn't fix it. + """ + # Start the MCP server in the background + asyncio.create_task( + mcp.run_http_async( + show_banner=False, log_level="error", host="0.0.0.0", port=4000 + ) + ) + + # Create the MCP tools client with the URL of the MCP server that's accessible by the Suna instance + mcp_tools = kortix.MCPTools( + "http://localhost:4000/mcp/", # Since we are running Suna locally, we can use the local URL + "Kortix", + allowed_tools=["get_wind_direction"], + ) + await mcp_tools.initialize() + + kortix_client = kortix.Kortix( + os.getenv("KORTIX_API_KEY", "pk_xxx:sk_xxx"), + "http://localhost:8000/api", + ) + + # Setup the agent + agent_id = kv.get("agent_id") + if not agent_id: + agent = await kortix_client.Agent.create( + name="Generic Agent", + system_prompt="You are a generic agent. You can use the tools provided to you to answer questions.", + mcp_tools=[mcp_tools], + allowed_tools=["get_weather"], + ) + kv.set("agent_id", agent._agent_id) + else: + agent = await kortix_client.Agent.get(agent_id) + await agent.update(allowed_tools=["get_weather"]) + + # Setup the thread + thread_id = kv.get("thread_id") + if not thread_id: + thread = await kortix_client.Thread.create() + kv.set("thread_id", thread._thread_id) + else: + thread = await kortix_client.Thread.get(thread_id) + + # Run the agent + agent_run = await agent.run("What is the wind direction in Bangalore?", thread) + + stream = await agent_run.get_stream() + + await print_stream(stream) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/example/kv.py b/sdk/example/kv.py new file mode 100644 index 00000000..b9e3a50c --- /dev/null +++ b/sdk/example/kv.py @@ -0,0 +1,47 @@ +import json +import os +from typing import Any, Optional +from dotenv import load_dotenv + +load_dotenv("./.env") + + +# Local key-value store for storing agent and thread IDs +class LocalKVStore: + def __init__(self, filename: str = ".kvstore.json"): + self.filename = filename + self._data = {} + self._load() + + def _load(self): + if os.path.exists(self.filename): + try: + with open(self.filename, "r", encoding="utf-8") as f: + self._data = json.load(f) + except Exception: + self._data = {} + else: + self._data = {} + + def _save(self): + with open(self.filename, "w", encoding="utf-8") as f: + json.dump(self._data, f, indent=2) + + def get(self, key: str, default: Optional[Any] = None) -> Any: + return self._data.get(key, default) + + def set(self, key: str, value: Any): + self._data[key] = value + self._save() + + def delete(self, key: str): + if key in self._data: + del self._data[key] + self._save() + + def clear(self): + self._data = {} + self._save() + + +kv = LocalKVStore() diff --git a/sdk/example/mcp_server.py b/sdk/example/mcp_server.py new file mode 100644 index 00000000..4c1b4c5b --- /dev/null +++ b/sdk/example/mcp_server.py @@ -0,0 +1,13 @@ +from fastmcp import FastMCP + +mcp = FastMCP(name="Kortix") + + +@mcp.tool +async def get_weather(city: str) -> str: + return f"The weather in {city} is windy." + + +@mcp.tool +async def get_wind_direction(city: str) -> str: + return f"The wind direction in {city} is from the north." diff --git a/sdk/kortix/kortix.py b/sdk/kortix/kortix.py index 1817e73a..e90ff744 100644 --- a/sdk/kortix/kortix.py +++ b/sdk/kortix/kortix.py @@ -5,7 +5,7 @@ from .tools import AgentPressTools, MCPTools class Kortix: - def __init__(self, api_key: str, api_url="http://localhost:8000/api"): + def __init__(self, api_key: str, api_url="https://suna.so/api"): self._agents_client = agents.create_agents_client(api_url, api_key) self._threads_client = threads.create_threads_client(api_url, api_key)