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
117 changes: 98 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ pip install agora-agent-sdk

## Quick Start

Use the **builder pattern** with `Agent` and `AgentSession`:
Use the **builder pattern** with `Agent` and `AgentSession`. The SDK auto-generates all required tokens:

```python
from agora_agent import Agora, Area
from agora_agent.agentkit import Agent
from agora_agent.agentkit import Agent, expires_in_hours
from agora_agent.agentkit.vendors import OpenAI, ElevenLabsTTS, DeepgramSTT

client = Agora(
Expand All @@ -28,34 +28,113 @@ client = Agora(

agent = (
Agent(name="support-assistant", instructions="You are a helpful voice assistant.")
# Create Agent: STT → LLM → TTS → (optional) Avatar
.with_stt(DeepgramSTT(api_key="your-deepgram-key", language="en-US"))
.with_llm(OpenAI(api_key="your-openai-key", model="gpt-4o-mini"))
.with_tts(ElevenLabsTTS(key="your-elevenlabs-key", model_id="eleven_flash_v2_5", voice_id="your-voice-id", sample_rate=24000))
.with_stt(DeepgramSTT(api_key="your-deepgram-key", language="en-US"))
# .with_avatar(HeyGenAvatar(...)) # optional
)

session = agent.create_session(client, channel="support-room-123", agent_uid="1", remote_uids=["100"])
agent_id = session.start()
session.say("Hello! How can I help you today?")
session = agent.create_session(
client,
channel="support-room-123",
agent_uid="1",
remote_uids=["100"],
expires_in=expires_in_hours(12), # optional — default is 24 h (Agora max)
)

# start() returns a session ID unique to this agent session
agent_session_id = session.start()

# In production, stop is typically called when your client signals the session has ended.
# Your server receives that request and calls session.stop().
session.stop()
```

For async usage, use `AsyncAgora` and `await session.start()`, etc. See [Quick Start](docs/getting-started/quick-start.md).

### Session lifecycle

`start()` joins the agent to the channel and returns a **session ID** — a unique identifier for this agent session. The session stays active until `stop()` is called.

There are two ways to stop a session depending on how your server is structured:

**Option 1 — Hold the session in memory:**

```python
# start-session handler
agent_session_id = session.start() # unique ID for this session
# stop-session handler (same process, session still in scope)
session.stop()
```

For async usage, use `AsyncAgora` and `await session.start()`, `await session.say()`, etc. See [Quick Start](docs/getting-started/quick-start.md).
**Option 2 — Store the session ID and stop by ID (stateless servers):**

```python
# start-session handler: return session ID to your client app
agent_session_id = session.start()
# ... return agent_session_id to client ...

# stop-session handler: client sends back agent_session_id
client = Agora(area=Area.US, app_id="...", app_certificate="...")
client.stop_agent(agent_session_id)
```

### Manual tokens (for debugging)

Generate tokens yourself and pass them in — useful when inspecting or reusing tokens:

```python
from agora_agent import Agora, Area
from agora_agent.agentkit.token import generate_convo_ai_token, expires_in_hours

APP_ID = "your-app-id"
APP_CERT = "your-app-certificate"
CHANNEL = "support-room-123"
AGENT_UID = "1"

# Auth header token — used by the SDK to authenticate REST API calls
auth_token = generate_convo_ai_token(
app_id=APP_ID, app_certificate=APP_CERT,
channel_name=CHANNEL, account=AGENT_UID,
token_expire=expires_in_hours(12),
)

# Channel join token — embedded in the start request so the agent can join the channel
join_token = generate_convo_ai_token(
app_id=APP_ID, app_certificate=APP_CERT,
channel_name=CHANNEL, account=AGENT_UID,
token_expire=expires_in_hours(12),
)

client = Agora(
area=Area.US,
app_id=APP_ID,
app_certificate=APP_CERT,
auth_token=auth_token, # SDK sets Authorization: agora token=<auth_token>
)

session = agent.create_session(
client, channel=CHANNEL, agent_uid=AGENT_UID, remote_uids=["100"],
token=join_token, # channel join token
)
```

## Documentation

| Topic | Link |
|-------|------|
| **API docs** | [docs.agora.io](https://docs.agora.io/en/conversational-ai/overview) |
| **Installation** | [docs/getting-started/installation.md](docs/getting-started/installation.md) |
| Topic | Link |
| ------------------ | -------------------------------------------------------------------------------- |
| **API docs** | [docs.agora.io](https://docs.agora.io/en/conversational-ai/overview) |
| **Installation** | [docs/getting-started/installation.md](docs/getting-started/installation.md) |
| **Authentication** | [docs/getting-started/authentication.md](docs/getting-started/authentication.md) |
| **Quick Start** | [docs/getting-started/quick-start.md](docs/getting-started/quick-start.md) |
| **Cascading flow** | [docs/guides/cascading-flow.md](docs/guides/cascading-flow.md) |
| **MLLM flow** | [docs/guides/mllm-flow.md](docs/guides/mllm-flow.md) |
| **Low-level API** | [docs/guides/low-level-api.md](docs/guides/low-level-api.md) |
| **Error handling** | [docs/guides/error-handling.md](docs/guides/error-handling.md) |
| **Pagination** | [docs/guides/pagination.md](docs/guides/pagination.md) |
| **Advanced** | [docs/guides/advanced.md](docs/guides/advanced.md) |
| **API reference** | [reference.md](reference.md) |
| **Quick Start** | [docs/getting-started/quick-start.md](docs/getting-started/quick-start.md) |
| **Cascading flow** | [docs/guides/cascading-flow.md](docs/guides/cascading-flow.md) |
| **MLLM flow** | [docs/guides/mllm-flow.md](docs/guides/mllm-flow.md) |
| **Low-level API** | [docs/guides/low-level-api.md](docs/guides/low-level-api.md) |
| **Error handling** | [docs/guides/error-handling.md](docs/guides/error-handling.md) |
| **Pagination** | [docs/guides/pagination.md](docs/guides/pagination.md) |
| **Advanced** | [docs/guides/advanced.md](docs/guides/advanced.md) |
| **API reference** | [reference.md](reference.md) |

## Contributing

Expand Down
53 changes: 48 additions & 5 deletions docs/getting-started/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,35 @@ client = AsyncAgora(
)
```

## Pre-built token

Pass a manually generated `agora token=...` string via `auth_token`. Use this for debugging or when you want to control the REST API token lifecycle yourself:

```python
from agora_agent import Agora, Area
from agora_agent.agentkit.token import generate_convo_ai_token

raw_token = generate_convo_ai_token(
app_id='your-app-id',
app_certificate='your-app-certificate',
channel_name='your-channel',
account='1',
)

client = Agora(
area=Area.US,
app_id='your-app-id',
app_certificate='your-app-certificate',
auth_token=raw_token, # SDK sets Authorization: agora token=<raw_token>
)
```

## Auth Mode Comparison

| Mode | When to use | What you need |
|---|---|---|
| **App credentials** | Most applications. SDK manages ConvoAI tokens per request. | `app_id` + `app_certificate` |
| **Pre-built token** | Debugging, or when you manage the REST API token lifecycle. | `app_id` + `app_certificate` + `auth_token` |
| **Basic Auth** | When using customer-level credentials from the Agora Console. | `app_id` + `app_certificate` + `customer_id` + `customer_secret` |

## Advanced: Manual Token Generation
Expand All @@ -88,16 +112,16 @@ rtc_token = generate_rtc_token(
app_certificate='your-app-certificate',
channel='your-channel',
uid=1,
expiry_seconds=3600,
expiry_seconds=86400, # default; max allowed by Agora is 24 hours (86400 s)
)

# ConvoAI token (RTC + RTM combined, for REST API auth)
# ConvoAI token (RTC + RTM combined, for REST API auth and channel join)
convo_token = generate_convo_ai_token(
app_id='your-app-id',
app_certificate='your-app-certificate',
channel_name='your-channel',
account='1001',
token_expire=3600,
token_expire=86400, # default; max allowed by Agora is 24 hours (86400 s)
)
auth_header = f'agora token={convo_token}'
```
Expand All @@ -111,7 +135,7 @@ auth_header = f'agora token={convo_token}'
| `channel` | `str` | Yes | — | Channel name |
| `uid` | `int` | Yes | — | User ID (0 = any) |
| `role` | `int` | No | `ROLE_PUBLISHER` (1) | RTC role (`ROLE_PUBLISHER` or `ROLE_SUBSCRIBER`) |
| `expiry_seconds` | `int` | No | `3600` | Token expiry in seconds |
| `expiry_seconds` | `int` | No | `86400` | Token expiry in seconds (max: 86400 = 24 h) |

### `generate_convo_ai_token()` Reference

Expand All @@ -121,9 +145,28 @@ auth_header = f'agora token={convo_token}'
| `app_certificate` | `str` | Yes | — | Agora App Certificate |
| `channel_name` | `str` | Yes | — | Channel the agent will join |
| `account` | `str` | Yes | — | Agent UID as a string (e.g. `"1001"`) |
| `token_expire` | `int` | No | `3600` | Seconds until token expires |
| `token_expire` | `int` | No | `86400` | Seconds until token expires (max: 86400 = 24 h) |
| `privilege_expire` | `int` | No | `0` | Seconds until privileges expire (0 = same as `token_expire`) |

## Token expiry

When the SDK auto-generates a token (app credentials mode, or session without a pre-built `token`), the default lifetime is **86400 seconds (24 hours)** — the Agora maximum. You can customise this via `expires_in` on `create_session()`:

```python
from agora_agent.agentkit import expires_in_hours, expires_in_minutes

session = agent.create_session(
client,
channel='room-123',
agent_uid='1',
remote_uids=['100'],
expires_in=expires_in_hours(12), # 12-hour token
# expires_in=expires_in_minutes(30), # 30-minute token
)
```

`expires_in_hours()` and `expires_in_minutes()` validate the value and raise `ValueError` if it is ≤ 0, or warn and cap at 86400 if it exceeds 24 hours. Valid range: **1–86400 seconds**.

## Areas

The `area` parameter determines which Agora region your requests are routed to:
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ create_session(
token: Optional[str] = None,
idle_timeout: Optional[int] = None,
enable_string_uid: Optional[bool] = None,
expires_in: Optional[int] = None,
) -> AgentSession
```

Expand All @@ -127,7 +128,8 @@ Creates an `AgentSession` bound to the given client and channel.
| `agent_uid` | `str` | Yes | UID for the agent |
| `remote_uids` | `List[str]` | Yes | UIDs of remote participants |
| `name` | `Optional[str]` | No | Session name (defaults to agent name) |
| `token` | `Optional[str]` | No | Pre-built RTC token |
| `token` | `Optional[str]` | No | Pre-built RTC+RTM token |
| `expires_in` | `Optional[int]` | No | Token lifetime in seconds (default: `86400` = 24 h, Agora max). Only applies when the token is auto-generated. Use `expires_in_hours()` or `expires_in_minutes()` for clarity. Valid range: 1–86400. |
| `idle_timeout` | `Optional[int]` | No | Idle timeout in seconds |
| `enable_string_uid` | `Optional[bool]` | No | Enable string UIDs |

Expand Down
13 changes: 12 additions & 1 deletion src/agora_agent/agentkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from .agent import Agent
from .agent_session import AgentSession, AgentSessionOptions, AsyncAgentSession
from .token import GenerateConvoAITokenOptions, GenerateTokenOptions, generate_convo_ai_token, generate_rtc_token
from .token import (
GenerateConvoAITokenOptions,
GenerateTokenOptions,
MAX_EXPIRY_SECONDS,
generate_convo_ai_token,
generate_rtc_token,
expires_in_hours,
expires_in_minutes,
)
from .vendors import (
AkoolAvatar,
AmazonSTT,
Expand Down Expand Up @@ -51,6 +59,9 @@
"GenerateTokenOptions",
"generate_convo_ai_token",
"GenerateConvoAITokenOptions",
"MAX_EXPIRY_SECONDS",
"expires_in_hours",
"expires_in_minutes",
"BaseLLM",
"BaseTTS",
"BaseSTT",
Expand Down
22 changes: 16 additions & 6 deletions src/agora_agent/agentkit/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ..agents.types.start_agents_request_properties_turn_detection import StartAgentsRequestPropertiesTurnDetection
from ..agents.types.start_agents_request_properties_sal import StartAgentsRequestPropertiesSal
from ..agents.types.start_agents_request_properties_parameters import StartAgentsRequestPropertiesParameters
from .token import generate_rtc_token
from .token import generate_convo_ai_token, _validate_expires_in
from .vendors.base import BaseAvatar, BaseLLM, BaseMLLM, BaseSTT, BaseTTS

TurnDetectionConfig = StartAgentsRequestPropertiesTurnDetection
Expand Down Expand Up @@ -178,6 +178,7 @@ def create_session(
token: typing.Optional[str] = None,
idle_timeout: typing.Optional[int] = None,
enable_string_uid: typing.Optional[bool] = None,
expires_in: typing.Optional[int] = None,
) -> "AgentSession":
from .agent_session import AgentSession

Expand All @@ -194,6 +195,7 @@ def create_session(
remote_uids=remote_uids,
idle_timeout=idle_timeout,
enable_string_uid=enable_string_uid,
expires_in=expires_in,
)

def create_async_session(
Expand All @@ -206,6 +208,7 @@ def create_async_session(
token: typing.Optional[str] = None,
idle_timeout: typing.Optional[int] = None,
enable_string_uid: typing.Optional[bool] = None,
expires_in: typing.Optional[int] = None,
) -> "AsyncAgentSession":
"""Create an async session for use with :class:`~agora_agent.AsyncAgora`.

Expand All @@ -227,6 +230,7 @@ def create_async_session(
remote_uids=remote_uids,
idle_timeout=idle_timeout,
enable_string_uid=enable_string_uid,
expires_in=expires_in,
)

def to_properties(
Expand All @@ -239,17 +243,23 @@ def to_properties(
token: typing.Optional[str] = None,
app_id: typing.Optional[str] = None,
app_certificate: typing.Optional[str] = None,
token_expiry_seconds: typing.Optional[int] = None,
expires_in: typing.Optional[int] = None,
) -> StartAgentsRequestProperties:
if token is None:
if app_id is None or app_certificate is None:
raise ValueError("Either token or app_id+app_certificate must be provided")
token = generate_rtc_token(
validated_expires_in = _validate_expires_in(expires_in) if expires_in is not None else None
# Use generate_convo_ai_token (RTC + RTM) so the token works whether or
# not the caller enables advanced_features.enable_rtm.
token_kwargs: typing.Dict[str, typing.Any] = {}
if validated_expires_in is not None:
token_kwargs["token_expire"] = validated_expires_in
token = generate_convo_ai_token(
app_id=app_id,
app_certificate=app_certificate,
channel=channel,
uid=int(agent_uid),
expiry_seconds=token_expiry_seconds or 3600,
channel_name=channel,
account=agent_uid,
**token_kwargs,
)

is_mllm_mode = (
Expand Down
Loading