mirror of
https://github.com/run-llama/gemini-live-demo.git
synced 2026-07-01 20:24:02 -04:00
first commit
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
name: Linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.12
|
||||
|
||||
- name: Run linter
|
||||
shell: bash
|
||||
run: uv run -- pre-commit run -a
|
||||
@@ -0,0 +1,21 @@
|
||||
name: CI Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
testing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Run Tests
|
||||
run: uv run -- pytest tests/test_*.py
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Typecheck
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
core-typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Run Mypy
|
||||
working-directory: src
|
||||
run: uv run -- mypy gemini_live_demo
|
||||
@@ -0,0 +1,9 @@
|
||||
# python generated files
|
||||
.venv
|
||||
__pycache__/
|
||||
.*_cache/
|
||||
lib/
|
||||
build/
|
||||
|
||||
# env variables
|
||||
.env
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
default_language_version:
|
||||
python: python3
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: check-symlinks
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: detect-private-key
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.11.8
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--exit-non-zero-on-fix, --fix]
|
||||
- id: ruff-format
|
||||
exclude: ".*poetry.lock|.*_static|.*uv.lock|.*ipynb|.*docs.*"
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.0.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies:
|
||||
[
|
||||
"types-requests",
|
||||
"types-Deprecated",
|
||||
"types-redis",
|
||||
"types-setuptools",
|
||||
"types-PyYAML",
|
||||
"types-protobuf==4.24.0.4",
|
||||
]
|
||||
args:
|
||||
[
|
||||
--namespace-packages,
|
||||
--explicit-package-bases,
|
||||
--disallow-untyped-defs,
|
||||
--ignore-missing-imports,
|
||||
--python-version=3.9,
|
||||
]
|
||||
entry: bash -c "export MYPYPATH=src/gemini_live_demo"
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.10.1
|
||||
hooks:
|
||||
- id: black-jupyter
|
||||
name: black-docs-py
|
||||
alias: black
|
||||
files: ^(README.md|CONTRIBUTING.md)
|
||||
# Using PEP 8's line length in docs prevents excess left/right scrolling
|
||||
args: [--line-length=79]
|
||||
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: 1.16.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
name: black-docs-text
|
||||
alias: black
|
||||
types_or: [rst, markdown, tex]
|
||||
additional_dependencies: [black==23.10.1]
|
||||
# Using PEP 8's line length in docs prevents excess left/right scrolling
|
||||
args: [--line-length=79]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/srstevenson/nb-clean
|
||||
rev: 3.1.0
|
||||
hooks:
|
||||
- id: nb-clean
|
||||
args: [--preserve-cell-outputs, --remove-empty-cells]
|
||||
|
||||
- repo: https://github.com/pappasam/toml-sort
|
||||
rev: v0.23.1
|
||||
hooks:
|
||||
- id: toml-sort-fix
|
||||
@@ -0,0 +1 @@
|
||||
3.13
|
||||
@@ -0,0 +1,48 @@
|
||||
# Contributing to `gemini-live-demo`
|
||||
|
||||
Do you want to contribute to this project? Make sure to read this guidelines first :)
|
||||
|
||||
## Issue
|
||||
|
||||
**When to do it**:
|
||||
|
||||
- You found bugs but you don't know how to solve them or don't have time/will to do the solve
|
||||
- You want new features but you don't know how to implement them or don't have time/will to do the implementation
|
||||
|
||||
> ⚠️ _Always check open and closed issues before you submit yours to avoid duplicates_
|
||||
|
||||
**How to do it**:
|
||||
|
||||
- Open an issue
|
||||
- Give the issue a meaningful title (short but effective problem/feature request description)
|
||||
- Describe the problem/feature request
|
||||
|
||||
## Traditional contribution
|
||||
|
||||
**When to do it**:
|
||||
|
||||
- You found bugs and corrected them
|
||||
- You optimized/improved the code
|
||||
- You added new features that you think could be useful to others
|
||||
|
||||
**How to do it**:
|
||||
|
||||
1. Fork this repository
|
||||
2. Install `pre-commit` and make sure to have it within the Git Hooks for your fork:
|
||||
|
||||
```bash
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
3. Change the things you want, and make sure tests still pass or add new ones:
|
||||
|
||||
```bash
|
||||
pytest tests/test_*.py
|
||||
```
|
||||
|
||||
3. Commit your changes
|
||||
4. Make sure your changes pass the pre-commit linting/type checking, if not modify them so that they pass
|
||||
5. Submit pull request (make sure to provide a thorough description of the changes)
|
||||
|
||||
### Thanks for contributing!
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Jerry Liu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Gemini-Live Demo
|
||||
|
||||
This is a demo repository showcasing Gemini Live x LlamaIndex integration.
|
||||
|
||||
## Install and Launch
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> _This is a [uv](https://docs.astral.sh/uv/) project, so make sure to have uv [installed](https://docs.astral.sh/uv/getting-started/installation/)_
|
||||
|
||||
Clone this repository locally:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/run-llama/gemini-live-demo
|
||||
cd gemini-live-demo
|
||||
```
|
||||
|
||||
And install the needed dependencies:
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
Now create a `.env` file and add your `GOOGLE_API_KEY` there:
|
||||
|
||||
```bash
|
||||
touch .env
|
||||
echo GOOGLE_API_KEY=*** > .env
|
||||
```
|
||||
|
||||
Launch the application with
|
||||
|
||||
```bash
|
||||
uv run src/gemini_live_demo/main.py
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) to get started.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](./LICENSE).
|
||||
@@ -0,0 +1,23 @@
|
||||
[project]
|
||||
name = "gemini-demo"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"llama-index-voice-agents-gemini-live",
|
||||
"mypy>=1.17.0",
|
||||
"pre-commit>=4.2.0",
|
||||
"pytest>=8.4.1",
|
||||
"python-dotenv>=1.1.1",
|
||||
"ruff>=0.12.5"
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
disable_error_code = ["import-not-found", "import-untyped"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["src"]
|
||||
|
||||
[tool.uv.sources]
|
||||
llama-index-voice-agents-gemini-live = {git = "https://github.com/run-llama/llama_index", subdirectory = "llama-index-integrations/voice_agents/llama-index-voice-agents-gemini-live", branch = "main"}
|
||||
@@ -0,0 +1,31 @@
|
||||
from llama_index.voice_agents.gemini_live import GeminiLiveVoiceAgent # type: ignore
|
||||
from llama_index.core.tools import FunctionTool
|
||||
from utils import get_weather, filter_events, filter_messages # type: ignore
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
weather_tool = FunctionTool.from_defaults(
|
||||
fn=get_weather,
|
||||
name="get_weather",
|
||||
description="Get the weather at a given location",
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
conversation = GeminiLiveVoiceAgent(tools=[weather_tool])
|
||||
|
||||
await conversation.start()
|
||||
|
||||
if conversation._quitflag:
|
||||
print("Events")
|
||||
print(conversation.export_events(filter=filter_events))
|
||||
print()
|
||||
print("Messages")
|
||||
print(conversation.export_messages(filter=filter_messages))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,42 @@
|
||||
from llama_index.core.voice_agents import BaseVoiceAgentEvent
|
||||
from llama_index.core.llms import ChatMessage, TextBlock
|
||||
from typing import List
|
||||
import random
|
||||
import json
|
||||
|
||||
|
||||
# use filter functions to export messages and events without your terminal being swamped by base64-encoded audio bytes :)
|
||||
def filter_events(
|
||||
events: List[BaseVoiceAgentEvent],
|
||||
) -> List[BaseVoiceAgentEvent]:
|
||||
evs = []
|
||||
for event in events:
|
||||
if "audio" not in event.type_t:
|
||||
evs.append(event)
|
||||
return evs
|
||||
|
||||
|
||||
def filter_messages(messages: List[ChatMessage]) -> List[ChatMessage]:
|
||||
msgs = []
|
||||
for message in messages:
|
||||
msg = ChatMessage(role=message.role, blocks=[])
|
||||
for b in message.blocks:
|
||||
if isinstance(b, TextBlock):
|
||||
msg.blocks.append(b)
|
||||
if len(msg.blocks) > 0:
|
||||
msgs.append(msg)
|
||||
return msgs
|
||||
|
||||
|
||||
def get_weather(location: str) -> str:
|
||||
"""Fetch weather data for a given location."""
|
||||
return json.dumps(
|
||||
{
|
||||
"location": location,
|
||||
"temperature_c": round(random.uniform(15, 30), 1),
|
||||
"humidity_percent": random.randint(40, 90),
|
||||
"wind_speed_kmh": round(random.uniform(5, 25), 1),
|
||||
"precipitation_probability_percent": random.randint(0, 100),
|
||||
},
|
||||
indent=4,
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
import pytest
|
||||
import json
|
||||
from typing import List, Optional
|
||||
|
||||
from src.gemini_live_demo.utils import get_weather, filter_events, filter_messages # type: ignore
|
||||
from llama_index.core.llms import ChatMessage, AudioBlock, TextBlock
|
||||
from llama_index.core.voice_agents import BaseVoiceAgentEvent
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def messages() -> List[ChatMessage]:
|
||||
return [
|
||||
ChatMessage(role="user", blocks=[AudioBlock(audio=b"Hello")]),
|
||||
ChatMessage(
|
||||
role="assistant",
|
||||
blocks=[AudioBlock(audio=b"Hello back"), TextBlock(text="hello back")],
|
||||
),
|
||||
ChatMessage(role="user", content="now what?"),
|
||||
]
|
||||
|
||||
|
||||
class Audio(BaseVoiceAgentEvent):
|
||||
pass
|
||||
|
||||
|
||||
class Text(BaseVoiceAgentEvent):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def events() -> List[BaseVoiceAgentEvent]:
|
||||
return [
|
||||
Audio(type_t="audio"),
|
||||
Text(type_t="text"),
|
||||
Text(type_t="text"),
|
||||
]
|
||||
|
||||
|
||||
def is_serializable(s: str) -> tuple[bool, Optional[dict]]:
|
||||
try:
|
||||
d = json.loads(s)
|
||||
return True, d
|
||||
except json.JSONDecodeError:
|
||||
return False, None
|
||||
|
||||
|
||||
def test_weather_tool() -> None:
|
||||
a = get_weather("San Francisco")
|
||||
assert isinstance(a, str)
|
||||
serializable, serialized = is_serializable(a)
|
||||
assert serializable
|
||||
assert serialized is not None
|
||||
assert serialized["location"] == "San Francisco"
|
||||
|
||||
|
||||
def test_filter_messages(messages: List[ChatMessage]):
|
||||
filtered = filter_messages(messages)
|
||||
assert isinstance(filtered, list)
|
||||
assert isinstance(filtered[0], ChatMessage)
|
||||
assert len(filtered) == 2
|
||||
assert filtered[1].content == "now what?"
|
||||
|
||||
|
||||
def test_filter_events(events: List[BaseVoiceAgentEvent]):
|
||||
filtered = filter_events(events)
|
||||
assert isinstance(filtered, list)
|
||||
assert isinstance(filtered[0], BaseVoiceAgentEvent)
|
||||
assert len(filtered) == 2
|
||||
assert all(ev.type_t == "text" for ev in filtered)
|
||||
Reference in New Issue
Block a user