Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 13 additions & 92 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Will output:
}
```

for claude, you should pass 2nd argument as SchemaFormat.claude or `claude`:
For claude, you should pass 2nd argument as SchemaFormat.claude or `claude`:

```python
from function_schema import get_function_schema
Expand All @@ -90,114 +90,35 @@ Please refer to the [Claude tool use](https://docs.anthropic.com/claude/docs/too
You can use any type hinting supported by python for the first argument of `Annotated`. including:
`typing.Literal`, `typing.Optional`, `typing.Union`, and `T | None` for python 3.10+.
`Doc` class or plain string in `Annotated` is used for describe the parameter.
`Doc` metadata is the [PEP propose](https://peps.python.org/pep-0727/) for standardizing the metadata in type hints.
currently, implemented in `typing-extensions` module. Also `function_schema.Doc` is provided for compatibility.

Enumeratable candidates can be defined with `enum.Enum` in the argument of `Annotated`.
In shorthand, you can use `typing.Literal` as the type will do the same thing:

```python
import enum

class AnimalType(enum.Enum):
dog = enum.auto()
cat = enum.auto()

def get_animal(
animal: Annotated[str, Doc("The animal to get"), AnimalType],
) -> str:
"""Returns the animal."""
return f"Animal is {animal.value}"
```
In this example, each name of `AnimalType` enums(`dog`, `cat`) is used as an enum schema.
In shorthand, you can use `typing.Literal` as the type will do the same thing.
from typing import Annotated, Literal

```python
def get_animal(
animal: Annotated[Literal["dog", "cat"], Doc("The animal to get")],
) -> str:
"""Returns the animal."""
return f"Animal is {animal}"
```


### Plain String in Annotated

The string value of `Annotated` is used as a description for convenience.

```python
def get_weather(
city: Annotated[str, "The city to get the weather for"], # <- string value of Annotated is used as a description
unit: Annotated[Optional[str], "The unit to return the temperature in"] = "celcius",
) -> str:
"""Returns the weather for the given city."""
return f"Weather for {city} is 20°C"
```

But this would create a predefined meaning for any plain string inside of `Annotated`,
and any tool that was using plain strings in them for any other purpose, which is currently allowed, would now be invalid.
Please refer to the [PEP 0727, Plain String in Annotated](https://peps.python.org/pep-0727/#plain-string-in-annotated) for more information.

### Usage with OpenAI API

You can use this schema to make a function call in OpenAI API:
```python
import openai
openai.api_key = "sk-..."

# Create an assistant with the function
assistant = client.beta.assistants.create(
instructions="You are a weather bot. Use the provided functions to answer questions.",
model="gpt-4-turbo-preview",
tools=[{
"type": "function",
"function": get_function_schema(get_weather),
}]
)

run = client.beta.messages.create(
assistant_id=assistant.id,
messages=[
{"role": "user", "content": "What's the weather like in Seoul?"}
]
)

# or with chat completion

result = openai.chat.completion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "What's the weather like in Seoul?"}
],
tools=[{
"type": "function",
"function": get_function_schema(get_weather)
}],
tool_call="auto",
)
```

### Usage with Anthropic Claude

```python
import anthropic

client = anthropic.Client()

response = client.beta.tools.messages.create(
model="claude-3-opus-20240229",
max_tokens=4096,
tools=[get_function_schema(get_weather, "claude")],
messages=[
{"role": "user", "content": "What's the weather like in Seoul?"}
]
)
```

### CLI usage

```sh
function_schema mymodule.py my_function | jq
```

### More Examples

For comprehensive usage examples with different AI platforms, see the [examples directory](./examples/):

- **[Basic Usage](./examples/basic_usage.py)** - Core features and function definition patterns
- **[OpenAI Integration](./examples/openai_example.py)** - Assistant API and Chat Completion examples
- **[Claude Integration](./examples/claude_example.py)** - Anthropic Claude tool calling examples
- **[MCP Integration](./examples/mcp_example.py)** - Model Context Protocol examples
- **[CLI Usage](./examples/cli_example.py)** - Command-line interface examples

## License
MIT License
95 changes: 95 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Function Schema Examples

This directory contains practical examples of how to use the `function-schema` library with various AI platforms and protocols.

## Examples

### 🔧 Basic Usage (`basic_usage.py`)
Demonstrates the fundamental features of function-schema:
- Creating functions with type annotations and Doc metadata
- Generating JSON schemas
- Using enums and Literal types for parameter constraints
- Different annotation styles

**Run:** `python examples/basic_usage.py`

### 🤖 OpenAI Integration (`openai_example.py`)
Shows how to integrate with OpenAI's APIs:
- Assistant API with tool calling
- Chat Completion API with function calling
- Multiple tool definitions

**Run:** `python examples/openai_example.py`

### 🧠 Claude Integration (`claude_example.py`)
Demonstrates Anthropic Claude tool calling:
- Basic tool calling setup
- Multi-turn conversations with tools
- Claude-specific schema format

**Run:** `python examples/claude_example.py`

### 📟 CLI Usage (`cli_example.py`)
Examples of using the command-line interface:
- Generating schemas from Python files
- Different output formats
- Working with multiple functions

**Test the CLI:**
```bash
# Install the package first
pip install -e .

# Generate schema for a function
function_schema examples/cli_example.py get_weather

# Generate with pretty JSON formatting
function_schema examples/cli_example.py get_weather | jq

# Generate for Claude format
function_schema examples/cli_example.py get_weather claude
```

### 🔌 MCP Integration (`mcp_example.py`)
Shows integration with Model Context Protocol:
- Creating MCP-compatible tool definitions
- Server manifest generation
- Tool calling examples
- Resource access patterns

**Run:** `python examples/mcp_example.py`

## Running the Examples

1. **Install the package:**
```bash
pip install -e .
```

2. **Run any example:**
```bash
python examples/basic_usage.py
python examples/openai_example.py
python examples/claude_example.py
python examples/mcp_example.py
```

3. **Test CLI functionality:**
```bash
function_schema examples/cli_example.py get_weather
```

## Integration Notes

- **OpenAI**: Requires `openai` library and API key for actual usage
- **Claude**: Requires `anthropic` library and API key for actual usage
- **MCP**: Conceptual example showing schema compatibility
- **CLI**: Works out of the box with the installed package

## Schema Formats

The library supports multiple output formats:
- **OpenAI format** (default): Uses `parameters` key
- **Claude format**: Uses `input_schema` key

Specify format with: `get_function_schema(func, "claude")`
88 changes: 88 additions & 0 deletions examples/basic_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
Basic usage example for function-schema library.

This example demonstrates how to:
1. Define a function with type annotations and Doc metadata
2. Generate a JSON schema from the function
3. Use enum for parameter constraints
"""

from typing import Annotated, Optional, Literal
from function_schema import Doc, get_function_schema
import enum
import json


def get_weather(
city: Annotated[str, Doc("The city to get the weather for")],
unit: Annotated[
Optional[str],
Doc("The unit to return the temperature in"),
enum.Enum("Unit", "celcius fahrenheit")
] = "celcius",
) -> str:
"""Returns the weather for the given city."""
return f"Weather for {city} is 20°C"


def get_animal_with_enum(
animal: Annotated[str, Doc("The animal to get"),
enum.Enum("AnimalType", "dog cat")],
) -> str:
"""Returns the animal using enum."""
return f"Animal is {animal}"


class AnimalType(enum.Enum):
dog = enum.auto()
cat = enum.auto()


def get_animal_with_class_enum(
animal: Annotated[str, Doc("The animal to get"), AnimalType],
) -> str:
"""Returns the animal using class-based enum."""
return f"Animal is {animal.value}"


def get_animal_with_literal(
animal: Annotated[Literal["dog", "cat"], Doc("The animal to get")],
) -> str:
"""Returns the animal using Literal type."""
return f"Animal is {animal}"


def get_weather_with_string_annotation(
city: Annotated[str, "The city to get the weather for"],
unit: Annotated[Optional[str], "The unit to return the temperature in"] = "celcius",
) -> str:
"""Returns the weather for the given city using plain string annotations."""
return f"Weather for {city} is 20°C"


if __name__ == "__main__":
# Generate schema for the main weather function
schema = get_function_schema(get_weather)
print("Basic weather function schema:")
print(json.dumps(schema, indent=2))

print("\n" + "="*50 + "\n")

# Generate schema for Claude format
claude_schema = get_function_schema(get_weather, "claude")
print("Schema for Claude:")
print(json.dumps(claude_schema, indent=2))

print("\n" + "="*50 + "\n")

# Generate schema for enum example
enum_schema = get_function_schema(get_animal_with_enum)
print("Animal enum schema:")
print(json.dumps(enum_schema, indent=2))

print("\n" + "="*50 + "\n")

# Generate schema for Literal example
literal_schema = get_function_schema(get_animal_with_literal)
print("Animal literal schema:")
print(json.dumps(literal_schema, indent=2))
Loading