diff --git a/pyproject.toml b/pyproject.toml index 4a239170..5f689543 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,13 +144,11 @@ context = { workspace = true } django-hello = { workspace = true } dynamic-tools = { workspace = true } evaluators = { workspace = true } -fastapi-bugbot = { workspace = true } flask-hello = { workspace = true } gemini-code-execution = { workspace = true } gemini-context-caching = { workspace = true } google-genai-media = { workspace = true } middleware = { workspace = true } -middleware-coding-agent = { workspace = true } ollama-sample = { workspace = true } output-formats = { workspace = true } prompts = { workspace = true } diff --git a/samples/README.md b/samples/README.md index 3a6a6663..6bb2acc5 100644 --- a/samples/README.md +++ b/samples/README.md @@ -28,7 +28,6 @@ Dev UI: http://localhost:4000. Most samples need `GEMINI_API_KEY`. See [plugins/ | `context` | Pass context through `generate()`, flows, and tools | | `dynamic-tools` | Create a tool at runtime and trace plain functions | | `evaluators` | Run simple custom evaluators with `genkit eval:run` | -| `fastapi-bugbot` | A small FastAPI app that reviews code | | `flask-hello` | Expose Genkit flows through Flask | | `gemini-code-execution` | Ask Gemini to write and run code | | `gemini-context-caching` | Cache a large source document for follow-up prompts | diff --git a/samples/django-hello/README.md b/samples/django-hello/README.md index 22ec3f77..a025747a 100644 --- a/samples/django-hello/README.md +++ b/samples/django-hello/README.md @@ -1,6 +1,6 @@ # Django Hello -Serve a Genkit flow through Django and stream the model response back to the client. Mirrors `flask-hello` and `fastapi-bugbot` but uses Django's ASGI server and the `genkit-plugin-django` adaptor. +Serve a Genkit flow through Django and stream the model response back to the client. Mirrors `flask-hello` but uses Django's ASGI server and the `genkit-plugin-django` adaptor. ```bash export GEMINI_API_KEY=your-api-key diff --git a/samples/django-hello/recipes/views.py b/samples/django-hello/recipes/views.py index 00fb935c..26b8fc10 100644 --- a/samples/django-hello/recipes/views.py +++ b/samples/django-hello/recipes/views.py @@ -24,8 +24,7 @@ from genkit_google_genai import GoogleAI from pydantic import BaseModel, Field -from genkit import Genkit, ModelResponse -from genkit._core._action import ActionRunContext +from genkit import ActionRunContext, Genkit, ModelResponse from genkit.plugin_api import RequestData ai = Genkit( diff --git a/samples/fastapi-bugbot/README.md b/samples/fastapi-bugbot/README.md deleted file mode 100644 index 078085d4..00000000 --- a/samples/fastapi-bugbot/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# FastAPI BugBot - -A small FastAPI app that reviews code for security, bug, and style issues. - -```bash -export GEMINI_API_KEY=your-api-key -uv sync -uv run src/main.py -``` - -- API: http://localhost:8080 -- Swagger: http://localhost:8080/docs - -```bash -curl -X POST http://localhost:8080/review \ - -H "Content-Type: application/json" \ - -d '{"code":"eval(user_input)","language":"python"}' -``` - -To inspect the underlying flows in Dev UI instead: - -```bash -genkit start -- uv run src/main.py -``` - -- Dev UI: http://localhost:4000 diff --git a/samples/fastapi-bugbot/prompts/analyze_bugs.prompt b/samples/fastapi-bugbot/prompts/analyze_bugs.prompt deleted file mode 100644 index 6e67feff..00000000 --- a/samples/fastapi-bugbot/prompts/analyze_bugs.prompt +++ /dev/null @@ -1,24 +0,0 @@ ---- -model: googleai/gemini-flash-latest -input: - schema: - code: string - language?: string -output: - schema: Analysis ---- - -You are an expert code reviewer. Analyze the following {{language}} code for potential bugs and logic errors. - -Code: -```{{language}} -{{code}} -``` - -Focus on: -- Null pointer/undefined errors -- Race conditions and concurrency issues -- Resource leaks -- Logic errors and edge cases - -Return ONLY bugs with HIGH confidence. diff --git a/samples/fastapi-bugbot/prompts/analyze_diff.prompt b/samples/fastapi-bugbot/prompts/analyze_diff.prompt deleted file mode 100644 index 146f4c69..00000000 --- a/samples/fastapi-bugbot/prompts/analyze_diff.prompt +++ /dev/null @@ -1,27 +0,0 @@ ---- -model: googleai/gemini-flash-latest -input: - schema: - diff: string - context?: string -output: - schema: Analysis ---- - -You are a code reviewer. Analyze this code diff for issues. - -{{#if context}} -Context: {{context}} -{{/if}} - -Diff: -```diff -{{diff}} -``` - -Focus on changes that introduce: -- Security vulnerabilities -- Bugs or logic errors -- Style violations - -Return ONLY issues in the changed lines. diff --git a/samples/fastapi-bugbot/prompts/analyze_security.prompt b/samples/fastapi-bugbot/prompts/analyze_security.prompt deleted file mode 100644 index b07de569..00000000 --- a/samples/fastapi-bugbot/prompts/analyze_security.prompt +++ /dev/null @@ -1,24 +0,0 @@ ---- -model: googleai/gemini-flash-latest -input: - schema: - code: string - language?: string -output: - schema: Analysis ---- - -You are a security expert code reviewer. Analyze the following {{language}} code for security vulnerabilities. - -Code: -```{{language}} -{{code}} -``` - -Focus on: -- SQL injection, XSS, command injection -- Authentication and authorization issues -- Cryptographic weaknesses -- Input validation problems - -Return ONLY security issues with HIGH confidence. diff --git a/samples/fastapi-bugbot/prompts/analyze_style.prompt b/samples/fastapi-bugbot/prompts/analyze_style.prompt deleted file mode 100644 index ad543859..00000000 --- a/samples/fastapi-bugbot/prompts/analyze_style.prompt +++ /dev/null @@ -1,24 +0,0 @@ ---- -model: googleai/gemini-flash-latest -input: - schema: - code: string - language?: string -output: - schema: Analysis ---- - -You are a code style expert. Analyze the following {{language}} code for style issues and best practices. - -Code: -```{{language}} -{{code}} -``` - -Focus on: -- Naming conventions -- Code organization -- Documentation -- Idiomatic patterns for {{language}} - -Return ONLY important style issues. diff --git a/samples/fastapi-bugbot/pyproject.toml b/samples/fastapi-bugbot/pyproject.toml deleted file mode 100644 index 6c5ca05e..00000000 --- a/samples/fastapi-bugbot/pyproject.toml +++ /dev/null @@ -1,18 +0,0 @@ -[project] -name = "fastapi-bugbot" -version = "0.2.0" -requires-python = ">=3.10" -dependencies = [ - "genkit", - "genkit-fastapi", - "genkit-google-genai", - "python-dotenv>=1.0.0", - "uvicorn[standard]>=0.34.0", -] - -[build-system] -build-backend = "hatchling.build" -requires = ["hatchling"] - -[tool.hatch.build.targets.wheel] -packages = ["src"] diff --git a/samples/fastapi-bugbot/src/main.py b/samples/fastapi-bugbot/src/main.py deleted file mode 100644 index 038b7a03..00000000 --- a/samples/fastapi-bugbot/src/main.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright 2025 Google LLC -# SPDX-License-Identifier: Apache-2.0 - -r"""BugBot: AI Code Reviewer. - - genkit start -- uv run src/main.py - curl localhost:8080/review -d '{"code": "query = f\"SELECT * FROM users WHERE id={user_input}\""}' - -If something looks wrong, check localhost:4000 to see what the model actually received. -""" - -import asyncio -from pathlib import Path -from typing import Literal - -import uvicorn -from dotenv import load_dotenv -from fastapi import FastAPI -from genkit_fastapi import genkit_fastapi_handler -from genkit_google_genai import GoogleAI -from pydantic import BaseModel, Field -from typing_extensions import Never - -from genkit import Flow, Genkit - -_ = load_dotenv() - -# The Dev UI reflection server starts automatically in a background thread -# when GENKIT_ENV=dev is set — no lifespan wiring needed. -ai = Genkit( - plugins=[GoogleAI()], - model='googleai/gemini-flash-latest', - prompt_dir=Path(__file__).resolve().parent.parent / 'prompts', -) - - -Severity = Literal['critical', 'warning', 'info'] -Category = Literal['security', 'bug', 'style'] - - -class Issue(BaseModel): - """A single issue found in the code.""" - - line: int = Field(description='Line number where the issue occurs') - title: str = Field(description='Brief title like "SQL Injection Risk"') - severity: Severity - category: Category - explanation: str = Field(description='Why this is a problem') - suggestion: str = Field(description='How to fix it') - - -class Analysis(BaseModel): - """Analysis result containing found issues.""" - - issues: list[Issue] = Field(default_factory=list) - - -class CodeInput(BaseModel): - """Input for code analysis.""" - - code: str - language: str = 'python' - - -class DiffInput(BaseModel): - """Input for diff analysis.""" - - diff: str - context: str = '' - - -security_prompt = ai.prompt('analyze_security', input_schema=CodeInput, output_schema=Analysis) -bugs_prompt = ai.prompt('analyze_bugs', input_schema=CodeInput, output_schema=Analysis) -style_prompt = ai.prompt('analyze_style', input_schema=CodeInput, output_schema=Analysis) -diff_prompt = ai.prompt('analyze_diff', input_schema=DiffInput, output_schema=Analysis) - - -@ai.flow() -async def analyze_security(input: CodeInput) -> Analysis: - """Analyze code for security vulnerabilities.""" - response = await security_prompt(input=input) - return response.output - - -@ai.flow() -async def analyze_bugs(input: CodeInput) -> Analysis: - """Analyze code for potential bugs.""" - response = await bugs_prompt(input=input) - return response.output - - -@ai.flow() -async def analyze_style(input: CodeInput) -> Analysis: - """Analyze code for style issues.""" - response = await style_prompt(input=input) - return response.output - - -@ai.flow() -async def review_code(input: CodeInput) -> Analysis: - """Run all analyzers in parallel and combine results.""" - security, bugs, style = await asyncio.gather( - analyze_security(input), - analyze_bugs(input), - analyze_style(input), - ) - return Analysis(issues=security.issues + bugs.issues + style.issues) - - -@ai.flow() -async def review_diff(input: DiffInput) -> Analysis: - """Review a code diff for issues.""" - response = await diff_prompt(input=input) - return response.output - - -app = FastAPI(title='BugBot', description='AI-powered code review API') - - -@app.post('/review') -async def review(input: CodeInput) -> Analysis: - """Review code for security, bugs, and style issues.""" - return await review_code(input) - - -@app.post('/review/security') -async def review_security_endpoint(input: CodeInput) -> Analysis: - """Review code for security issues only.""" - return await analyze_security(input) - - -@app.post('/review/diff') -async def review_diff_endpoint(input: DiffInput) -> Analysis: - """Review a code diff.""" - return await review_diff(input) - - -@app.post('/flow/review', response_model=None) -@genkit_fastapi_handler(ai) -def flow_review() -> Flow[CodeInput, Analysis, Never]: - """Expose review_code flow directly via {"data": {"code": "...", "language": "..."}}.""" - return review_code - - -@app.post('/flow/security', response_model=None) -@genkit_fastapi_handler(ai) -def flow_security() -> Flow[CodeInput, Analysis, Never]: - """Expose analyze_security flow directly.""" - return analyze_security - - -if __name__ == '__main__': - uvicorn.run(app, host='0.0.0.0', port=8080) # noqa: S104 diff --git a/samples/flask-hello/src/main.py b/samples/flask-hello/src/main.py index 6b8bc1b0..8bc54b60 100755 --- a/samples/flask-hello/src/main.py +++ b/samples/flask-hello/src/main.py @@ -23,9 +23,8 @@ from genkit_google_genai import GoogleAI from pydantic import BaseModel, Field -from genkit import Genkit, ModelResponse -from genkit._core._action import ActionRunContext -from genkit._core._context import RequestData +from genkit import ActionRunContext, Genkit, ModelResponse +from genkit.plugin_api import RequestData ai = Genkit( plugins=[GoogleAI()], diff --git a/samples/google-genai-media/src/main.py b/samples/google-genai-media/src/main.py index 17922896..167eca25 100644 --- a/samples/google-genai-media/src/main.py +++ b/samples/google-genai-media/src/main.py @@ -14,19 +14,12 @@ # # SPDX-License-Identifier: Apache-2.0 -"""Google GenAI media - one simple example each for speech, image, and video.""" - -import asyncio -import time -from typing import Any, Literal +"""Google GenAI media - simple examples for speech and image generation.""" from genkit_google_genai import GoogleAI from pydantic import BaseModel, Field from genkit import Genkit -from genkit._core._background import lookup_background_action -from genkit._core._typing import Operation, Part, Role, TextPart -from genkit.model import Message, ModelRequest ai = Genkit(plugins=[GoogleAI()]) @@ -44,46 +37,22 @@ class ImageInput(BaseModel): prompt: str = Field(default='A watercolor postcard of San Francisco at sunrise', description='Image prompt') -class VideoInput(BaseModel): - """Input for Veo.""" - - model: Literal[ - 'googleai/veo-3.1-generate-preview', - 'googleai/veo-3.1-fast-generate-preview', - 'googleai/veo-3.1-generate-001', - 'googleai/veo-3.1-fast-generate-001', - 'googleai/veo-3.0-generate-001', - 'googleai/veo-3.0-fast-generate-001', - 'googleai/veo-2.0-generate-001', - ] = Field(default='googleai/veo-3.1-generate-preview', description='Veo model for generation') - prompt: str = Field( - default='A paper airplane gliding through a bright classroom, cinematic slow motion', - description='Video prompt', - ) - aspect_ratio: str = Field(default='16:9', description='Video aspect ratio') - duration_seconds: int = Field(default=5, description='Video duration in seconds') - resolution: str | None = Field( - default=None, description='Output resolution (for supported models, e.g. "720p", "1080p")' - ) - seed: int | None = Field(default=None, description='Optional RNG seed') - - -def _first_media_url(response: Any) -> str | None: - """Return the first media URL in a model response.""" - +def _first_media_url(response: object) -> str | None: + """Extract media URL from first candidate message part if present.""" message = getattr(response, 'message', None) - if not message: + if message is None: return None - for part in message.content: + content = getattr(message, 'content', []) + for part in content: media = getattr(part.root, 'media', None) - if media and getattr(media, 'url', None): + if media is not None and getattr(media, 'url', None): return media.url return None @ai.flow(name='generate_speech') async def tts_speech_generator(input: SpeechInput) -> dict[str, str | None]: - """Turn text into speech with one TTS call.""" + """Generate audio bytes with Gemini TTS.""" response = await ai.generate( model='googleai/gemini-2.5-flash-preview-tts', @@ -105,54 +74,6 @@ async def imagen_image_generator(input: ImageInput) -> dict[str, str | None]: return {'model': 'googleai/imagen-3.0-generate-002', 'image_url': _first_media_url(response)} -async def _poll_video(operation: Operation, model_name: str) -> Operation: - """Wait for a background video operation to finish.""" - - action = await lookup_background_action(ai.registry, f'/background-model/{model_name}') - if action is None: - raise ValueError(f'Veo background model not found: {model_name}') - - started_at = time.monotonic() - while not operation.done: - if time.monotonic() - started_at > 180: - raise TimeoutError('Timed out waiting for Veo output') - await asyncio.sleep(3) - operation = await action.check(operation) - return operation - - -@ai.flow(name='generate_video') -async def veo_video_generator(input: VideoInput) -> dict[str, str | int | None]: - """Generate one video by starting and polling a background model.""" - - action = await lookup_background_action(ai.registry, f'/background-model/{input.model}') - if action is None: - raise ValueError(f'Veo background model not found: {input.model}') - - operation = await action.start( - ModelRequest( - messages=[Message(role=Role.USER, content=[Part(root=TextPart(text=input.prompt))])], - config=input.model_dump(exclude_none=True, exclude={'prompt', 'model'}), - ) - ) - operation = await _poll_video(operation, input.model) - - video_url = None - if isinstance(operation.output, dict): - message = operation.output.get('message', {}) - content = message.get('content', []) - if content: - media = content[0].get('media', {}) - video_url = media.get('url') - - return { - 'model': input.model, - 'operation_id': operation.id, - 'video_url': video_url, - 'duration_seconds': input.duration_seconds, - } - - async def main() -> None: """Run the fast media demos once.""" try: diff --git a/samples/middleware-coding-agent/.gitignore b/samples/middleware-coding-agent/.gitignore deleted file mode 100644 index b4163610..00000000 --- a/samples/middleware-coding-agent/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -__pycache__/ - -# The agent edits files here in place; outputs are reproducible by re-running. -workspace/ diff --git a/samples/middleware-coding-agent/README.md b/samples/middleware-coding-agent/README.md deleted file mode 100644 index 1cac9cba..00000000 --- a/samples/middleware-coding-agent/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# middleware-coding-agent - -Interactive coding-agent REPL that wires up the -[`Filesystem`](../../plugins/middleware/src/genkit/plugins/middleware/_filesystem.py), -[`Skills`](../../plugins/middleware/src/genkit/plugins/middleware/_skills.py), -and [`ToolApproval`](../../plugins/middleware/src/genkit/plugins/middleware/_tool_approval.py) -middleware against a sandboxed workspace. - -## What's here - -``` -middleware-coding-agent/ -├── src/main.py # interactive REPL -├── skills/ -│ ├── python-expert/SKILL.md # house style for editing Python -│ └── test-writer/SKILL.md # house style for writing pytest tests -└── workspace/ # sandbox the agent reads, writes, edits in - # (created on first run; contents gitignored) -``` - -The model gets: - -- the contents of `workspace/` via `Filesystem(root_dir=…, allow_write_access=True)` — - `list_files`, `read_file`, `write_file`, `edit_file`, all confined to that - directory. -- a system prompt listing the two skills, plus a `use_skill` tool it calls - to pull in the full `SKILL.md` content on demand. -- `ToolApproval(allowed_tools=['read_file', 'list_files', 'use_skill'])` — - read-only tools run without prompting; anything that can mutate the - workspace (`write_file`, `edit_file`) interrupts and waits for your - `y/N` from the CLI before resuming. - -## Run it - -```bash -cd py/samples/middleware-coding-agent -GEMINI_API_KEY=... genkit start -- uv run src/main.py -``` - -Type a request at the REPL prompt in your terminal (e.g. `build a tiny -priority queue module with push/pop/peek and pytest tests`), hit enter, -and approve each write the agent proposes. Conversation history persists -across turns until you type `exit`. - -If you want the agent to fix or extend an existing file instead of -starting from scratch, drop the file into `workspace/` first and reference -it by name in your prompt. - -## Resetting between runs - -The agent edits `workspace/` in place. To start over: - -```bash -rm -rf py/samples/middleware-coding-agent/workspace/* -``` diff --git a/samples/middleware-coding-agent/pyproject.toml b/samples/middleware-coding-agent/pyproject.toml deleted file mode 100644 index 879426b6..00000000 --- a/samples/middleware-coding-agent/pyproject.toml +++ /dev/null @@ -1,18 +0,0 @@ -[project] -name = "middleware-coding-agent" -version = "0.1.0" -requires-python = ">=3.10" -dependencies = [ - "genkit", - "genkit-google-genai", - "genkit-middleware", - "pydantic>=2.10.5", - "structlog>=25.2.0", -] - -[build-system] -build-backend = "hatchling.build" -requires = ["hatchling"] - -[tool.hatch.build.targets.wheel] -packages = ["src"] diff --git a/samples/middleware-coding-agent/skills/python-expert/SKILL.md b/samples/middleware-coding-agent/skills/python-expert/SKILL.md deleted file mode 100644 index 220f2f17..00000000 --- a/samples/middleware-coding-agent/skills/python-expert/SKILL.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: python-expert -description: Conventions for clean, idiomatic Python. Load whenever you read, edit, or write Python source files. ---- - -# Python expert - -When working with Python in this workspace, follow these conventions: - -- **Type-hint everything.** Parameters, returns, attributes, locals where the type isn't obvious. -- **Prefer dataclasses** for simple data containers over hand-written `__init__`s. -- **Raise specific exceptions** (`ValueError`, `KeyError`, `LookupError`) with informative messages. Avoid bare `Exception`. -- **Don't swallow errors.** Don't `except Exception: pass`. Let unexpected errors propagate. -- **Match the surrounding style.** If the file uses single quotes and 4-space indent, match it. Don't reformat unrelated lines. -- **Comments explain why, not what.** Skip narration like `# loop over items`; only comment non-obvious intent. -- **Small, focused edits.** When fixing a bug, change only what's necessary. Leave the rest of the file untouched so the diff stays readable. diff --git a/samples/middleware-coding-agent/skills/test-writer/SKILL.md b/samples/middleware-coding-agent/skills/test-writer/SKILL.md deleted file mode 100644 index 17d38784..00000000 --- a/samples/middleware-coding-agent/skills/test-writer/SKILL.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: test-writer -description: How to write pytest tests for modules in this workspace. Load whenever you are about to write or extend tests. ---- - -# Test writer - -When writing pytest tests in this workspace: - -- **One test file per module.** `foo.py` lives next to `foo_test.py` (suffix, not prefix). -- **Cover the happy path AND at least one edge case.** Empty input, duplicates, boundary values — pick what matters for the unit under test. -- **Use `pytest.mark.parametrize`** when the same assertion runs over a small table of inputs. Keep IDs descriptive. -- **Name tests `test___`.** Examples: `test_total_empty_cart_returns_zero`, `test_add_duplicate_item_merges_quantities`. -- **Arrange / Act / Assert.** Three clear blocks. No setup hidden in fixtures unless it's reused across at least two tests. -- **Assert behavior, not implementation.** Don't reach into private attributes or count function calls; check the observable result. -- **Imports at module top.** Don't import inside test functions. diff --git a/samples/middleware-coding-agent/src/main.py b/samples/middleware-coding-agent/src/main.py deleted file mode 100644 index 98f7f07f..00000000 --- a/samples/middleware-coding-agent/src/main.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -"""Agentic coding REPL — Filesystem + Skills + ToolApproval middleware. - -An interactive coding agent that reads, edits, and writes files inside a -sandboxed ``workspace/`` directory. Read-only tools (``read_file``, -``list_files``, ``use_skill``) run automatically; everything that can -mutate the workspace (``write_file``, ``edit_file``) is gated by -``ToolApproval``, so the CLI pauses and asks ``y/N`` before each write. - -The agent state — middleware instances and message history — is owned by a -``CodingAgent`` session object built once per ``main()`` invocation. -That ties every ``ai.generate()`` and resume in this REPL to the same -middleware stack. ``Filesystem`` itself keeps no cross-call cache; file -content reaches the model through enqueued messages inside each call. - -Re-running cleanly: - -* The agent mutates files in ``workspace/`` directly. To start over, - ``rm -rf workspace/*`` — the directory itself is recreated on next run. -""" - -from pathlib import Path - -from genkit_google_genai import GoogleAI -from genkit_middleware import Filesystem, Middleware, Skills, ToolApproval - -from genkit import Genkit, Message, ModelResponse, Part, Role, TextPart, ToolRequestPart, restart_tool - -_HERE = Path(__file__).resolve().parent.parent -_WORKSPACE = _HERE / 'workspace' -_SKILLS = _HERE / 'skills' - -ai = Genkit( - plugins=[GoogleAI(), Middleware()], - model='googleai/gemini-flash-latest', -) - - -SYSTEM_PROMPT = ( - 'You are a helpful coding agent. Very terse but thoughtful and careful.\n' - f'Your working directory is {_WORKSPACE}, you are not allowed to access anything outside it.\n' - 'Use plain filenames relative to the workspace root (e.g. ``foo.py``, not ``./foo.py`` ' - 'or absolute paths). You must ``read_file`` an existing file before you can ``write_file`` ' - 'or ``edit_file`` it — new files do not need a prior read.\n' - 'Use skills. ALWAYS start by analyzing the current state of the workspace, ' - 'there might be something already there.' -) - - -class CodingAgent: - """One agent session: owns the middleware stack and the running conversation.""" - - def __init__(self) -> None: - self.middleware = [ - ToolApproval(allowed_tools=['read_file', 'list_files', 'use_skill']), - Skills(skill_paths=[str(_SKILLS)]), - Filesystem(root_dir=str(_WORKSPACE), allow_write_access=True), - ] - self.messages: list[Message] = [ - Message(role=Role.SYSTEM, content=[Part(TextPart(text=SYSTEM_PROMPT))]), - ] - - async def turn(self, user_input: str) -> ModelResponse: - """Drive one user turn to completion across any number of approval prompts.""" - restart: list[ToolRequestPart] | None = None - while True: - response = await ai.generate( - prompt=user_input if restart is None else None, - messages=self.messages, - resume_restart=restart, - max_turns=20, - use=self.middleware, - ) - if not response.interrupts: - self.messages = response.messages - return response - - approved = await _ask_for_approvals(response.interrupts) - if not approved: - print('Tool denied.') # noqa: T201 - self.messages = response.messages - return response - - print('Resuming...') # noqa: T201 - restart = approved - self.messages = response.messages - - -async def _ask_for_approvals(interrupts: list[ToolRequestPart]) -> list[ToolRequestPart]: - """Prompt the user y/N for each pending interrupt; return the approved restart parts.""" - approved: list[ToolRequestPart] = [] - for trp in interrupts: - print('\n*** Tool Approval Required ***') # noqa: T201 - print(f'Tool: {trp.tool_request.name}') # noqa: T201 - print(f'Input: {trp.tool_request.input}') # noqa: T201 - if input('Approve? (y/N): ').strip().lower() in ('y', 'yes'): - approved.append( - restart_tool(interrupt=trp, resumed_metadata={'tool_approved': True}), - ) - return approved - - -async def main() -> None: - """Interactive REPL — one ``CodingAgent`` per process, one ``turn()`` per user line.""" - _WORKSPACE.mkdir(parents=True, exist_ok=True) - - print('--- Coding Agent ---') # noqa: T201 - print('Type your request. To exit, type "exit".') # noqa: T201 - - agent = CodingAgent() - - while True: - try: - user_input = input('\n> ').strip() - except EOFError: - break - if user_input.lower() == 'exit': - break - if not user_input: - continue - - try: - response = await agent.turn(user_input) - except Exception as e: # noqa: BLE001 - top-level REPL: surface, don't crash - print(f'Error during generation: {e}') # noqa: T201 - continue - - print(f'\nAI Response:\n{response.text}') # noqa: T201 - - -if __name__ == '__main__': - ai.run_main(main()) diff --git a/samples/prompts/src/main.py b/samples/prompts/src/main.py index 601477e0..4cd27b92 100755 --- a/samples/prompts/src/main.py +++ b/samples/prompts/src/main.py @@ -21,8 +21,7 @@ from genkit_google_genai import GoogleAI from pydantic import BaseModel, Field -from genkit import Genkit -from genkit._core._action import ActionRunContext +from genkit import ActionRunContext, Genkit ai = Genkit( plugins=[GoogleAI()], diff --git a/uv.lock b/uv.lock index 545dc9cc..c99c39f5 100644 --- a/uv.lock +++ b/uv.lock @@ -17,7 +17,6 @@ members = [ "context", "django-hello", "evaluators", - "fastapi-bugbot", "flask-hello", "gemini-code-execution", "gemini-context-caching", @@ -36,7 +35,6 @@ members = [ "genkit-workspace", "google-genai-media", "middleware", - "middleware-coding-agent", "ollama-sample", "output-formats", "prompts", @@ -1440,27 +1438,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/dd/d0ee25348ac58245ee9f90b6f3cbb666bf01f69be7e0911f9851bddbda16/fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec", size = 102950, upload-time = "2026-02-12T13:54:54.528Z" }, ] -[[package]] -name = "fastapi-bugbot" -version = "0.2.0" -source = { editable = "samples/fastapi-bugbot" } -dependencies = [ - { name = "genkit" }, - { name = "genkit-fastapi" }, - { name = "genkit-google-genai" }, - { name = "python-dotenv" }, - { name = "uvicorn", extra = ["standard"] }, -] - -[package.metadata] -requires-dist = [ - { name = "genkit", editable = "packages/genkit" }, - { name = "genkit-fastapi", editable = "packages/genkit-fastapi" }, - { name = "genkit-google-genai", editable = "packages/genkit-google-genai" }, - { name = "python-dotenv", specifier = ">=1.0.0" }, - { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" }, -] - [[package]] name = "fastjsonschema" version = "2.21.2" @@ -3742,27 +3719,6 @@ requires-dist = [ { name = "structlog", specifier = ">=24.0.0" }, ] -[[package]] -name = "middleware-coding-agent" -version = "0.1.0" -source = { editable = "samples/middleware-coding-agent" } -dependencies = [ - { name = "genkit" }, - { name = "genkit-google-genai" }, - { name = "genkit-middleware" }, - { name = "pydantic" }, - { name = "structlog" }, -] - -[package.metadata] -requires-dist = [ - { name = "genkit", editable = "packages/genkit" }, - { name = "genkit-google-genai", editable = "packages/genkit-google-genai" }, - { name = "genkit-middleware", editable = "packages/genkit-middleware" }, - { name = "pydantic", specifier = ">=2.10.5" }, - { name = "structlog", specifier = ">=25.2.0" }, -] - [[package]] name = "mistune" version = "3.2.1"