Skip to content

feat(agents): enhance LiveKit CLI Agent with comprehensive Python ecosystem support and improved UX #624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

dwayn
Copy link
Member

@dwayn dwayn commented Jul 25, 2025

Enhances the LiveKit CLI Agent's Python project detection and Docker generation capabilities, adding support for UV, Poetry, Hatch, and pip Python project types while improving the overall developer experience with better information and guidance.

Note: This PR also incorporates (and should supersede) the work from @rektdeckard in #623

Key Features

Enhanced Python Ecosystem Support

  • Multi-tool Detection: Added support for UV, Poetry, PDM, Hatch, and Pipenv projects
  • Intelligent Priority System: uv.lock → poetry.lock/Pipfile.lock → requirements.txt → pyproject.toml content analysis
  • TOML Parsing: Tool detection via [tool.poetry], [tool.pdm], [tool.hatch], [tool.uv] sections
  • UV-Specific Logic: Advanced detection using [dependency-groups] and UV command patterns

Docker Generation Improvements

  • Dockerignore Integration: Recursive file traversal with pattern matching support and exclusion of invalid files for build
  • Idiomatic Docker patterns: for better build customization
  • Enhanced Documentation: Comprehensive system package guides for audio, ML, and computer vision for docker container configuration
  • Node.js Improvements: Updated templates with pnpm consistency and better practices

UX Improvements for CLI

  • Smart Entry Point Detection: Intelligent prioritization (main.py > src/main.py > agent.py)
  • Educational Guidance: Contextual help messages and best practice recommendations
  • User-Friendly Selection: Enhanced project file selection with descriptive options

Technical Notes

Priority-Based Detection Logic

  1. Lock Files: uv.lock (UV) → poetry.lock/Pipfile.lock (pip-compatible)
  2. Requirements: requirements.txt (classic pip)
  3. TOML Analysis: Tool-specific sections in pyproject.toml
  4. Content Fallback: UV-specific patterns for edge cases

Comprehensive Test Coverage

  • Test cases covering all new functionality
  • Edge case handling: Malformed TOML, complex project structures, priority conflicts
  • Multi-tool scenarios: Projects with multiple Python tools configured
  • Utility testing: Complete coverage of new helper functions

Testing

  • ✅ All existing tests pass
  • ✅ New comprehensive test suite covers edge cases
  • ✅ Manual testing with real Python projects (UV, Poetry, PDM, pip)
  • ✅ Docker generation tested across all project types

dwayn added 3 commits July 25, 2025 18:25
…ool support

  - Add ProjectType enum system for type-safe project handling
  - Implement sophisticated UV detection via uv.lock and pyproject.toml patterns
  - Add Poetry, PDM, Hatch, Pipenv detection with TOML parsing
  - Add priority-based detection: uv.lock > lock files > requirements.txt > pyproject.toml
  - Add FileExists utility for cleaner file detection
  - Preserve educational UV warnings for missing lock files
…ile discovery

  - Add dockerignore support
  - Recursive file traversal for endpoint discovery
  - Implement ARG-based Docker patterns for idiomatic container builds
  - Add Node.js template improvements
  - Add comprehensive template documentation and system package guides
  - Improve user feedback and guidance on docker configuration for agents
  - Separate pip and uv based docker template files
  - Update python.uv and python.pip docker file templates with enhanced documentation
…ection

  - Add PDM, Hatch, Pipenv project detection tests
  - Add TOML error handling and complex parsing scenario tests
  - Add priority order detection and edge case coverage
  - Add FileExists utility tests with edge cases
  - Add IsNode() method consistency tests
  - Update existing tests to use new DetectProjectType architecture
FROM base AS build

RUN apt-get update -qq && apt-get install --no-install-recommends -y ca-certificates
COPY --link . .

RUN pnpm install --frozen-lockfile
RUN npm run build
RUN pnpm run build
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may also want to detect yarn (yarn.lock) or npm (package-lock.json) projects, and rename the base one here to node.pnpm.Dockerfile

Copy link
Member

@rektdeckard rektdeckard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments, but overall looks quite good. Thanks for the added user feedback, this will feel good with some slight style tweaks to fit what we do elsewhere in CLI. Not sure about some of the entrypoint detection logic, would appreciate a little closer look.

Comment on lines +9 to +11
func TestProjectDetection(t *testing.T) {
// Test UV project detection with the actual agent-starter-python example
uvProjectPath := "/Volumes/Code/workspaces/cloud-agents/livekit-examples/agent-starter-python"
Copy link
Member

@rektdeckard rektdeckard Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously tests like this won't work on anyone else's machine. But if we wanted to we could include some actual minimal examples in this repo and test them with embed.FS. I imagine Go's compiler is smart enough not to compile them into the binary if only referenced from test files? But I'm not sure.

Comment on lines +119 to +120
fmt.Printf(" ► Build your agent: docker build -t my-agent .\n")
fmt.Printf(" ► Test locally: docker run my-agent\n")
Copy link
Member

@rektdeckard rektdeckard Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This typically won't work, as when WE run the container, we do secret injection. If we want to keep this, at the very least it will need to be docker run --env-file .env.local my-agent, but there's no guarantee they have the required LK secrets in their env file. Because it's iffy, I'm not sure we should say this at all?

Comment on lines +175 to 178
fileList := make(map[string]bool)
for _, file := range allFiles {
fileList[file] = true
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're gonna use a map, let's just use one from the start. Also, the "map of empty structs as a set" pattern is a tiny but very "go-pilled" optimization here 😛

allFiles := make(map[string]struct{})

suffix = ".py"
case "node":
} else if projectType == ProjectTypeNode {
suffix = ".js"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have projectType.FileExt() now

return candidates[0], nil
}

options := candidates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the copy necessary?

Comment on lines +254 to +265
case strings.Contains(option, "src/"):
description = fmt.Sprintf("%s (modern src/ layout)", option)
case strings.Contains(option, "main."):
description = fmt.Sprintf("%s (common main entry point)", option)
case strings.Contains(option, "agent."):
description = fmt.Sprintf("%s (LiveKit agent)", option)
case strings.Contains(option, "app."):
description = fmt.Sprintf("%s (application entry point)", option)
case strings.Contains(option, "__main__.py"):
description = fmt.Sprintf("%s (Python module entry)", option)
default:
description = option
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pretty unnecessary

Comment on lines +268 to +276
// For Python module paths, suggest both file and module syntax
if projectType.IsPython() && strings.HasPrefix(option, "src/") && strings.HasSuffix(option, ".py") {
modulePath := strings.TrimPrefix(option, "src/")
modulePath = strings.TrimSuffix(modulePath, ".py")
modulePath = strings.ReplaceAll(modulePath, "/", ".")
if modulePath != "" {
description += fmt.Sprintf(" [can also use: src.%s]", modulePath)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand what this is doing? What can you use e.g. "src.agent.main" for?

}
}

// Use our sophisticated UV detection for pyproject.toml files without explicit tool sections
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"sophosticated" 😆

// - [tool.uv]: UV-specific tool configuration section
if strings.Contains(content, "[dependency-groups]") ||
strings.Contains(content, "uv sync") ||
strings.Contains(content, "[tool.uv]") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we already check this above? Would it not also be nice to use the parsed toml doc rather than check the whole content, since we've already parsed it at this point I think this function needs a human touch to sit right with me lol.

Comment on lines +1 to +22
package util

import (
"os"
"path/filepath"
"testing"
)

// TestFileExists tests the FileExists utility function
func TestFileExists(t *testing.T) {
tempDir := t.TempDir()

// Test 1: File exists
testFile := "test-file.txt"
os.WriteFile(filepath.Join(tempDir, testFile), []byte("test content"), 0644)

if !FileExists(tempDir, testFile) {
t.Errorf("Expected FileExists to return true for existing file")
}

// Test 2: File does not exist
nonExistentFile := "non-existent.txt"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to be testing a simple boolean wrapper around a stdlib function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants