Skip to content

feat: add OpenAI Agents SDK cookbook example#146

Open
desiorac wants to merge 2 commits intousemoss:mainfrom
desiorac:feat/openai-agents-cookbook
Open

feat: add OpenAI Agents SDK cookbook example#146
desiorac wants to merge 2 commits intousemoss:mainfrom
desiorac:feat/openai-agents-cookbook

Conversation

@desiorac
Copy link
Copy Markdown

@desiorac desiorac commented Apr 11, 2026

Pull Request Checklist

  • I have read the CONTRIBUTING guide.
  • I have updated the documentation (if applicable).
  • My code follows the style guidelines of this project.
  • I have performed a self-review of my own code.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.

Description

Add an OpenAI Agents SDK cookbook example for Moss semantic search.

Follows the same pattern as the existing CrewAI cookbook — reuses the travel data, provides tool wrappers, an interactive multi-agent demo, and live platform tests.

What's included

  • moss_openai_agents.py — 6 tool factory functions wrapping MossClient methods as @function_tool callables:

    • moss_search_tool — semantic search with load_index() before first query
    • moss_search_with_filter_tool — search with optional metadata filter arguments
    • moss_add_docs_tool, moss_delete_docs_tool, moss_get_docs_tool — document CRUD
    • moss_list_indexes_tool — index listing
    • moss_tools() — convenience factory for all 6 tools
  • example_usage.py — Multi-agent travel planner demo using agent.as_tool() for orchestration

  • test_live.py — Live platform tests covering all tool operations

  • README.md — Setup, quick start, metadata filter usage, local-vs-cloud speed tradeoff

Design decisions

  • Used factory functions (not classes) since the Agents SDK uses @function_tool decorators — this keeps the API idiomatic
  • load_index() is called lazily on first search, not at construction time
  • Metadata filters use explicit filter_field + filter_value string args (LLM-friendly) rather than a raw dict

Fixes #92

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Open with Devin

Add cookbook example showing how to use Moss semantic search with the
OpenAI Agents SDK. Includes function tools for search (with optional
metadata filters), document management, and index listing.

Demo: multi-agent travel planner where specialist agents search
separate Moss indexes and a planner orchestrates via agent.as_tool().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 11, 2026 08:01
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +146 to +151
if not result.docs:
return "No documents found."
return "\n\n".join(
f"[{doc.id}]: {doc.text[:200]}{'...' if len(doc.text) > 200 else ''}"
for doc in result.docs
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 get_docs returns List[DocumentInfo], not an object with .docs — causes AttributeError

MossClient.get_docs() returns List[DocumentInfo] (confirmed by the type stub at sdks/python/sdk/src/moss/__init__.pyi:41-45, tests at sdks/python/sdk/tests/test_client.py:260-276, and the e2e test at sdks/python/sdk/tests/test_e2e.py:284-288). However, moss_get_docs_tool treats the return value as an object with a .docs attribute (like SearchResult), accessing result.docs on lines 146 and 150. This will raise AttributeError: 'list' object has no attribute 'docs' every time the tool is invoked. The existing crewai cookbook (examples/cookbook/crewai/moss_crewai.py:168-172) correctly iterates over the list directly.

Suggested change
if not result.docs:
return "No documents found."
return "\n\n".join(
f"[{doc.id}]: {doc.text[:200]}{'...' if len(doc.text) > 200 else ''}"
for doc in result.docs
)
if not result:
return "No documents found."
return "\n\n".join(
f"[{doc.id}]: {doc.text[:200]}{'...' if len(doc.text) > 200 else ''}"
for doc in result
)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Args:
doc_ids: Optional list of document IDs to retrieve.
"""
options = GetDocumentsOptions(ids=doc_ids) if doc_ids else GetDocumentsOptions()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Wrong keyword argument ids instead of doc_ids in GetDocumentsOptions

GetDocumentsOptions.__init__ accepts doc_ids as its parameter (per sdks/python/sdk/src/moss/__init__.pyi:85), but the tool passes ids=doc_ids. Every other usage in the codebase uses doc_ids= — see sdks/python/sdk/tests/test_client.py:264, examples/cookbook/crewai/moss_crewai.py:167, examples/python/comprehensive_sample.py:277. Since GetDocumentsOptions is a Rust-backed PyO3 class, passing an unknown keyword argument ids will either raise a TypeError or silently create an empty options object that fetches all documents instead of the requested subset.

Suggested change
options = GetDocumentsOptions(ids=doc_ids) if doc_ids else GetDocumentsOptions()
options = GetDocumentsOptions(doc_ids=doc_ids) if doc_ids else GetDocumentsOptions()
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new cookbook example demonstrating how to use Moss semantic search as tools within the OpenAI Agents SDK, including a multi-agent demo and live tests.

Changes:

  • Introduces moss_openai_agents.py tool factory functions wrapping MossClient operations for OpenAI Agents SDK.
  • Adds an interactive multi-agent travel planner demo (example_usage.py) plus travel dataset JSON files.
  • Adds setup/docs (README.md, .env.example) and a live-test script (test_live.py) for platform validation.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
examples/cookbook/openai-agents/moss_openai_agents.py Implements Agents SDK function tools for Moss search, filtering, CRUD, and index listing.
examples/cookbook/openai-agents/example_usage.py Multi-agent travel planner demo wiring specialist agents + planner orchestration.
examples/cookbook/openai-agents/test_live.py Scripted “live” verification of tool operations against the Moss API.
examples/cookbook/openai-agents/README.md Cookbook documentation: setup, quick start, tool descriptions, and demo instructions.
examples/cookbook/openai-agents/pyproject.toml Declares the example as a Python project with dependencies.
examples/cookbook/openai-agents/.env.example Environment variable template for Moss/OpenAI credentials.
examples/cookbook/openai-agents/data/destinations_moss.json Travel destination documents used by the demo.
examples/cookbook/openai-agents/data/stays_moss.json Travel stays documents used by the demo.
examples/cookbook/openai-agents/data/activities_moss.json Travel activities documents used by the demo.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Args:
doc_ids: Optional list of document IDs to retrieve.
"""
options = GetDocumentsOptions(ids=doc_ids) if doc_ids else GetDocumentsOptions()
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetDocumentsOptions is constructed with ids=..., but the Moss SDK uses doc_ids (see other examples/tests). As written, this will raise a TypeError at runtime and break moss_get_docs.

Suggested change
options = GetDocumentsOptions(ids=doc_ids) if doc_ids else GetDocumentsOptions()
options = GetDocumentsOptions(doc_ids=doc_ids) if doc_ids else GetDocumentsOptions()

Copilot uses AI. Check for mistakes.
opts = QueryOptions(
top_k=top_k,
alpha=alpha,
filter={filter_field: filter_value},
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metadata filter passed to QueryOptions is not in the filter expression format used by Moss (it expects objects like { "field": ..., "condition": {"$eq": ...} } and logical operators such as $and). Passing {filter_field: filter_value} is likely to be ignored or error in local queries; build a proper $eq filter expression instead.

Suggested change
filter={filter_field: filter_value},
filter={"field": filter_field, "condition": {"$eq": filter_value}},

Copilot uses AI. Check for mistakes.
ids: Optional document IDs (auto-generated if omitted).
upsert: If True, update existing documents with the same ID.
"""
doc_ids = ids or [str(uuid.uuid4()) for _ in texts]
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zip(doc_ids, texts) truncates to the shorter list. If ids is provided with a different length than texts, some documents will be silently dropped or some IDs ignored. Validate that len(ids) == len(texts) (or generate missing IDs per text, as other cookbooks do) before building docs.

Suggested change
doc_ids = ids or [str(uuid.uuid4()) for _ in texts]
if ids is None:
doc_ids = [str(uuid.uuid4()) for _ in texts]
else:
if len(ids) != len(texts):
raise ValueError("ids must have the same length as texts.")
doc_ids = ids

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +37
nonlocal _index_loaded
if not _index_loaded:
await client.load_index(index_name)
_index_loaded = True

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lazy load_index() guard uses a plain boolean. If the tool can be invoked concurrently, multiple calls may race and trigger multiple load_index() calls. Consider protecting the first-load path with an asyncio.Lock/asyncio.Event (or memoizing the load task) so only one load happens per tool instance.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +11
import asyncio
import uuid
from typing import Any

from agents import RunContextWrapper, function_tool
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused imports (asyncio, Any, RunContextWrapper) add noise and may fail linting in stricter environments. Remove them, or use RunContextWrapper in tool signatures if intended.

Suggested change
import asyncio
import uuid
from typing import Any
from agents import RunContextWrapper, function_tool
import uuid
from agents import function_tool

Copilot uses AI. Check for mistakes.
"openai-agents>=0.0.7",
"moss>=1.0.0",
"python-dotenv",
]
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pyproject.toml uses Hatchling but does not declare what to include in the wheel/sdist (unlike other cookbook examples). With only flat modules (no package dir), pip install -e . / building a wheel is likely to omit moss_openai_agents.py and example_usage.py, causing ModuleNotFoundError. Add [tool.hatch.build.targets.wheel] packages = ["moss_openai_agents.py"] and include needed files under [tool.hatch.build.targets.sdist].

Suggested change
]
]
[tool.hatch.build.targets.wheel]
packages = ["moss_openai_agents.py"]
[tool.hatch.build.targets.sdist]
include = [
"moss_openai_agents.py",
"example_usage.py",
"README.md",
]

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +55
"text": "Nine Hours Capsule Hotel - Minimalist capsule stay in Shinjuku. High-speed Wi-Fi, lockers, and showers included. Price: \u00c2\u00a53,500 per night. Location: Shinjuku/Kanda. Amenities: Wi-Fi, Locker, Shower.",
"metadata": {
"location": "Shinjuku/Kanda",
"price": "\u00c2\u00a53,500"
}
},
{
"id": "stay-tok-102",
"text": "Khaosan Tokyo Origami - Highly rated hostel in Asakusa. Offers dormitory beds and a communal kitchen to save on meal costs. Price: \u00c2\u00a52,800 per night. Location: Asakusa. Amenities: Kitchen, Laundry, Lounge.",
"metadata": {
"location": "Asakusa",
"price": "\u00c2\u00a52,800"
}
},
{
"id": "stay-tok-103",
"text": "Sakura Hotel Ikebukuro - Budget hotel with private rooms and 24-hour cafe. Good for groups of two on a budget. Price: \u00c2\u00a57,000 per night. Location: Ikebukuro. Amenities: Private Room, Cafe, Multilingual Staff.",
"metadata": {
"location": "Ikebukuro",
"price": "\u00c2\u00a57,000"
}
},
{
"id": "stay-vn-104",
"text": "Little Hanoi Hostel - Authentic hostel in the heart of the Old Quarter. Offers free breakfast and walking tours. Price: \u00c2\u00a51,200 per night. Location: Hanoi, Vietnam. Amenities: Free Breakfast, Walking Tours, Bicycle Rental.",
"metadata": {
"location": "Hanoi, Vietnam",
"price": "\u00c2\u00a51,200"
}
},
{
"id": "stay-al-105",
"text": "Santi Quanta Hostel - Boutique hostel near the Skanderbeg Square. Known for a social atmosphere and clean facilities. Price: \u00c2\u00a52,000 per night. Location: Tirana, Albania. Amenities: Common Room, City Maps, Air Conditioning.",
"metadata": {
"location": "Tirana, Albania",
"price": "\u00c2\u00a52,000"
}
},
{
"id": "stay-tr-106",
"text": "Cheers Lighthouse - Budget-friendly guesthouse in Sultanahmet with views of the Marmara Sea. Very close to the Blue Mosque. Price: \u00c2\u00a54,500 per night. Location: Istanbul, Turkey. Amenities: Ocean View, Restaurant, Airport Shuttle.",
"metadata": {
"location": "Istanbul, Turkey",
"price": "\u00c2\u00a54,500"
}
},
{
"id": "stay-ro-107",
"text": "Kismet Dao Hostel - Legendary hostel in Brasov. Famous for its backyard BBQ and proximity to the Black Church. Price: \u00c2\u00a52,500 per night. Location: Brasov, Romania. Amenities: Garden BBQ, Lockers, Kitchen Access.",
"metadata": {
"location": "Brasov, Romania",
"price": "\u00c2\u00a52,500"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The travel data contains mojibake (e.g., \u00c2\u00a5 rendering as Â¥), which will show up in demo outputs and tool results. Re-encode these currency symbols/ranges to proper UTF-8 (e.g., ¥ and ) so users don’t see corrupted text.

Suggested change
"text": "Nine Hours Capsule Hotel - Minimalist capsule stay in Shinjuku. High-speed Wi-Fi, lockers, and showers included. Price: \u00c2\u00a53,500 per night. Location: Shinjuku/Kanda. Amenities: Wi-Fi, Locker, Shower.",
"metadata": {
"location": "Shinjuku/Kanda",
"price": "\u00c2\u00a53,500"
}
},
{
"id": "stay-tok-102",
"text": "Khaosan Tokyo Origami - Highly rated hostel in Asakusa. Offers dormitory beds and a communal kitchen to save on meal costs. Price: \u00c2\u00a52,800 per night. Location: Asakusa. Amenities: Kitchen, Laundry, Lounge.",
"metadata": {
"location": "Asakusa",
"price": "\u00c2\u00a52,800"
}
},
{
"id": "stay-tok-103",
"text": "Sakura Hotel Ikebukuro - Budget hotel with private rooms and 24-hour cafe. Good for groups of two on a budget. Price: \u00c2\u00a57,000 per night. Location: Ikebukuro. Amenities: Private Room, Cafe, Multilingual Staff.",
"metadata": {
"location": "Ikebukuro",
"price": "\u00c2\u00a57,000"
}
},
{
"id": "stay-vn-104",
"text": "Little Hanoi Hostel - Authentic hostel in the heart of the Old Quarter. Offers free breakfast and walking tours. Price: \u00c2\u00a51,200 per night. Location: Hanoi, Vietnam. Amenities: Free Breakfast, Walking Tours, Bicycle Rental.",
"metadata": {
"location": "Hanoi, Vietnam",
"price": "\u00c2\u00a51,200"
}
},
{
"id": "stay-al-105",
"text": "Santi Quanta Hostel - Boutique hostel near the Skanderbeg Square. Known for a social atmosphere and clean facilities. Price: \u00c2\u00a52,000 per night. Location: Tirana, Albania. Amenities: Common Room, City Maps, Air Conditioning.",
"metadata": {
"location": "Tirana, Albania",
"price": "\u00c2\u00a52,000"
}
},
{
"id": "stay-tr-106",
"text": "Cheers Lighthouse - Budget-friendly guesthouse in Sultanahmet with views of the Marmara Sea. Very close to the Blue Mosque. Price: \u00c2\u00a54,500 per night. Location: Istanbul, Turkey. Amenities: Ocean View, Restaurant, Airport Shuttle.",
"metadata": {
"location": "Istanbul, Turkey",
"price": "\u00c2\u00a54,500"
}
},
{
"id": "stay-ro-107",
"text": "Kismet Dao Hostel - Legendary hostel in Brasov. Famous for its backyard BBQ and proximity to the Black Church. Price: \u00c2\u00a52,500 per night. Location: Brasov, Romania. Amenities: Garden BBQ, Lockers, Kitchen Access.",
"metadata": {
"location": "Brasov, Romania",
"price": "\u00c2\u00a52,500"
"text": "Nine Hours Capsule Hotel - Minimalist capsule stay in Shinjuku. High-speed Wi-Fi, lockers, and showers included. Price: ¥3,500 per night. Location: Shinjuku/Kanda. Amenities: Wi-Fi, Locker, Shower.",
"metadata": {
"location": "Shinjuku/Kanda",
"price": "¥3,500"
}
},
{
"id": "stay-tok-102",
"text": "Khaosan Tokyo Origami - Highly rated hostel in Asakusa. Offers dormitory beds and a communal kitchen to save on meal costs. Price: ¥2,800 per night. Location: Asakusa. Amenities: Kitchen, Laundry, Lounge.",
"metadata": {
"location": "Asakusa",
"price": "¥2,800"
}
},
{
"id": "stay-tok-103",
"text": "Sakura Hotel Ikebukuro - Budget hotel with private rooms and 24-hour cafe. Good for groups of two on a budget. Price: ¥7,000 per night. Location: Ikebukuro. Amenities: Private Room, Cafe, Multilingual Staff.",
"metadata": {
"location": "Ikebukuro",
"price": "¥7,000"
}
},
{
"id": "stay-vn-104",
"text": "Little Hanoi Hostel - Authentic hostel in the heart of the Old Quarter. Offers free breakfast and walking tours. Price: ¥1,200 per night. Location: Hanoi, Vietnam. Amenities: Free Breakfast, Walking Tours, Bicycle Rental.",
"metadata": {
"location": "Hanoi, Vietnam",
"price": "¥1,200"
}
},
{
"id": "stay-al-105",
"text": "Santi Quanta Hostel - Boutique hostel near the Skanderbeg Square. Known for a social atmosphere and clean facilities. Price: ¥2,000 per night. Location: Tirana, Albania. Amenities: Common Room, City Maps, Air Conditioning.",
"metadata": {
"location": "Tirana, Albania",
"price": "¥2,000"
}
},
{
"id": "stay-tr-106",
"text": "Cheers Lighthouse - Budget-friendly guesthouse in Sultanahmet with views of the Marmara Sea. Very close to the Blue Mosque. Price: ¥4,500 per night. Location: Istanbul, Turkey. Amenities: Ocean View, Restaurant, Airport Shuttle.",
"metadata": {
"location": "Istanbul, Turkey",
"price": "¥4,500"
}
},
{
"id": "stay-ro-107",
"text": "Kismet Dao Hostel - Legendary hostel in Brasov. Famous for its backyard BBQ and proximity to the Black Church. Price: ¥2,500 per night. Location: Brasov, Romania. Amenities: Garden BBQ, Lockers, Kitchen Access.",
"metadata": {
"location": "Brasov, Romania",
"price": "¥2,500"

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +8
"id": "dest-tok-001",
"text": "Tokyo Budget Overview: Tokyo can be affordable. Use a Pasmo or Suica card for subways. Average daily budget for low-cost travelers is \u00c2\u00a55,000\u00e2\u20ac\u201c\u00c2\u00a58,000. Best free view: Tokyo Metropolitan Government Building in Shinjuku.",
"metadata": {
"country": "Japan",
"topic": "Tokyo Budget Overview"
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This JSON content includes mojibake sequences (e.g., \u00e2\u20ac\u201c for an en-dash) that will render incorrectly in the demo. Consider replacing them with proper punctuation (e.g., ) and currency symbols to avoid confusing outputs.

Copilot uses AI. Check for mistakes.
on_invoke_tool requires a ToolContext instance — passing None causes
silent errors in openai-agents >= 0.13 where ctx.tool_name is accessed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yatharthk2
Copy link
Copy Markdown
Collaborator

Hi @desiorac can you please sign the cla for pr to be reviewed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: OpenAI Agents SDK Cookbook

4 participants