Using magic
Agentica’s magic enables you to turn regular Python or TypeScript functions into AI-implemented functions. Define a function with a descriptive prompt or docstring and simply call it like any other function.
from agentica import magic
@magic()
def rhymes(word_a: str, word_b: str) -> bool:
"""
Returns whether `word_a` rhymes with `word_b`.
"""
...
When to use magic functions
Magic functions work best for completing simple, well-defined tasks that don’t benefit from maintaining context across multiple operations.
For longer-running, multi-task, contextual problem solving, see Agents.
Signature and parameters
@magic(
*scope: Any,
mcp: str = None,
persist: bool = False,
model: Literal[
'openai:gpt-3.5-turbo',
'openai:gpt-4o',
'openai:gpt-4.1',
'openai:gpt-5',
'anthropic:claude-sonnet-4',
'anthropic:claude-opus-4.1',
'anthropic:claude-sonnet-4.5',
] = 'openai:gpt-4.1',
max_tokens: int = None,
listener: Callable[[], AgentListener] = DEFAULT_AGENT_LISTENER,
)
async my_magic_func(my: str, args: float) -> dict[int, bytes]:
"""My magic function description"""
...
Parameters
- scope: Objects and tools available during execution (functions, classes, SDK clients, data, types, exceptions, etc.).
- mcp: Path to an MCP configuration file to expose MCP tools. See Advanced › MCP.
- model: Backing model used for execution. See options above.
- max_tokensmaxTokens: Maximum number of tokens the function can generate in one inference round.
In Python, magic functions can be defined to be synchronous or asynchronous.
You can pass anything into your magic function’s scope, and the magic function can interact with it.
Additional Python parameters:
- persist: Whether to persist the magic function invocation history between calls. Defaults to
False.
- listener: Optional listener constructor which tracks magic function invocation and interactions, logging them depending on which listener you provide.
Additional TypeScript parameters:
- streaming: Enable streaming mode to receive response chunks as they’re generated.
- Objects passed to
scope or as arguments are presented without private methods or field names (fields with a leading _).
- Async functions in
scope are exposed to the REPL as synchronous functions returning Future[T]. The REPL includes a top-level event loop, so the AI can await these futures directly and use standard patterns like asyncio.gather().
See the full API in the references: Python | TypeScript.
Expose the full programmatic power of an SDK or API. No MCP server is required. Simply pass it into your magic functions scope.
You can also expose existing remote or local MCP tools by passing an MCP configuration path. See Advanced › MCP.
Prerequisites:
- Python: run
pip install art or uv add art
- TypeScript: run
npm install figlet (or use pnpm, bun)
from agentica.magic.logging import set_default_agent_listener
set_default_agent_listener(None)
from agentica import magic
from art import text2art
@magic(text2art)
def greet(name: str) -> str:
"""
Use the provided function to create a fancy greeting.
"""
...
print(greet("agentica"))
__ __ ____ ___ __ _
/ / / /__ / / /___ / | ____ ____ ____ / /_(_)________ _
/ /_/ / _ \/ / / __ \ / /| |/ __ `/ _ \/ __ \/ __/ / ___/ __ `/
/ __ / __/ / / /_/ / / ___ / /_/ / __/ / / / /_/ / /__/ /_/ /
/_/ /_/\___/_/_/\____( ) /_/ |_\__, /\___/_/ /_/\__/_/\___/\__,_/
|/ /____/
Streaming
Stream responses as they are being generated.
from agentica import magic
from agentica.magic.logging.loggers.stream_logger import StreamLogger
@magic(model='openai:gpt-4o')
async def word_counter(corpus: str) -> int:
"""Returns the number of words in the corpus."""
...
stream = StreamLogger()
with stream:
res = asyncio.create_task(
word_counter("True! — nervous — very, very dreadfully nervous I had been and am; but why will you say that I am mad? The disease had sharpened my senses — not destroyed — not dulled them. Above all was the sense of hearing acute. I heard all things in the heaven and in the earth. I heard many things in hell. How, then, am I mad? Hearken! and observe how healthily — how calmly I can tell you the whole story.")
)
async for chunk in stream:
if chunk.role == 'agent':
print(chunk, end="", flush=True)
print()
print(await res)
Using MCP
from dataclasses import dataclass
from agentica import magic
@dataclass
class Report:
name: str
blurb: str
@magic(mcp="./my-mcp.json")
async def run_report(company: str) -> Report:
"""
Create a brief company report for the given company name.
Returns a report with:
- name: The official company name
- blurb: A 1-2 sentence description of the company's main business focus
"""
...
Advanced
You can expose custom exceptions in scope so they can be raised from within execution (see Advanced, including information on logging, retries, rate-limiting and prefix caching). For more examples, see Examples.