Skip to content

Commit 4b540c9

Browse files
committed
happy tests
1 parent b05a60a commit 4b540c9

File tree

10 files changed

+3337
-433
lines changed

10 files changed

+3337
-433
lines changed

docs/ai/instructions/ai-plugin.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11

22
# Plugin Development Guide
33

4+
## Example Plugin
5+
6+
An example plugin is located in `plugins/example`. Copying the example is the best way to create a new plugin. After copying the example be sure to:
7+
8+
- Update the folder name "example" to your plugin's name
9+
- Open `pyproject.toml` and update the name, description etc
10+
- Update the event types in your `events.py` file
11+
- Register your events in the plugin's `__init__` method
12+
413
## Folder Structure
514

615
Every plugin should follow this structure, an example for the plugin named elevenlabs:
@@ -10,6 +19,8 @@ Every plugin should follow this structure, an example for the plugin named eleve
1019
- pyproject.toml
1120
- README.md
1221
- py.typed
22+
- tests
23+
- example
1324
- vision_agents/plugins/
1425
- elevenlabs/
1526
- __init__.py
@@ -31,23 +42,13 @@ tts = elevenlabs.TTS()
3142
llm = anthropic.LLM()
3243
```
3344

34-
## Example Plugin
35-
36-
An example plugin is located in `plugins/example`. Copying the example is the best way to create a new plugin. After copying the example be sure to:
37-
38-
- Update the folder name "example" to your plugin's name
39-
- Open `pyproject.toml` and update the name, description etc
40-
- Update the event types in your `events.py` file
41-
- Register your events in the plugin's `__init__` method
42-
4345
## Guidelines
4446

4547
When building the plugin read these guides:
4648

4749
- **TTS**: [ai-tts.md](ai-tts.md)
4850
- **STT**: [ai-stt.md](ai-stt.md)
49-
- **STS/realtime/LLM**: [ai-llm.md](ai-llm.md)
50-
- **Video processor**: [ai-video-processor.md](ai-video-processor.md)
51+
- **STS/realtime/LLM**: [ai-llm.md](ai-llm.md) or [ai-realtime-llm.md](ai-realtime-llm.md)
5152

5253
## Update pyproject.toml
5354

plugins/aws/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,19 @@ agent = Agent(
4141
)
4242
```
4343

44+
## Running the examples
45+
46+
Create a `.env` file, or cp .env.example to .env and fill in
47+
48+
```
49+
STREAM_API_KEY=your_stream_api_key_here
50+
STREAM_API_SECRET=your_stream_api_secret_here
51+
52+
AWS_BEARER_TOKEN_BEDROCK=
53+
AWS_ACCESS_KEY_ID=
54+
AWS_SECRET_ACCESS_KEY=
55+
56+
FAL_KEY=
57+
CARTESIA_API_KEY=
58+
DEEPGRAM_API_KEY=
59+
```

plugins/aws/example/.env.example

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
# Stream API credentials
21
STREAM_API_KEY=your_stream_api_key_here
32
STREAM_API_SECRET=your_stream_api_secret_here
4-
EXAMPLE_BASE_URL=https://pronto.getstream.io
53

6-
# Deepgram API credentials
7-
DEEPGRAM_API_KEY=your_deepgram_api_key_here
4+
AWS_BEARER_TOKEN_BEDROCK=
5+
AWS_ACCESS_KEY_ID=
6+
AWS_SECRET_ACCESS_KEY=
7+
8+
FAL_KEY=
9+
CARTESIA_API_KEY=
10+
DEEPGRAM_API_KEY=
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Stream API credentials
2+
STREAM_API_KEY=your_stream_api_key_here
3+
STREAM_API_SECRET=your_stream_api_secret_here
4+
5+
AWS_BEARER_TOKEN_BEDROCK=
6+
AWS_ACCESS_KEY_ID=
7+
AWS_SECRET_ACCESS_KEY=
8+

plugins/example/example/__init__.py

Whitespace-only changes.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import asyncio
2+
import logging
3+
from uuid import uuid4
4+
5+
from dotenv import load_dotenv
6+
7+
from vision_agents.core import User
8+
from vision_agents.core.agents import Agent
9+
from vision_agents.plugins import aws, getstream, cartesia, deepgram, smart_turn
10+
11+
load_dotenv()
12+
13+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s [call_id=%(call_id)s] %(name)s: %(message)s")
14+
logger = logging.getLogger(__name__)
15+
16+
17+
async def start_agent() -> None:
18+
agent = Agent(
19+
edge=getstream.Edge(),
20+
agent_user=User(name="Friendly AI"),
21+
instructions="Be nice to the user",
22+
llm=aws.LLM(model="qwen.qwen3-32b-v1:0"),
23+
tts=cartesia.TTS(),
24+
stt=deepgram.STT(),
25+
turn_detection=smart_turn.TurnDetection(buffer_duration=2.0, confidence_threshold=0.5),
26+
# Enable turn detection with FAL/ Smart turn
27+
)
28+
await agent.create_user()
29+
30+
call = agent.edge.client.video.call("default", str(uuid4()))
31+
await agent.edge.open_demo(call)
32+
33+
with await agent.join(call):
34+
await asyncio.sleep(5)
35+
await agent.llm.simple_response(text="Say hi")
36+
await agent.finish()
37+
38+
39+
if __name__ == "__main__":
40+
asyncio.run(start_agent())
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[project]
2+
name = "my-example"
3+
version = "0.0.0"
4+
requires-python = ">=3.10"
5+
6+
# put only what this example needs
7+
dependencies = [
8+
"python-dotenv>=1.0",
9+
"vision-agents-plugins-gemini",
10+
"vision-agents-plugins-getstream",
11+
"vision-agents",
12+
"google-genai>=1.33.0",
13+
"opentelemetry-exporter-otlp>=1.37.0",
14+
"opentelemetry-exporter-prometheus>=0.58b0",
15+
"prometheus-client>=0.23.1",
16+
"opentelemetry-sdk>=1.37.0",
17+
]
18+
19+
[tool.uv.sources]
20+
"vision-agents-plugins-getstream" = {path = "../../../plugins/getstream", editable=true}
21+
"vision-agents-plugins-gemini" = {path = "../../../plugins/gemini", editable=true}
22+
"vision-agents" = {path = "../../../agents-core", editable=true}

plugins/example/example/uv.lock

Lines changed: 3232 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/test_events.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -270,48 +270,6 @@ async def another_handler(event: AnotherEvent):
270270
assert not any("Called handler another_handler" in msg for msg in log_messages)
271271

272272

273-
@pytest.mark.asyncio
274-
async def test_silent_suppresses_handler_logging(caplog):
275-
"""Test that marking an event as silent suppresses the 'Called handler' log message."""
276-
import logging
277-
278-
manager = EventManager()
279-
manager.register(ValidEvent)
280-
manager.register(AnotherEvent)
281-
282-
handler_called = []
283-
284-
@manager.subscribe
285-
async def valid_handler(event: ValidEvent):
286-
handler_called.append("valid")
287-
288-
@manager.subscribe
289-
async def another_handler(event: AnotherEvent):
290-
handler_called.append("another")
291-
292-
# Mark ValidEvent as silent
293-
manager.silent(ValidEvent)
294-
295-
# Capture logs at INFO level
296-
with caplog.at_level(logging.INFO):
297-
# Send both events
298-
manager.send(ValidEvent(field=42))
299-
manager.send(AnotherEvent(value="test"))
300-
await manager.wait()
301-
302-
# Both handlers should have been called
303-
assert handler_called == ["valid", "another"]
304-
305-
# Check log messages
306-
log_messages = [record.message for record in caplog.records]
307-
308-
# Should NOT see "Called handler" for ValidEvent (it's silent)
309-
assert not any("Called handler valid_handler" in msg and "custom.validevent" in msg for msg in log_messages)
310-
311-
# SHOULD see "Called handler" for AnotherEvent (not silent)
312-
assert any("Called handler another_handler" in msg and "custom.anotherevent" in msg for msg in log_messages)
313-
314-
315273
@pytest.mark.asyncio
316274
@pytest.mark.integration
317275
async def test_protobuf_events_with_base_event():

0 commit comments

Comments
 (0)