<!-- OpenSmartRoute: osr-integrations. Source https://github.com/isathish/OpenSmartRoute/blob/main/.claude/skills/osr-integrations/SKILL.md; HTML https://opensmartroute.ai/docs/skills/osr-integrations -->
---
name: osr-integrations
description: Connect OpenSmartRoute to the outside world - OpenAI-compatible LLM clients and handlers, MCP tool catalogues (stdio client, signed manifests), A2A agent cards, agent harnesses (callable, HTTP, subprocess), LangGraph nodes and conditions, Microsoft Agent Framework executors, OpenAI function-calling tool specs, persona loaders and Agent-Skills SKILL.md packages. Use when wiring a real provider or framework into the router, importing tools or agents, or authoring a SKILL.md that the router can select.
license: Apache-2.0
compatibility: OpenSmartRoute >= 0.4, Python >= 3.10
metadata:
  author: opensmartroute
  osr-domains: "coding general"
  osr-tags: "opensmartroute integrations mcp a2a langgraph openai skills"
  osr-quality-prior: "0.85"
  osr-primary: "false"
---

# OpenSmartRoute integrations

Everything below produces `RouteTarget`s or handlers; the router itself has no I/O.

## LLM providers (`adapters.openai_compat`)

```python
from opensmartroute.adapters import OpenAICompatClient, chat_handler, embedder, judge_fn
openai = OpenAICompatClient()                                   # OPENAI_API_KEY (or *_FILE) from env
ollama = OpenAICompatClient("http://localhost:11434/v1", api_key="ollama")
registry.get("llm-frontier").handler = chat_handler(openai, model="gpt-4o", system_prompt=None)
registry.get("llm-small").handler   = chat_handler(ollama, model="llama3.1")
router = Router(registry,
    strategies=[CapabilityStrategy(), SimilarityStrategy(embedder=embedder(openai, "text-embedding-3-small"))],
    llm_judge=LLMJudgeStrategy(judge_fn(openai, "gpt-4o-mini")), escalate_llm_judge_below=0.5)
```
`chat_handler` returns `ChatResult(text, model, prompt_tokens, completion_tokens, latency_ms, raw)`;
execution derives cost from tokens x `usd_per_1k_tokens`. Local embeddings:
`sentence_transformers_embedder(model)` (`embeddings` extra).

## MCP tools (`adapters.mcp`)

```python
from opensmartroute.adapters import tools_from_mcp, connect_mcp, tools_from_manifest
targets = tools_from_mcp(tools_list_json, server="github", call=lambda name, args: client.call(name, args),
                         cost_per_call_usd=0.0, latency_ms=300, quality_prior=0.6, max_description_risk=0.6)
client, targets = connect_mcp(["npx", "-y", "@modelcontextprotocol/server-filesystem", "."], server="fs")
```
Ids are `server:tool`; `inputSchema` is kept in `metadata`; risky descriptions are dropped. Prefer
signed manifests in production (`sign_manifest` / `verify_manifest` / `tools_from_manifest`).

Which *server* to connect in the first place (`adapters.mcp_servers`):

```python
from opensmartroute.adapters import ServerCard, recommend_servers
cards = [ServerCard.from_dict(d) for d in registry_json]            # name, description, tools, tags, auth,
for rec in recommend_servers("open a pull request", cards, k=2,    #   latency_ms, region, data_boundary
                             constraints=req.constraints, allowed_auth=["none", "token"]):
    rec.server.name, rec.score, rec.rationale, rec.matched_tools
```
BM25 + hashed-dense fusion over the card text; hard filters on latency, region, data boundary and
auth kind run before ranking, so a private request never gets a public-only server recommended.

## vLLM semantic-router configs (`adapters.semantic_router`)

```python
from opensmartroute.adapters import load_semantic_router_config
imported = load_semantic_router_config("config.yaml")            # or a dict
router = Router(imported.registry, strategies=[RulesStrategy(imported.rules), *default_strategies()])
imported.categories, imported.default_model, imported.warnings
```
Categories with `model_scores` become `RouteTarget`s (`quality_prior` = mean score, cost from
`model_config.pricing`, `effort: high` when every category uses reasoning) plus one `Rule` per
category; `system_prompt` lands in the top target's `instructions`.

## A2A agents (`adapters.a2a`)

```python
from opensmartroute.adapters import fetch_agent_card, agent_from_card, skills_from_card, a2a_handler
card = fetch_agent_card("https://agents.example.com/travel")          # https only, /.well-known/agent.json
agent = agent_from_card(card, call=a2a_handler(card["url"], token=load_secret("A2A_TOKEN")))
for s in skills_from_card(card): registry.add(s)                       # primary=False, id "<agent>/<skill>"
```

## Agent harnesses (`adapters.harness`)

`CallableHarness(fn)` (`fn(task, context, history) -> str | dict | HarnessResult`),
`HTTPHarness(url, api_key_env=..., timeout_s=300)` (POST `{"task","context","history"}`),
`SubprocessHarness(argv, json_output=True, system_flag="--system")`. Wrap with
`harness_handler(harness)` and assign to a `kind: agent` target. Return `HarnessResult(text, success,
cost_usd, prompt_tokens, completion_tokens, quality)` so outcomes carry real cost/quality.

## Frameworks (`adapters.frameworks`)

```python
node = langgraph_node(router, execute=False, plan=False)      # state["messages"] -> route_target, route
graph.add_conditional_edges("route", langgraph_condition(by="target", default="__end__"), {...})
executor, handoffs = maf_router_executor(router)               # Microsoft Agent Framework
spec, handle = openai_tool_spec("route_request"), openai_tool_handler(router)   # function calling
decision, result = route_and_execute(router, text, history=..., context=..., execute=True)
```

## Personas and skills

- Personas: `load_personas(path)` accepts a directory of `*.md`, `.json`, `.jsonl` or `.csv`;
  `persona_target(name, prompt, description, domains=..., primary=False)`. Their `instructions` are
  composed into `context["system"]` as `# Persona: <name>` when they fill a plan slot.
- Skills: `load_skills(root)` globs `*/SKILL.md`; `load_skill(dir)` loads one.

### Authoring a SKILL.md the router can select

```markdown
---
name: sql-reporting                      # must equal the directory name; ^[a-z0-9]+(-[a-z0-9]+)*$, <= 64
description: Writes warehouse SQL for recurring reports. Use for SQL or analytics questions.   # <= 1024
license: Apache-2.0
metadata:
  osr-domains: "data_analysis coding"    # routing hints (space/comma separated)
  osr-languages: "en"
  osr-tags: "sql"
  osr-quality-prior: "0.82"
  osr-primary: "false"                   # slot-only: layered on a model in the plan
  osr-latency-ms: "0"
  osr-requires: "warehouse-schema"       # optional skill-graph edges, space/comma separated ids
  osr-conflicts: ""
  osr-composes: "chart-builder"
---
# Body = instructions, disclosed only when the skill is selected (progressive disclosure)
```
Only `name`/`description` are used for routing; write the description for the *request* the skill
should attract (it seeds `examples`, one per sentence). `osr-domains`/`osr-actions` must use the
signal ontology names (`signals.DOMAIN_LEXICON` / `ACTION_LEXICON`, e.g. `coding`, `data_analysis`,
`code_generation`, `reasoning`); an unmatched declared scope is penalised, so omit `osr-actions`
when the skill spans many request types. Validate with `osr skills <root>`; register with
`registry.add(skill)` or `--skills <root>` on the CLI; the router fills the plan's skill slot
(`router.route(req, plan=True)`) or scores skills directly via `candidates=[...]`.
`osr-requires` / `osr-conflicts` / `osr-composes` feed `discovery.SkillGraph(load_skills(root))`;
`graph.compose(["sql-reporting"])` returns the dependency-ordered skill set and the ids dropped for
conflicts, and `graph.observe(skill_ids, success)` learns co-usage companions.

## OpenAI-compatible proxy

`osr -t targets.yaml serve` exposes `POST /v1/chat/completions`; `model: "auto"` (or
`"opensmartroute"`) lets the router pick, a real target id pins it. See the deploy skill.
