From 310645641193c59b6876c9451d148f82f06860ad Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Mon, 30 Jun 2025 15:48:44 -0700 Subject: [PATCH] Initial working --- .gitignore | 12 ++++++++++++ LICENSE | 21 ++++++++++++++++++++ README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++ evals/test_agent.py | 24 +++++++++++++++++++++++ pyproject.toml | 44 +++++++++++++++++++++++++++++++++++++++++ src/__init__.py | 1 + src/agent.py | 40 +++++++++++++++++++++++++++++++++++++ 7 files changed, 190 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 evals/test_agent.py create mode 100644 pyproject.toml create mode 100644 src/__init__.py create mode 100644 src/agent.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9442dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.env +.env.* +.DS_Store +__pycache__ +.idea +KMS +uv.lock +.venv +.vscode +*.egg-info +.pytest_cache +.ruff_cache \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5acd05 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 LiveKit, Inc. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..47b06fd --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ + + LiveKit logo + + +# Voice AI Assistant with LiveKit Agents + +

+ Deploy a sandbox app + • + LiveKit Agents Docs + • + LiveKit Cloud + • + Blog +

+ +A simple voice AI assistant built with [LiveKit Agents for Python](https://github.com/livekit/agents). + +## Dev Setup + +Clone the repository and install dependencies to a virtual environment: + +```console +cd agent-starter-python +uv sync +``` + +Set up the environment by copying `.env.example` to `.env` and filling in the required values: + +- `LIVEKIT_URL` +- `LIVEKIT_API_KEY` +- `LIVEKIT_API_SECRET` +- `OPENAI_API_KEY` +- `DEEPGRAM_API_KEY` + +You can also do this automatically using the LiveKit CLI: + +```bash +lk app env -w .env +``` + +Run the agent: + +```console +uv run python src/agent.py dev +``` + +This agent requires a frontend application to communicate with. Use a [starter app](https://docs.livekit.io/agents/start/frontend/#starter-apps), our hosted [Sandbox](https://cloud.livekit.io/projects/p_/sandbox) frontends, or the [LiveKit Agents Playground](https://agents-playground.livekit.io/). diff --git a/evals/test_agent.py b/evals/test_agent.py new file mode 100644 index 0000000..f2f6789 --- /dev/null +++ b/evals/test_agent.py @@ -0,0 +1,24 @@ +import pytest + +from livekit.agents import AgentSession, llm +from livekit.plugins import openai +from agent import Assistant + + +def _llm() -> llm.LLM: + return openai.LLM(model="gpt-4o-mini", temperature=0.45) + +@pytest.mark.asyncio +async def test_greeting() -> None: + + async with ( + _llm() as llm, + AgentSession(llm=llm) as session, + ): + await session.start(Assistant()) + result = await session.run(user_input="Hi there how are you?") + await result.expect.message(role="assistant").judge( + llm, intent="should offer a friendly greeting to the user" + ) + result.expect.no_more_events() + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..23903a8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "agent-starter-python" +version = "0.1.0" +description = "Simple voice AI assistant built with LiveKit Agents for Python" +requires-python = ">=3.9" + +dependencies = [ + "livekit-agents", + "livekit-plugins-openai", + "livekit-plugins-turn-detector", + "livekit-plugins-silero", + "livekit-plugins-cartesia", + "livekit-plugins-deepgram", + "python-dotenv", + "livekit-plugins-noise-cancellation~=0.2.1", +] + +[dependency-groups] +dev = [ + "pytest", + "pytest-asyncio", +] + +[tool.uv.sources] +livekit-agents = { path = "../../livekit/agents/livekit-agents", editable = true } +livekit-plugins-openai = { path = "../../livekit/agents/livekit-plugins/livekit-plugins-openai", editable = true } +livekit-plugins-turn-detector = { path = "../../livekit/agents/livekit-plugins/livekit-plugins-turn-detector", editable = true } +livekit-plugins-silero = { path = "../../livekit/agents/livekit-plugins/livekit-plugins-silero", editable = true } +livekit-plugins-cartesia = { path = "../../livekit/agents/livekit-plugins/livekit-plugins-cartesia", editable = true } +livekit-plugins-deepgram = { path = "../../livekit/agents/livekit-plugins/livekit-plugins-deepgram", editable = true } + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-dir] +"" = "src" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..c98ec70 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +# This file makes the src directory a Python package \ No newline at end of file diff --git a/src/agent.py b/src/agent.py new file mode 100644 index 0000000..75caf85 --- /dev/null +++ b/src/agent.py @@ -0,0 +1,40 @@ +from dotenv import load_dotenv + +from livekit import agents +from livekit.agents import AgentSession, Agent, RoomInputOptions +from livekit.plugins import openai, noise_cancellation, silero, deepgram, cartesia +from livekit.plugins.turn_detector.multilingual import MultilingualModel + +load_dotenv() + + +class Assistant(Agent): + def __init__(self) -> None: + super().__init__(instructions="You are a helpful voice AI assistant.") + + +async def entrypoint(ctx: agents.JobContext): + session = AgentSession( + stt=deepgram.STT(), + llm=openai.LLM(model="gpt-4o-mini"), + tts=cartesia.TTS(), + vad=silero.VAD.load(), + turn_detection=MultilingualModel(), + ) + + await session.start( + room=ctx.room, + agent=Assistant(), + room_input_options=RoomInputOptions( + # LiveKit Cloud enhanced noise cancellation + # - If self-hosting, omit this parameter + # - For telephony applications, use `BVCTelephony` for best results + noise_cancellation=noise_cancellation.BVC(), + ), + ) + + await ctx.connect() + + +if __name__ == "__main__": + agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))