vision-agents-plugins-openrouter 0.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Distribution / packaging
8
+ .Python
9
+ build/
10
+ dist/
11
+ downloads/
12
+ develop-eggs/
13
+ eggs/
14
+ .eggs/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ share/python-wheels/
21
+ pip-wheel-metadata/
22
+ MANIFEST
23
+ *.egg-info/
24
+ *.egg
25
+
26
+ # Installer logs
27
+ pip-log.txt
28
+ pip-delete-this-directory.txt
29
+
30
+ # Unit test / coverage reports
31
+ htmlcov/
32
+ .tox/
33
+ .nox/
34
+ .coverage
35
+ .coverage.*
36
+ .cache
37
+ coverage.xml
38
+ nosetests.xml
39
+ *.cover
40
+ *.py,cover
41
+ .hypothesis/
42
+ .pytest_cache/
43
+
44
+ # Type checker / lint caches
45
+ .mypy_cache/
46
+ .dmypy.json
47
+ dmypy.json
48
+ .pytype/
49
+ .pyre/
50
+ .ruff_cache/
51
+
52
+ # Environments
53
+ .venv
54
+ env/
55
+ venv/
56
+ ENV/
57
+ env.bak/
58
+ venv.bak/
59
+ .env
60
+ .env.local
61
+ .env.*.local
62
+ .env.bak
63
+ pyvenv.cfg
64
+ .python-version
65
+
66
+ # Editors / IDEs
67
+ .vscode/
68
+ .idea/
69
+
70
+ # Jupyter Notebook
71
+ .ipynb_checkpoints/
72
+
73
+ # OS / Misc
74
+ .DS_Store
75
+ *.log
76
+
77
+ # Tooling & repo-specific
78
+ pyrightconfig.json
79
+ shell.nix
80
+ bin/*
81
+ lib/*
82
+ stream-py/
83
+
84
+ # Artifacts / assets
85
+ *.pt
86
+ *.kef
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: vision-agents-plugins-openrouter
3
+ Version: 0.1.9
4
+ Summary: OpenRouter plugin for vision agents
5
+ Project-URL: Documentation, https://visionagents.ai/
6
+ Project-URL: Website, https://visionagents.ai/
7
+ Project-URL: Source, https://github.com/GetStream/Vision-Agents
8
+ License-Expression: MIT
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: vision-agents
11
+ Requires-Dist: vision-agents-plugins-openai
12
+ Description-Content-Type: text/markdown
13
+
14
+ # OpenRouter Plugin
15
+
16
+ OpenRouter plugin for vision agents. This plugin provides LLM capabilities using OpenRouter's API, which is compatible with the OpenAI API format.
17
+
18
+ ## Note/ Issues
19
+
20
+ Instruction following doesn't always work with openrouter atm.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ uv pip install vision-agents-plugins-openrouter
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from vision_agents.plugins import openrouter, getstream, elevenlabs, cartesia, deepgram, smart_turn
32
+
33
+
34
+ agent = Agent(
35
+ edge=getstream.Edge(),
36
+ agent_user=User(name="OpenRouter AI"),
37
+ instructions="Be helpful and friendly to the user",
38
+ llm=openrouter.LLM(
39
+ model="anthropic/claude-haiku-4.5", # Can also use other models like anthropic/claude-3-opus
40
+ ),
41
+ tts=elevenlabs.TTS(),
42
+ stt=deepgram.STT(),
43
+ turn_detection=smart_turn.TurnDetection(
44
+ buffer_duration=2.0, confidence_threshold=0.5
45
+ )
46
+ )
47
+ ```
@@ -0,0 +1,34 @@
1
+ # OpenRouter Plugin
2
+
3
+ OpenRouter plugin for vision agents. This plugin provides LLM capabilities using OpenRouter's API, which is compatible with the OpenAI API format.
4
+
5
+ ## Note/ Issues
6
+
7
+ Instruction following doesn't always work with openrouter atm.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ uv pip install vision-agents-plugins-openrouter
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ from vision_agents.plugins import openrouter, getstream, elevenlabs, cartesia, deepgram, smart_turn
19
+
20
+
21
+ agent = Agent(
22
+ edge=getstream.Edge(),
23
+ agent_user=User(name="OpenRouter AI"),
24
+ instructions="Be helpful and friendly to the user",
25
+ llm=openrouter.LLM(
26
+ model="anthropic/claude-haiku-4.5", # Can also use other models like anthropic/claude-3-opus
27
+ ),
28
+ tts=elevenlabs.TTS(),
29
+ stt=deepgram.STT(),
30
+ turn_detection=smart_turn.TurnDetection(
31
+ buffer_duration=2.0, confidence_threshold=0.5
32
+ )
33
+ )
34
+ ```
@@ -0,0 +1,51 @@
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 openrouter, getstream, elevenlabs, 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
+ """Example agent using OpenRouter LLM.
19
+
20
+ This example demonstrates how to use the OpenRouter plugin with a Vision Agent.
21
+ OpenRouter provides access to multiple LLM providers through a unified API.
22
+
23
+ Set OPENROUTER_API_KEY environment variable before running.
24
+ """
25
+ agent = Agent(
26
+ edge=getstream.Edge(),
27
+ agent_user=User(name="OpenRouter AI"),
28
+ instructions="Be helpful and friendly to the user",
29
+ llm=openrouter.LLM(
30
+ model="openai/gpt-4o", # Can also use other models like anthropic/claude-3-opus
31
+ ),
32
+ tts=elevenlabs.TTS(),
33
+ stt=deepgram.STT(),
34
+ turn_detection=smart_turn.TurnDetection(
35
+ buffer_duration=2.0, confidence_threshold=0.5
36
+ )
37
+ )
38
+ await agent.create_user()
39
+
40
+ call = agent.edge.client.video.call("default", str(uuid4()))
41
+ await agent.edge.open_demo(call)
42
+
43
+ with await agent.join(call):
44
+ await asyncio.sleep(5)
45
+ await agent.llm.simple_response(text="Hello! I'm powered by OpenRouter.")
46
+ await agent.finish()
47
+
48
+
49
+ if __name__ == "__main__":
50
+ asyncio.run(start_agent())
51
+
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "openrouter-example"
3
+ version = "0.0.0"
4
+ requires-python = ">=3.10"
5
+
6
+ dependencies = [
7
+ "python-dotenv>=1.0",
8
+ "vision-agents-plugins-openrouter",
9
+ "vision-agents-plugins-getstream",
10
+ "vision-agents-plugins-elevenlabs",
11
+ "vision-agents-plugins-deepgram",
12
+ "vision-agents-plugins-cartesia",
13
+ "vision-agents-plugins-smart-turn",
14
+ "vision-agents",
15
+ ]
16
+
17
+ [tool.uv.sources]
18
+ "vision-agents-plugins-openrouter" = {path = "..", editable=true}
19
+ "vision-agents-plugins-openai" = {path = "../../openai", editable=true}
20
+ "vision-agents-plugins-getstream" = {path = "../../getstream", editable=true}
21
+ "vision-agents-plugins-elevenlabs" = {path = "../../elevenlabs", editable=true}
22
+ "vision-agents-plugins-deepgram" = {path = "../../deepgram", editable=true}
23
+ "vision-agents-plugins-cartesia" = {path = "../../cartesia", editable=true}
24
+ "vision-agents-plugins-smart-turn" = {path = "../../smart_turn", editable=true}
25
+ "vision-agents" = {path = "../../../agents-core", editable=true}
26
+
File without changes
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vision-agents-plugins-openrouter"
7
+ dynamic = ["version"]
8
+ description = "OpenRouter plugin for vision agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ dependencies = [
13
+ "vision-agents",
14
+ "vision-agents-plugins-openai",
15
+ ]
16
+
17
+ [project.urls]
18
+ Documentation = "https://visionagents.ai/"
19
+ Website = "https://visionagents.ai/"
20
+ Source = "https://github.com/GetStream/Vision-Agents"
21
+
22
+ [tool.hatch.version]
23
+ source = "vcs"
24
+ raw-options = { root = "..", search_parent_directories = true, fallback_version = "0.0.0" }
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["."]
28
+
29
+ [tool.uv.sources]
30
+ vision-agents = { workspace = true }
31
+ vision-agents-plugins-openai = { workspace = true }
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "pytest>=8.4.1",
36
+ "pytest-asyncio>=1.0.0",
37
+ ]
38
+
@@ -0,0 +1,156 @@
1
+ """Tests for OpenRouter LLM plugin."""
2
+
3
+ import os
4
+
5
+ import pytest
6
+ from dotenv import load_dotenv
7
+
8
+ from vision_agents.core.agents.conversation import Message, InMemoryConversation
9
+ from vision_agents.core.llm.events import (
10
+ LLMResponseChunkEvent,
11
+ )
12
+ from vision_agents.plugins.openrouter import LLM
13
+
14
+ load_dotenv()
15
+
16
+
17
+ class TestOpenRouterLLM:
18
+ """Test suite for OpenRouter LLM class."""
19
+
20
+ def assert_response_successful(self, response):
21
+ """Utility method to verify a response is successful.
22
+
23
+ A successful response has:
24
+ - response.text is set (not None and not empty)
25
+ - response.exception is None
26
+
27
+ Args:
28
+ response: LLMResponseEvent to check
29
+ """
30
+ assert response.text is not None, "Response text should not be None"
31
+ assert len(response.text) > 0, "Response text should not be empty"
32
+ assert not hasattr(response, "exception") or response.exception is None, (
33
+ f"Response should not have an exception, got: {getattr(response, 'exception', None)}"
34
+ )
35
+
36
+ def test_message(self):
37
+ """Test basic message normalization."""
38
+ messages = LLM._normalize_message("say hi")
39
+ assert isinstance(messages[0], Message)
40
+ message = messages[0]
41
+ assert message.original is not None
42
+ assert message.content == "say hi"
43
+
44
+ def test_advanced_message(self):
45
+ """Test advanced message format with image."""
46
+ img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/2023_06_08_Raccoon1.jpg/1599px-2023_06_08_Raccoon1.jpg"
47
+
48
+ advanced = [
49
+ {
50
+ "role": "user",
51
+ "content": [
52
+ {"type": "input_text", "text": "what do you see in this image?"},
53
+ {"type": "input_image", "image_url": f"{img_url}"},
54
+ ],
55
+ }
56
+ ]
57
+ messages = LLM._normalize_message(advanced)
58
+ assert messages[0].original is not None
59
+
60
+ @pytest.fixture
61
+ async def llm(self) -> LLM:
62
+ """Fixture for OpenRouter LLM with z-ai/glm-4.6 model."""
63
+ if not os.environ.get("OPENROUTER_API_KEY"):
64
+ pytest.skip("OPENROUTER_API_KEY environment variable not set")
65
+
66
+ llm = LLM(model="anthropic/claude-haiku-4.5")
67
+ llm._conversation = InMemoryConversation("be friendly", [])
68
+ return llm
69
+
70
+ @pytest.mark.integration
71
+ async def test_simple(self, llm: LLM):
72
+ """Test simple response generation."""
73
+ response = await llm.simple_response(
74
+ "Explain quantum computing in 1 paragraph",
75
+ )
76
+
77
+ self.assert_response_successful(response)
78
+
79
+ @pytest.mark.integration
80
+ async def test_native_api(self, llm: LLM):
81
+ """Test native OpenAI-compatible API."""
82
+ response = await llm.create_response(
83
+ input="say hi", instructions="You are a helpful assistant."
84
+ )
85
+
86
+ self.assert_response_successful(response)
87
+ assert hasattr(response.original, "id") # OpenAI-compatible response has id
88
+
89
+ @pytest.mark.integration
90
+ async def test_streaming(self, llm: LLM):
91
+ """Test streaming response."""
92
+ streamingWorks = False
93
+
94
+ @llm.events.subscribe
95
+ async def passed(event: LLMResponseChunkEvent):
96
+ nonlocal streamingWorks
97
+ streamingWorks = True
98
+
99
+ response = await llm.simple_response(
100
+ "Explain quantum computing in 1 paragraph",
101
+ )
102
+
103
+ await llm.events.wait()
104
+
105
+ self.assert_response_successful(response)
106
+ assert streamingWorks, "Streaming should have generated chunk events"
107
+
108
+ @pytest.mark.integration
109
+ async def test_memory(self, llm: LLM):
110
+ """Test conversation memory using simple_response."""
111
+ await llm.simple_response(
112
+ text="There are 2 dogs in the room",
113
+ )
114
+ response = await llm.simple_response(
115
+ text="How many paws are there in the room?",
116
+ )
117
+
118
+ self.assert_response_successful(response)
119
+ assert "8" in response.text or "eight" in response.text.lower(), (
120
+ f"Expected '8' or 'eight' in response, got: {response.text}"
121
+ )
122
+
123
+ @pytest.mark.integration
124
+ async def test_native_memory(self, llm: LLM):
125
+ """Test conversation memory using native API."""
126
+ await llm.create_response(
127
+ input="There are 2 dogs in the room",
128
+ )
129
+ response = await llm.create_response(
130
+ input="How many paws are there in the room?",
131
+ )
132
+
133
+ self.assert_response_successful(response)
134
+ assert "8" in response.text or "eight" in response.text.lower(), (
135
+ f"Expected '8' or 'eight' in response, got: {response.text}"
136
+ )
137
+
138
+ @pytest.mark.integration
139
+ async def test_instruction_following(self):
140
+ """Test that the LLM follows system instructions."""
141
+ if not os.environ.get("OPENROUTER_API_KEY"):
142
+ pytest.skip("OPENROUTER_API_KEY environment variable not set")
143
+
144
+ pytest.skip("instruction following doesnt always work")
145
+ llm = LLM(model="anthropic/claude-haiku-4.5")
146
+ llm._set_instructions("Only reply in 2 letter country shortcuts")
147
+
148
+ response = await llm.simple_response(
149
+ text="Which country is rainy, protected from water with dikes and below sea level?",
150
+ )
151
+
152
+ self.assert_response_successful(response)
153
+ assert "nl" in response.text.lower(), (
154
+ f"Expected 'NL' in response, got: {response.text}"
155
+ )
156
+
@@ -0,0 +1,5 @@
1
+
2
+ from .openrouter_llm import OpenRouterLLM as LLM
3
+
4
+ __all__ = ["LLM"]
5
+
@@ -0,0 +1,61 @@
1
+ """OpenRouter LLM implementation using OpenAI-compatible API."""
2
+ import os
3
+ from typing import Any
4
+
5
+ from vision_agents.plugins.openai import LLM as OpenAILLM
6
+
7
+
8
+ class OpenRouterLLM(OpenAILLM):
9
+ """OpenRouter LLM that extends OpenAI LLM with OpenRouter-specific configuration.
10
+
11
+ It proxies the regular models by setting base url.
12
+ It supports create response like the regular openAI API. It doesn't support conversation id, so that requires customization
13
+
14
+ TODO:
15
+ - Use manual conversation storage
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ *,
21
+ api_key: str | None = None,
22
+ base_url: str = "https://openrouter.ai/api/v1",
23
+ model: str = "openrouter/andromeda-alpha",
24
+ **kwargs: Any,
25
+ ) -> None:
26
+ """Initialize OpenRouter LLM.
27
+
28
+ Args:
29
+ api_key: OpenRouter API key. If not provided, uses OPENROUTER_API_KEY env var.
30
+ base_url: OpenRouter API base URL.
31
+ model: Model to use. Defaults to openai/gpt-4o.
32
+ **kwargs: Additional arguments passed to OpenAI LLM.
33
+ """
34
+ if api_key is None:
35
+ api_key = os.environ.get("OPENROUTER_API_KEY")
36
+ super().__init__(
37
+ api_key=api_key,
38
+ base_url=base_url,
39
+ model=model,
40
+ **kwargs,
41
+ )
42
+
43
+ async def create_conversation(self):
44
+ # Do nothing, dont call super
45
+ pass
46
+
47
+ def add_conversation_history(self, kwargs):
48
+ # Use the manual storage
49
+ # ensure the AI remembers the past conversation
50
+ # TODO: there are additional formats to support here.
51
+ new_messages = kwargs["input"]
52
+ if not isinstance(new_messages, list):
53
+ new_messages = [dict(content=new_messages, role="user", type="message")]
54
+ if hasattr(self, '_conversation') and self._conversation:
55
+ old_messages = [m.original for m in self._conversation.messages]
56
+ kwargs["input"] = old_messages + new_messages
57
+ # Add messages to conversation
58
+ normalized_messages = self._normalize_message(new_messages)
59
+ for msg in normalized_messages:
60
+ self._conversation.messages.append(msg)
61
+