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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ GOOGLE_API_KEY="your_gemini_key_here"
ANTHROPIC_API_KEY="sk-ant-..."
OPENAI_API_KEY="sk-..."
DEEPSEEK_API_KEY="your_deepseek_key_here"
# Optional — OpenAI-compatible hosts (e.g. examples/openai_compatible_host.py via Groq)
# GROQ_API_KEY="your_groq_key_here"
# GROQ_MODEL="llama-3.3-70b-versatile"

# GitHub (Optional — used by dev_tools/issue_resolver)
# Without this key the GitHub API applies an unauthenticated rate limit (60 req/hr per IP).
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Contributors add user-facing entries under `[Unreleased]` in the same PR. Mainta

### Added

- **Documentation:** OpenAI-compatible host guide and runnable Groq example covering shared `to_openai_tool()` usage, provider base URLs, credentials, and local servers (#261).
- **Framework:** `skillware/core/ui_schema.py` helpers to resolve dot paths and validate output-card field keys (#199).
- **CI:** Parametrized guard `tests/test_card_ui_schema.py` — every registry skill with output-card `ui_schema` must ship a fixture under `tests/fixtures/card_ui_schema/` whose samples cover all field keys (#199).

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,14 @@ for part in response.candidates[0].content.parts:
print(part.text)
```

For other providers and shared integration patterns, see the [usage guides index](docs/usage/README.md), [agent loops](docs/usage/agent_loops.md), [Gemini](docs/usage/gemini.md), [Claude](docs/usage/claude.md), [OpenAI](docs/usage/openai.md), [DeepSeek](docs/usage/deepseek.md), [Ollama](docs/usage/ollama.md), [API keys for skills](docs/usage/api_keys.md), and the [skill usage template](docs/usage/skill_usage_template.md) for contributors.
For other providers and shared integration patterns, see the [usage guides index](docs/usage/README.md), [agent loops](docs/usage/agent_loops.md), [Gemini](docs/usage/gemini.md), [Claude](docs/usage/claude.md), [OpenAI](docs/usage/openai.md), [OpenAI-compatible hosts](docs/usage/openai_compatible.md), [DeepSeek](docs/usage/deepseek.md), [Ollama](docs/usage/ollama.md), [API keys for skills](docs/usage/api_keys.md), and the [skill usage template](docs/usage/skill_usage_template.md) for contributors.

## Documentation

| Topic | Links |
| :--- | :--- |
| **Introduction** | [Introduction](docs/introduction.md) · [Vision](docs/vision.md) · [Comparison](COMPARISON.md) |
| **Usage guides** | [Skill Library](docs/skills/README.md) · [Usage Guide](docs/usage/README.md) · [Install extras](docs/usage/install_extras.md) · [Examples](examples/README.md) · [Agent Loops](docs/usage/agent_loops.md) · [API Keys](docs/usage/api_keys.md) · [CLI](docs/usage/cli.md) |
| **Usage guides** | [Skill Library](docs/skills/README.md) · [Usage Guide](docs/usage/README.md) · [OpenAI-compatible hosts](docs/usage/openai_compatible.md) · [Install extras](docs/usage/install_extras.md) · [Examples](examples/README.md) · [Agent Loops](docs/usage/agent_loops.md) · [API Keys](docs/usage/api_keys.md) · [CLI](docs/usage/cli.md) |
| **Security** | [Skill trust model](docs/security/skill-trust-model.md) · [SECURITY.md](SECURITY.md) |
| **Contributing** | [Contributing](CONTRIBUTING.md) · [Agent Native Workflow](docs/contributing/ai_native_workflow.md) · [Testing](docs/TESTING.md) · [Changelog](CHANGELOG.md) |

Expand Down
1 change: 1 addition & 0 deletions docs/usage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ To list locally available skills, inspect path resolution, or run bundle tests f
| Google Gemini | `to_gemini_tool()` | [gemini.md](gemini.md) | `GOOGLE_API_KEY` (install `skillware[gemini]` for `google-genai`) |
| Anthropic Claude | `to_claude_tool()` | [claude.md](claude.md) | `ANTHROPIC_API_KEY` |
| OpenAI (ChatGPT) | `to_openai_tool()` | [openai.md](openai.md) | `OPENAI_API_KEY` |
| OpenAI-compatible hosts | `to_openai_tool()` | [openai_compatible.md](openai_compatible.md) | Host-specific key |
| DeepSeek | `to_deepseek_tool()` | [deepseek.md](deepseek.md) | `DEEPSEEK_API_KEY` |
| Ollama (prompt mode) | `to_ollama_prompt()` | [ollama.md](ollama.md) | (local; no cloud key) |
| CLI | `skillware list`, `skillware paths`, `skillware test`, `skillware examples` | [cli.md](cli.md) | pytest in `[dev]` for `test` |
Expand Down
3 changes: 2 additions & 1 deletion docs/usage/agent_loops.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ flowchart LR

Provider guides contain full API details. Skill pages contain copy-paste examples with skill-specific paths and sample user messages.

OpenAI-compatible hosts reuse `to_openai_tool()`; see the [host guide](openai_compatible.md) and runnable [Groq example](../../examples/openai_compatible_host.py).

**Optional param validation:** Some agent-loop examples (e.g. `claude_wallet_check.py`, `gemini_tos_evaluator.py`) call `skill.validate_params(...)` before `execute()`; others call `execute()` directly.

---
Expand Down Expand Up @@ -105,4 +107,3 @@ skills in one harness.
| `defi/evm_tx_handler` | - | `gemini_evm_tx_handler.py` | `claude_evm_tx_handler.py` | - | - | - |
| `monitoring/token_limiter` | `token_limiter_loop.py` (local execute) | `gemini_token_limiter.py` | `claude_token_limiter.py` | (catalog page) | (catalog page) | (catalog page) |
| `finance/uk_companies_house_handler` | `uk_companies_house_handler_demo.py` | `gemini_uk_companies_house_handler.py` | (catalog page) | (catalog page) | (catalog page) | (catalog page) |

1 change: 1 addition & 0 deletions docs/usage/api_keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,5 +177,6 @@ Skills that talk only to `localhost` (for example a local Ollama instance) may n
- [Usage: Gemini](gemini.md) — agent-side `GOOGLE_API_KEY`
- [Usage: Claude](claude.md) — agent-side `ANTHROPIC_API_KEY`
- [Usage: OpenAI](openai.md) — agent-side `OPENAI_API_KEY`
- [OpenAI-compatible model hosts](openai_compatible.md) — host-specific keys and base URLs
- [Usage: DeepSeek](deepseek.md) — agent-side `DEEPSEEK_API_KEY`
- [Skill library](../skills/README.md) — per-skill environment requirements
17 changes: 17 additions & 0 deletions docs/usage/openai.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Skillware supports OpenAI Chat Completions tool calling via `SkillLoader.to_open

For agent credentials, set `OPENAI_API_KEY` (see [API keys for skills](api_keys.md) for local and CI setup). Skills that call external APIs during `execute()` may require additional variables documented on each skill page.

The same adapter also works with model hosts that accept the OpenAI `tools`
schema. See [OpenAI-compatible model hosts](openai_compatible.md) for provider
base URLs, credentials, and compatibility boundaries.

---

## Quick snippet
Expand All @@ -22,6 +26,18 @@ client = OpenAI()
# Pass bundle["instructions"] as the system message when you start the chat.
```

For a compatible host, keep the same adapter and configure the client. For
example, Groq uses a different key and base URL:

```python
import os

client = OpenAI(
api_key=os.environ["GROQ_API_KEY"],
base_url="https://api.groq.com/openai/v1",
)
```

---

## How it works
Expand Down Expand Up @@ -133,6 +149,7 @@ print(response.choices[0].message.content)

## Related documents

- [OpenAI-compatible model hosts](openai_compatible.md)
- [API keys for skills](api_keys.md)
- [Usage: DeepSeek](deepseek.md) (separate adapter)
- [Usage: Gemini](gemini.md)
Expand Down
99 changes: 99 additions & 0 deletions docs/usage/openai_compatible.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# OpenAI-compatible model hosts

Many model hosts accept the OpenAI Chat Completions `tools` schema. With those
hosts, use `SkillLoader.to_openai_tool()` and configure the OpenAI client with
the host's API key and `base_url`. A separate Skillware adapter is not needed
just because the model or vendor name is different.

Compatibility is host- and model-specific. Before relying on a model, confirm
that its current documentation says it supports Chat Completions tool calling,
then use the model ID published by that host.

## Choose the adapter

| Situation | Skillware adapter | Guide |
| :--- | :--- | :--- |
| OpenAI or a host that accepts the OpenAI `tools` schema | `to_openai_tool()` | This guide and [OpenAI](openai.md) |
| DeepSeek's first-party API | `to_deepseek_tool()` | [DeepSeek](deepseek.md) |
| Gemini or Claude's native tool API | `to_gemini_tool()` or `to_claude_tool()` | [Gemini](gemini.md) or [Claude](claude.md) |
| Ollama prompt mode, without OpenAI-style tool calling | `to_ollama_prompt()` | [Ollama](ollama.md) |

Use a provider-specific adapter when the provider's schema differs. Do not add
one `to_*_tool()` method per OpenAI-compatible vendor.

## Client pattern

This Groq example changes only the credential, base URL, and model passed to
the standard OpenAI client:

```python
import os

from openai import OpenAI

from skillware.core.env import load_env_file
from skillware.core.loader import SkillLoader

load_env_file()

bundle = SkillLoader.load_skill("compliance/tos_evaluator")
tool = SkillLoader.to_openai_tool(bundle)

client = OpenAI(
api_key=os.environ["GROQ_API_KEY"],
base_url="https://api.groq.com/openai/v1",
)

response = client.chat.completions.create(
model="<groq-model-id-with-tool-use>",
messages=[
{"role": "system", "content": bundle["instructions"]},
{"role": "user", "content": "Check https://example.com/docs."},
],
tools=[tool],
)
```

Handle `response.choices[0].message.tool_calls`, execute the skill, and return
the result exactly as in the [shared agent loop](agent_loops.md).

For a complete runnable Groq loop (load skill, call host, `execute()`, return
tool result), see [`examples/openai_compatible_host.py`](../../examples/openai_compatible_host.py).

## Common hosts

The endpoints below are the providers' documented general OpenAI-compatible
base URLs. Provider plans, regional endpoints, model IDs, and feature support
can change, so follow the linked provider documentation for current details.

| Host | `base_url` | Typical key variable | Notes |
| :--- | :--- | :--- | :--- |
| [Kimi / Moonshot](https://platform.kimi.ai/docs/api/chat) | `https://api.moonshot.ai/v1` | `MOONSHOT_API_KEY` | Chat Completions supports function tools; select a current Kimi model with tool use. |
| [Z.AI GLM](https://docs.z.ai/guides/develop/openai/python) | `https://api.z.ai/api/paas/v4/` | `ZAI_API_KEY` | Use the general API endpoint; the Coding Plan has a separate endpoint and use case. |
| [Groq](https://console.groq.com/docs/openai) | `https://api.groq.com/openai/v1` | `GROQ_API_KEY` | Use a model that the Groq model catalog marks for tool use. |
| [Mistral](https://docs.mistral.ai/resources/migration-guides) | `https://api.mistral.ai/v1` | `MISTRAL_API_KEY` | The OpenAI-compatible path uses Mistral model IDs. |
| [Together AI](https://docs.together.ai/docs/inference/openai-compatibility) | `https://api.together.ai/v1` | `TOGETHER_API_KEY` | Model IDs commonly use a `provider/model` form. |
| [Fireworks AI](https://docs.fireworks.ai/tools-sdks/openai-compatibility) | `https://api.fireworks.ai/inference/v1` | `FIREWORKS_API_KEY` | Use a Fireworks model or deployment ID that supports tools. |
| [OpenRouter](https://openrouter.ai/docs/quickstart) | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | This is a multi-model gateway; tool support depends on the routed model and provider. |
| [vLLM](https://docs.vllm.ai/en/latest/serving/online_serving/openai_compatible_server/) | `http://localhost:8000/v1` | Operator-defined | The server may require a configured token; the served model must support an appropriate chat template and tools. |
| [LiteLLM proxy](https://docs.litellm.ai/) | `http://localhost:4000` | Operator-defined | Use the proxy key and model alias configured by the proxy operator. |

Never commit API keys. Load them from the environment as described in
[API keys for skills](api_keys.md).

## Local-server boundary

An OpenAI-compatible route does not guarantee compatible tool behavior. For a
local vLLM or LiteLLM deployment, verify the server's tool-calling configuration
and the selected model before sending `tools=[tool]`. If the local runtime only
supports an instruction prompt, use the [Ollama prompt-mode guide](ollama.md)
instead of treating it as structured tool calling.

## Related documents

- [Runnable example: `openai_compatible_host.py`](../../examples/openai_compatible_host.py)
- [Usage: OpenAI](openai.md)
- [Usage: DeepSeek](deepseek.md)
- [Shared agent loops](agent_loops.md)
- [API keys for skills](api_keys.md)
- [Usage guides index](README.md)
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ from GitHub (network required); see
- [Gemini](../docs/usage/gemini.md)
- [Claude](../docs/usage/claude.md)
- [OpenAI](../docs/usage/openai.md)
- [OpenAI-compatible hosts](../docs/usage/openai_compatible.md)
- [DeepSeek](../docs/usage/deepseek.md)
- [Ollama](../docs/usage/ollama.md)
- [Install extras](../docs/usage/install_extras.md)
Expand Down Expand Up @@ -53,6 +54,7 @@ pip install -e ".[dev,all,agents]"
| `ollama_skills_test.py` | `finance/wallet_screening`, `office/pdf_form_filler`, `optimization/prompt_rewriter` | Ollama | `[finance_wallet_screening]`, `[office_pdf_form_filler]`, `[optimization_prompt_rewriter]`; install `ollama` separately | `ETHERSCAN_API_KEY`, `ANTHROPIC_API_KEY` | Loads multiple skills and tests prompt-mode tool calling with Ollama. |
| `ollama_tos_evaluator.py` | `compliance/tos_evaluator` | Ollama | `[compliance_tos_evaluator]`; install `ollama` separately | None | Runs the terms-of-service evaluator with local Ollama prompt-mode calls. |
| `openai_tos_evaluator.py` | `compliance/tos_evaluator` | OpenAI | `[compliance_tos_evaluator]`, `[openai]` | `OPENAI_API_KEY` | Runs the terms-of-service evaluator with OpenAI function calling. |
| `openai_compatible_host.py` | `compliance/tos_evaluator` | Groq (OpenAI-compatible) | `[compliance_tos_evaluator]`, `[openai]` | `GROQ_API_KEY`, `GROQ_MODEL` | Runs the terms-of-service evaluator through Groq's OpenAI-compatible API. |
| `pii_guardrail_flow.py` | `compliance/pii_masker` | Local execute | `[compliance_pii_masker]` | None | Demonstrates local PII masking before passing text to an external agent. |
| `prompt_compression_demo.py` | `optimization/prompt_rewriter` | Local execute | `[optimization_prompt_rewriter]` | None | Demonstrates prompt compression without a provider loop. |
| `novelty_extractor_demo.py` | `data_engineering/novelty_extractor` | Local execute | `[data_engineering_novelty_extractor]` | None | Demonstrates multi-turn corpus distillation using local embeddings with no API key. |
Expand Down
74 changes: 74 additions & 0 deletions examples/openai_compatible_host.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Run a Skillware tool loop through the OpenAI-compatible Groq API."""

import json
import os

from openai import OpenAI

from skillware.core.env import load_env_file
from skillware.core.loader import SkillLoader

load_env_file()

bundle = SkillLoader.load_skill("compliance/tos_evaluator")
print(f"Loaded Skill: {bundle['manifest']['name']}")

tos_skill = bundle["class"]()
openai_tool = SkillLoader.to_openai_tool(bundle)
tool_name = openai_tool["function"]["name"]
print(f"OpenAI tool name: {tool_name}")

client = OpenAI(
api_key=os.environ["GROQ_API_KEY"],
base_url="https://api.groq.com/openai/v1",
)
model = os.environ["GROQ_MODEL"]

user_query = (
"Before an agent crawls https://hackernoon.com/tagged/ai for research, "
"check whether that appears allowed."
)
print(f"User: {user_query}")

messages = [
{"role": "system", "content": bundle["instructions"]},
{"role": "user", "content": user_query},
]

response = client.chat.completions.create(
model=model,
messages=messages,
tools=[openai_tool],
)

while response.choices[0].message.tool_calls:
assistant_message = response.choices[0].message
messages.append(assistant_message)

for tool_call in assistant_message.tool_calls:
if tool_call.function.name != tool_name:
raise RuntimeError(f"Unexpected tool: {tool_call.function.name}")

fn_args = json.loads(tool_call.function.arguments)
print(f"OpenAI-compatible host requested tool: {tool_call.function.name}")
print(f"Input: {fn_args}")

result = tos_skill.execute(fn_args)
print(json.dumps(result, indent=2))

messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)

response = client.chat.completions.create(
model=model,
messages=messages,
tools=[openai_tool],
)

print("\nFinal Response:")
print(response.choices[0].message.content or "")
Loading