Skip to content
Open
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
21 changes: 21 additions & 0 deletions docs/agents/reference/agent-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ Names must match `^[a-zA-Z_][a-zA-Z0-9_-]*$`. Empty models represent inherited o
external-agent behavior. The complete constructor and serialization semantics are
maintained in [api-reference.md](../api-reference.md) and
`AgentConfigSerializer`; use those sources when adding a newly supported field.

## Jev agents

Use `JevAgent(name, model="jev-1.13", questions=questions)` or
`AgentDef(name=name, kind="jev", model="jev-1.13", questions=questions)`.
Questions are dictionaries with `instructions` and a `type`:
`choice` uses a `choices` map, `score` uses an ordered `scale`, and `boolean`
returns a probability. Omit questions to supply `context={"questions": questions}`.

Use `runtime.plan(agent, prompt)` to compile or `runtime.start(agent, prompt)`
to run. Call `handle.join()` and check `result.is_success` or `result.error`.
`result.output["result"]` preserves `model`, `answers`, `usage`, `latencyMs`
and optional `requestId`. Credentials and inference stay on Conductor.
No chat model or Python worker is needed.

[Example](../../../examples/agents/jev_agent.py)

For server-side Jev routing, use `Agent(strategy="router", router=selector,
agents=children)`. The Jev selector must have one fixed choice question whose
choice keys match the child agent names. No parent chat model is needed.
Routing runs one child and preserves its structured result.
2 changes: 2 additions & 0 deletions docs/agents/reference/agent-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
"properties": {
"name": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$" },
"model": { "type": ["string", "null"] },
"kind": { "const": "jev" },
"questions": { "type": "object", "additionalProperties": { "type": "object" } },
"baseUrl": { "type": ["string", "null"] },
"strategy": { "type": ["string", "null"] },
"maxTurns": { "type": ["integer", "null"], "minimum": 0 },
Expand Down
8 changes: 8 additions & 0 deletions examples/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ the Conductor CLI manages the server with `conductor server start`.
Framework-specific examples are in [ADK](adk/README.md),
[LangGraph](langgraph/README.md), and [OpenAI Agents SDK](openai/README.md).
Review tool side effects before using real credentials.

Jev: [jev_agent.py](jev_agent.py) compiles by default. Pass `--run` for inference.

- `jev_nested_triage.py`: Jev department selection, Jev specialist selection, then a Jev specialist.
- `luna_jev_triage.py --model INTEGRATION/luna-6`: Luna selects one of ten Jev specialists.

Both compile by default. Pass `--run` for inference. These require the server's
Jev router support and structured output for single-turn routers without synthesis.
53 changes: 53 additions & 0 deletions examples/agents/jev_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Compile a Jev agent. Pass --run for inference. Configure credentials on Conductor."""

import argparse
import json
import os

from conductor.ai.agents import AgentRuntime, JevAgent
from conductor.client.configuration.configuration import Configuration

PROMPT = "The customer reports a duplicate charge on the latest invoice."


def support_agent():
return JevAgent(
name="jev_support_agent",
model="jev-1.13",
questions={
"department": {
"type": "choice",
"instructions": "Which team should handle this issue?",
"choices": {
"billing": "Payment and invoice issues",
"technical": "Bugs and software issues",
"other": "Other requests",
},
}
},
)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--run", action="store_true", help="Start live Jev inference")
args = parser.parse_args()
config = Configuration(
server_api_url=os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api")
)
with AgentRuntime(config) as runtime:
agent = support_agent()
if not args.run:
print(json.dumps(runtime.plan(agent, PROMPT), indent=2))
return

handle = runtime.start(agent, PROMPT)
print("Execution:", handle.execution_id)
result = handle.join(timeout=120)
if not result.is_success:
raise RuntimeError(f"{result.status}: {result.error}")
print(json.dumps(result.output["result"], indent=2))


if __name__ == "__main__":
main()
86 changes: 86 additions & 0 deletions examples/agents/jev_nested_triage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Three levels: Jev department selector, Jev specialist selector, Jev specialist.

Requires server support for Jev routers. Compile by default, --run for inference.
"""

import argparse
import json
import os

from conductor.ai.agents import Agent, AgentRuntime, JevAgent, Strategy
from conductor.client.configuration.configuration import Configuration
from jev_specialists import SPECIALTIES, specialists

PROMPT = "Our latest invoice has two settled charges with different transaction IDs for the same purchase."


def routing_team(name, children, descriptions):
return Agent(
name=name,
strategy=Strategy.ROUTER,
router=JevAgent(
name=f"{name}_selector",
model="jev-1.13",
questions={
"agent": {
"type": "choice",
"instructions": "Choose the agent best suited to handle this request.",
"choices": descriptions,
}
},
),
agents=children,
max_turns=1,
synthesize=False,
)


def triage_agent():
leaves = specialists()
departments = []
for department in ("billing", "technical", "account"):
members = {
name: agent for name, agent in leaves.items() if SPECIALTIES[name][0] == department
}
departments.append(
routing_team(
f"jev_{department}_team",
list(members.values()),
{agent.name: SPECIALTIES[name][1] for name, agent in members.items()},
)
)
return routing_team(
"jev_nested_triage",
departments,
{
"jev_billing_team": "Charges, refunds and subscriptions",
"jev_technical_team": "API errors, outages, integrations and setup",
"jev_account_team": "Access, security and privacy",
},
)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--run", action="store_true")
parser.add_argument("--prompt", default=PROMPT)
args = parser.parse_args()
config = Configuration(
server_api_url=os.getenv("CONDUCTOR_SERVER_URL", "http://localhost:8080/api")
)
with AgentRuntime(config) as runtime:
agent = triage_agent()
if not args.run:
print(json.dumps(runtime.plan(agent, args.prompt), indent=2))
return
runtime.deploy(agent)
handle = runtime.start(agent, args.prompt)
print("Execution:", handle.execution_id)
result = handle.join(timeout=180)
if not result.is_success:
raise RuntimeError(result.error)
print(json.dumps(result.output, indent=2))


if __name__ == "__main__":
main()
122 changes: 122 additions & 0 deletions examples/agents/jev_specialists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Ten Jev specialists used by the routing examples."""

from conductor.ai.agents import JevAgent

SPECIALTIES = {
"duplicate_charge": (
"billing",
"Duplicate payments or repeated invoice charges",
{
"check_transactions": "Compare charge identifiers and payment status",
"refund_duplicate": "Refund a confirmed duplicate settled charge",
"explain_hold": "Explain a pending authorization hold",
},
),
"refund": (
"billing",
"Refund eligibility and refund progress",
{
"review_eligibility": "Check purchase date and refund policy",
"approve_refund": "Approve a refund supported by the supplied policy and evidence",
"trace_refund": "Trace an already approved refund",
},
),
"subscription": (
"billing",
"Subscription renewals, cancellation and plan changes",
{
"review_renewal": "Check renewal timing and notice",
"cancel_renewal": "Schedule cancellation at the end of the current term",
"change_plan": "Review the requested plan change",
},
),
"api_errors": (
"technical",
"API request failures, rate limits and invalid payloads",
{
"inspect_request": "Collect request identifiers and validate the payload",
"backoff": "Apply retry backoff for documented rate limiting",
"escalate_bug": "Escalate a reproducible server defect",
},
),
"outage": (
"technical",
"Service outages and widespread availability failures",
{
"check_scope": "Verify the affected services and customers",
"open_incident": "Open an incident for confirmed widespread impact",
"update_incident": "Attach evidence to an existing incident",
},
),
"integration": (
"technical",
"Webhook delivery and third-party integration failures",
{
"inspect_delivery": "Inspect delivery logs and endpoint responses",
"repair_config": "Correct an identified integration configuration error",
"replay_delivery": "Replay a failed delivery after checking idempotency",
},
),
"onboarding": (
"technical",
"Initial setup, configuration and first successful request",
{
"collect_requirements": "Clarify the intended setup and environment",
"provide_setup": "Provide the relevant setup steps",
"validate_setup": "Verify the completed configuration",
},
),
"access": (
"account",
"Login problems, account recovery and permissions",
{
"verify_identity": "Verify identity before changing account access",
"recover_account": "Use the approved account recovery process",
"review_permissions": "Review the required role and authorization",
},
),
"security": (
"account",
"Suspected compromise, leaked credentials and abuse",
{
"investigate": "Collect evidence and assess the scope of compromise",
"contain": "Recommend revoking confirmed compromised credentials",
"escalate_security": "Escalate an active compromise to security response",
},
),
"privacy": (
"account",
"Personal data access, export and deletion requests",
{
"verify_request": "Verify the requester and scope of the request",
"export_data": "Route a verified data export request",
"delete_data": "Route a verified deletion request through policy review",
},
),
}


def specialists():
return {
name: JevAgent(
name=f"jev_{name}",
model="jev-1.13",
questions={
"action": {
"type": "choice",
"instructions": f"Recommend the next step for {description}. Use only supplied evidence. Do not claim any action was executed.",
"choices": actions,
},
"urgency": {
"type": "score",
"instructions": "Rate urgency from low to critical based on customer impact.",
"scale": ["low", "normal", "high", "critical"],
},
"needs_more_information": {
"type": "boolean",
"instructions": "Is more information needed before taking the recommended action?",
},
},
)
for name, (_, description, actions) in SPECIALTIES.items()
}
63 changes: 63 additions & 0 deletions examples/agents/luna_jev_triage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Luna-6 selects one of ten Jev specialists using server-side routing.

Pass --model INTEGRATION/luna-6. Compile by default, --run for inference.
"""

import argparse
import json
import os

from conductor.ai.agents import Agent, AgentRuntime, Strategy
from conductor.client.configuration.configuration import Configuration
from jev_specialists import SPECIALTIES, specialists


def triage_agent(model):
candidates = specialists()
descriptions = "\n".join(
f"{agent.name}: {SPECIALTIES[name][1]}" for name, agent in candidates.items()
)
return Agent(
name="luna_jev_triage",
model=model,
strategy=Strategy.ROUTER,
router=Agent(
name="luna_jev_selector",
model=model,
instructions=f"Select exactly one specialist for the request. Return only its agent name.\n{descriptions}",
),
agents=list(candidates.values()),
max_turns=1,
synthesize=False,
)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--model", default=os.getenv("CONDUCTOR_AGENT_LLM_MODEL"))
parser.add_argument("--run", action="store_true")
parser.add_argument(
"--prompt", default="Our webhook endpoint returns 503 and order notifications are missing."
)
args = parser.parse_args()
if not args.model:
parser.error("Pass --model with your server's integration/luna-6 identifier")
config = Configuration(
server_api_url=os.getenv("CONDUCTOR_SERVER_URL", "http://localhost:8080/api")
)
with AgentRuntime(config) as runtime:
agent = triage_agent(args.model)
if not args.run:
print(json.dumps(runtime.plan(agent, args.prompt), indent=2))
return
runtime.deploy(agent)
handle = runtime.start(agent, args.prompt)
print("Execution:", handle.execution_id)
result = handle.join(timeout=180)
if not result.is_success:
raise RuntimeError(result.error)
print(json.dumps(result.output, indent=2))


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions src/conductor/ai/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def get_weather(city: str) -> str:
LocalCodeExecutor,
ServerlessCodeExecutor,
)
from conductor.ai.agents.jev import JevAgent

# Exceptions
from conductor.ai.agents.exceptions import (
Expand Down Expand Up @@ -232,6 +233,7 @@ def resolve_credentials(task: object, names: list) -> dict:
from conductor.ai.agents.tracing import is_tracing_enabled

__all__ = [
"JevAgent",
# OpenAI Agents SDK compatibility
"Runner",
"RunResult",
Expand Down
Loading
Loading