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.
- vision_agents_plugins_openrouter-0.1.9/.gitignore +86 -0
- vision_agents_plugins_openrouter-0.1.9/PKG-INFO +47 -0
- vision_agents_plugins_openrouter-0.1.9/README.md +34 -0
- vision_agents_plugins_openrouter-0.1.9/example/__init__.py +0 -0
- vision_agents_plugins_openrouter-0.1.9/example/openrouter_example.py +51 -0
- vision_agents_plugins_openrouter-0.1.9/example/pyproject.toml +26 -0
- vision_agents_plugins_openrouter-0.1.9/py.typed +0 -0
- vision_agents_plugins_openrouter-0.1.9/pyproject.toml +38 -0
- vision_agents_plugins_openrouter-0.1.9/tests/__init__.py +0 -0
- vision_agents_plugins_openrouter-0.1.9/tests/test_openrouter_llm.py +156 -0
- vision_agents_plugins_openrouter-0.1.9/vision_agents/plugins/openrouter/__init__.py +5 -0
- vision_agents_plugins_openrouter-0.1.9/vision_agents/plugins/openrouter/openrouter_llm.py +61 -0
|
@@ -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
|
+
```
|
|
File without changes
|
|
@@ -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
|
+
|
|
File without changes
|
|
@@ -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,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
|
+
|