Skip to content

Commit 2348225

Browse files
Merge pull request #7 from AgoraIO-Conversational-AI/improv/auth_session
Improv/auth session
2 parents 12631c4 + e6618b3 commit 2348225

8 files changed

Lines changed: 362 additions & 37 deletions

File tree

README.md

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ pip install agora-agent-sdk
1313

1414
## Quick Start
1515

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

1818
```python
1919
from agora_agent import Agora, Area
20-
from agora_agent.agentkit import Agent
20+
from agora_agent.agentkit import Agent, expires_in_hours
2121
from agora_agent.agentkit.vendors import OpenAI, ElevenLabsTTS, DeepgramSTT
2222

2323
client = Agora(
@@ -28,34 +28,113 @@ client = Agora(
2828

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

36-
session = agent.create_session(client, channel="support-room-123", agent_uid="1", remote_uids=["100"])
37-
agent_id = session.start()
38-
session.say("Hello! How can I help you today?")
38+
session = agent.create_session(
39+
client,
40+
channel="support-room-123",
41+
agent_uid="1",
42+
remote_uids=["100"],
43+
expires_in=expires_in_hours(12), # optional — default is 24 h (Agora max)
44+
)
45+
46+
# start() returns a session ID unique to this agent session
47+
agent_session_id = session.start()
48+
49+
# In production, stop is typically called when your client signals the session has ended.
50+
# Your server receives that request and calls session.stop().
51+
session.stop()
52+
```
53+
54+
For async usage, use `AsyncAgora` and `await session.start()`, etc. See [Quick Start](docs/getting-started/quick-start.md).
55+
56+
### Session lifecycle
57+
58+
`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.
59+
60+
There are two ways to stop a session depending on how your server is structured:
61+
62+
**Option 1 — Hold the session in memory:**
63+
64+
```python
65+
# start-session handler
66+
agent_session_id = session.start() # unique ID for this session
67+
# stop-session handler (same process, session still in scope)
3968
session.stop()
4069
```
4170

42-
For async usage, use `AsyncAgora` and `await session.start()`, `await session.say()`, etc. See [Quick Start](docs/getting-started/quick-start.md).
71+
**Option 2 — Store the session ID and stop by ID (stateless servers):**
72+
73+
```python
74+
# start-session handler: return session ID to your client app
75+
agent_session_id = session.start()
76+
# ... return agent_session_id to client ...
77+
78+
# stop-session handler: client sends back agent_session_id
79+
client = Agora(area=Area.US, app_id="...", app_certificate="...")
80+
client.stop_agent(agent_session_id)
81+
```
82+
83+
### Manual tokens (for debugging)
84+
85+
Generate tokens yourself and pass them in — useful when inspecting or reusing tokens:
86+
87+
```python
88+
from agora_agent import Agora, Area
89+
from agora_agent.agentkit.token import generate_convo_ai_token, expires_in_hours
90+
91+
APP_ID = "your-app-id"
92+
APP_CERT = "your-app-certificate"
93+
CHANNEL = "support-room-123"
94+
AGENT_UID = "1"
95+
96+
# Auth header token — used by the SDK to authenticate REST API calls
97+
auth_token = generate_convo_ai_token(
98+
app_id=APP_ID, app_certificate=APP_CERT,
99+
channel_name=CHANNEL, account=AGENT_UID,
100+
token_expire=expires_in_hours(12),
101+
)
102+
103+
# Channel join token — embedded in the start request so the agent can join the channel
104+
join_token = generate_convo_ai_token(
105+
app_id=APP_ID, app_certificate=APP_CERT,
106+
channel_name=CHANNEL, account=AGENT_UID,
107+
token_expire=expires_in_hours(12),
108+
)
109+
110+
client = Agora(
111+
area=Area.US,
112+
app_id=APP_ID,
113+
app_certificate=APP_CERT,
114+
auth_token=auth_token, # SDK sets Authorization: agora token=<auth_token>
115+
)
116+
117+
session = agent.create_session(
118+
client, channel=CHANNEL, agent_uid=AGENT_UID, remote_uids=["100"],
119+
token=join_token, # channel join token
120+
)
121+
```
43122

44123
## Documentation
45124

46-
| Topic | Link |
47-
|-------|------|
48-
| **API docs** | [docs.agora.io](https://docs.agora.io/en/conversational-ai/overview) |
49-
| **Installation** | [docs/getting-started/installation.md](docs/getting-started/installation.md) |
125+
| Topic | Link |
126+
| ------------------ | -------------------------------------------------------------------------------- |
127+
| **API docs** | [docs.agora.io](https://docs.agora.io/en/conversational-ai/overview) |
128+
| **Installation** | [docs/getting-started/installation.md](docs/getting-started/installation.md) |
50129
| **Authentication** | [docs/getting-started/authentication.md](docs/getting-started/authentication.md) |
51-
| **Quick Start** | [docs/getting-started/quick-start.md](docs/getting-started/quick-start.md) |
52-
| **Cascading flow** | [docs/guides/cascading-flow.md](docs/guides/cascading-flow.md) |
53-
| **MLLM flow** | [docs/guides/mllm-flow.md](docs/guides/mllm-flow.md) |
54-
| **Low-level API** | [docs/guides/low-level-api.md](docs/guides/low-level-api.md) |
55-
| **Error handling** | [docs/guides/error-handling.md](docs/guides/error-handling.md) |
56-
| **Pagination** | [docs/guides/pagination.md](docs/guides/pagination.md) |
57-
| **Advanced** | [docs/guides/advanced.md](docs/guides/advanced.md) |
58-
| **API reference** | [reference.md](reference.md) |
130+
| **Quick Start** | [docs/getting-started/quick-start.md](docs/getting-started/quick-start.md) |
131+
| **Cascading flow** | [docs/guides/cascading-flow.md](docs/guides/cascading-flow.md) |
132+
| **MLLM flow** | [docs/guides/mllm-flow.md](docs/guides/mllm-flow.md) |
133+
| **Low-level API** | [docs/guides/low-level-api.md](docs/guides/low-level-api.md) |
134+
| **Error handling** | [docs/guides/error-handling.md](docs/guides/error-handling.md) |
135+
| **Pagination** | [docs/guides/pagination.md](docs/guides/pagination.md) |
136+
| **Advanced** | [docs/guides/advanced.md](docs/guides/advanced.md) |
137+
| **API reference** | [reference.md](reference.md) |
59138

60139
## Contributing
61140

docs/getting-started/authentication.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,35 @@ client = AsyncAgora(
6868
)
6969
```
7070

71+
## Pre-built token
72+
73+
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:
74+
75+
```python
76+
from agora_agent import Agora, Area
77+
from agora_agent.agentkit.token import generate_convo_ai_token
78+
79+
raw_token = generate_convo_ai_token(
80+
app_id='your-app-id',
81+
app_certificate='your-app-certificate',
82+
channel_name='your-channel',
83+
account='1',
84+
)
85+
86+
client = Agora(
87+
area=Area.US,
88+
app_id='your-app-id',
89+
app_certificate='your-app-certificate',
90+
auth_token=raw_token, # SDK sets Authorization: agora token=<raw_token>
91+
)
92+
```
93+
7194
## Auth Mode Comparison
7295

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

78102
## Advanced: Manual Token Generation
@@ -88,16 +112,16 @@ rtc_token = generate_rtc_token(
88112
app_certificate='your-app-certificate',
89113
channel='your-channel',
90114
uid=1,
91-
expiry_seconds=3600,
115+
expiry_seconds=86400, # default; max allowed by Agora is 24 hours (86400 s)
92116
)
93117

94-
# ConvoAI token (RTC + RTM combined, for REST API auth)
118+
# ConvoAI token (RTC + RTM combined, for REST API auth and channel join)
95119
convo_token = generate_convo_ai_token(
96120
app_id='your-app-id',
97121
app_certificate='your-app-certificate',
98122
channel_name='your-channel',
99123
account='1001',
100-
token_expire=3600,
124+
token_expire=86400, # default; max allowed by Agora is 24 hours (86400 s)
101125
)
102126
auth_header = f'agora token={convo_token}'
103127
```
@@ -111,7 +135,7 @@ auth_header = f'agora token={convo_token}'
111135
| `channel` | `str` | Yes || Channel name |
112136
| `uid` | `int` | Yes || User ID (0 = any) |
113137
| `role` | `int` | No | `ROLE_PUBLISHER` (1) | RTC role (`ROLE_PUBLISHER` or `ROLE_SUBSCRIBER`) |
114-
| `expiry_seconds` | `int` | No | `3600` | Token expiry in seconds |
138+
| `expiry_seconds` | `int` | No | `86400` | Token expiry in seconds (max: 86400 = 24 h) |
115139

116140
### `generate_convo_ai_token()` Reference
117141

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

151+
## Token expiry
152+
153+
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()`:
154+
155+
```python
156+
from agora_agent.agentkit import expires_in_hours, expires_in_minutes
157+
158+
session = agent.create_session(
159+
client,
160+
channel='room-123',
161+
agent_uid='1',
162+
remote_uids=['100'],
163+
expires_in=expires_in_hours(12), # 12-hour token
164+
# expires_in=expires_in_minutes(30), # 30-minute token
165+
)
166+
```
167+
168+
`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**.
169+
127170
## Areas
128171

129172
The `area` parameter determines which Agora region your requests are routed to:

docs/reference/agent.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ create_session(
115115
token: Optional[str] = None,
116116
idle_timeout: Optional[int] = None,
117117
enable_string_uid: Optional[bool] = None,
118+
expires_in: Optional[int] = None,
118119
) -> AgentSession
119120
```
120121

@@ -127,7 +128,8 @@ Creates an `AgentSession` bound to the given client and channel.
127128
| `agent_uid` | `str` | Yes | UID for the agent |
128129
| `remote_uids` | `List[str]` | Yes | UIDs of remote participants |
129130
| `name` | `Optional[str]` | No | Session name (defaults to agent name) |
130-
| `token` | `Optional[str]` | No | Pre-built RTC token |
131+
| `token` | `Optional[str]` | No | Pre-built RTC+RTM token |
132+
| `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. |
131133
| `idle_timeout` | `Optional[int]` | No | Idle timeout in seconds |
132134
| `enable_string_uid` | `Optional[bool]` | No | Enable string UIDs |
133135

src/agora_agent/agentkit/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
from .agent import Agent
22
from .agent_session import AgentSession, AgentSessionOptions, AsyncAgentSession
3-
from .token import GenerateConvoAITokenOptions, GenerateTokenOptions, generate_convo_ai_token, generate_rtc_token
3+
from .token import (
4+
GenerateConvoAITokenOptions,
5+
GenerateTokenOptions,
6+
MAX_EXPIRY_SECONDS,
7+
generate_convo_ai_token,
8+
generate_rtc_token,
9+
expires_in_hours,
10+
expires_in_minutes,
11+
)
412
from .vendors import (
513
AkoolAvatar,
614
AmazonSTT,
@@ -51,6 +59,9 @@
5159
"GenerateTokenOptions",
5260
"generate_convo_ai_token",
5361
"GenerateConvoAITokenOptions",
62+
"MAX_EXPIRY_SECONDS",
63+
"expires_in_hours",
64+
"expires_in_minutes",
5465
"BaseLLM",
5566
"BaseTTS",
5667
"BaseSTT",

src/agora_agent/agentkit/agent.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..agents.types.start_agents_request_properties_turn_detection import StartAgentsRequestPropertiesTurnDetection
1111
from ..agents.types.start_agents_request_properties_sal import StartAgentsRequestPropertiesSal
1212
from ..agents.types.start_agents_request_properties_parameters import StartAgentsRequestPropertiesParameters
13-
from .token import generate_rtc_token
13+
from .token import generate_convo_ai_token, _validate_expires_in
1414
from .vendors.base import BaseAvatar, BaseLLM, BaseMLLM, BaseSTT, BaseTTS
1515

1616
TurnDetectionConfig = StartAgentsRequestPropertiesTurnDetection
@@ -178,6 +178,7 @@ def create_session(
178178
token: typing.Optional[str] = None,
179179
idle_timeout: typing.Optional[int] = None,
180180
enable_string_uid: typing.Optional[bool] = None,
181+
expires_in: typing.Optional[int] = None,
181182
) -> "AgentSession":
182183
from .agent_session import AgentSession
183184

@@ -194,6 +195,7 @@ def create_session(
194195
remote_uids=remote_uids,
195196
idle_timeout=idle_timeout,
196197
enable_string_uid=enable_string_uid,
198+
expires_in=expires_in,
197199
)
198200

199201
def create_async_session(
@@ -206,6 +208,7 @@ def create_async_session(
206208
token: typing.Optional[str] = None,
207209
idle_timeout: typing.Optional[int] = None,
208210
enable_string_uid: typing.Optional[bool] = None,
211+
expires_in: typing.Optional[int] = None,
209212
) -> "AsyncAgentSession":
210213
"""Create an async session for use with :class:`~agora_agent.AsyncAgora`.
211214
@@ -227,6 +230,7 @@ def create_async_session(
227230
remote_uids=remote_uids,
228231
idle_timeout=idle_timeout,
229232
enable_string_uid=enable_string_uid,
233+
expires_in=expires_in,
230234
)
231235

232236
def to_properties(
@@ -239,17 +243,23 @@ def to_properties(
239243
token: typing.Optional[str] = None,
240244
app_id: typing.Optional[str] = None,
241245
app_certificate: typing.Optional[str] = None,
242-
token_expiry_seconds: typing.Optional[int] = None,
246+
expires_in: typing.Optional[int] = None,
243247
) -> StartAgentsRequestProperties:
244248
if token is None:
245249
if app_id is None or app_certificate is None:
246250
raise ValueError("Either token or app_id+app_certificate must be provided")
247-
token = generate_rtc_token(
251+
validated_expires_in = _validate_expires_in(expires_in) if expires_in is not None else None
252+
# Use generate_convo_ai_token (RTC + RTM) so the token works whether or
253+
# not the caller enables advanced_features.enable_rtm.
254+
token_kwargs: typing.Dict[str, typing.Any] = {}
255+
if validated_expires_in is not None:
256+
token_kwargs["token_expire"] = validated_expires_in
257+
token = generate_convo_ai_token(
248258
app_id=app_id,
249259
app_certificate=app_certificate,
250-
channel=channel,
251-
uid=int(agent_uid),
252-
expiry_seconds=token_expiry_seconds or 3600,
260+
channel_name=channel,
261+
account=agent_uid,
262+
**token_kwargs,
253263
)
254264

255265
is_mllm_mode = (

0 commit comments

Comments
 (0)