-
Notifications
You must be signed in to change notification settings - Fork 101
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
base: main
Are you sure you want to change the base?
Conversation
…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 |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
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" |
There was a problem hiding this comment.
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.
fmt.Printf(" ► Build your agent: docker build -t my-agent .\n") | ||
fmt.Printf(" ► Test locally: docker run my-agent\n") |
There was a problem hiding this comment.
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?
fileList := make(map[string]bool) | ||
for _, file := range allFiles { | ||
fileList[file] = true | ||
} |
There was a problem hiding this comment.
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" | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the copy necessary?
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 |
There was a problem hiding this comment.
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
// 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) | ||
} | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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]") { |
There was a problem hiding this comment.
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.
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" |
There was a problem hiding this comment.
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.
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
Docker Generation Improvements
UX Improvements for CLI
Technical Notes
Priority-Based Detection Logic
Comprehensive Test Coverage
Testing