diff --git a/README.md b/README.md index 37cade65..1f221b3a 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,223 @@ +![Genkit logo](docs/resources/genkit-logo-dark.png#gh-dark-mode-only 'Genkit') +![Genkit logo](docs/resources/genkit-logo.png#gh-light-mode-only 'Genkit') + # Genkit Python SDK -Build powerful agentic apps with Genkit Python. Genkit is Google's open-source framework for building full-stack, AI-powered and agentic applications. +Build production-ready AI applications in Python with type-safe flows, structured outputs, and integrated observability. -## Quick Start +[Documentation](https://genkit.dev/docs/python/get-started/) | [Samples](samples/) | [Discord](https://discord.gg/qXt5zzQKpc) | [Report Issue](https://github.com/genkit-ai/genkit-python/issues) -Get started in three simple steps: +--- -1. **Install the SDK and your preferred model provider:** -```bash -uv add genkit genkit-google-genai -``` +## Quick Start & Core Patterns + +Install the SDK and model provider: -2. **Set your API key:** ```bash +uv add genkit genkit-google-genai genkit-google-cloud genkit-middleware export GEMINI_API_KEY="your-api-key" ``` -3. **Create your AI application:** +### 1. Streaming Generation + ```python from genkit import Genkit from genkit_google_genai import GoogleAI -# 1. Initialize Genkit with the Google AI (Gemini) plugin ai = Genkit(plugins=[GoogleAI()]) -# 2. Define a type-safe tool -@ai.tool(description="Get current weather for a city") -def get_weather(city: str) -> str: - return f"Sunny, 72°F in {city}" - -# 3. Define an observable flow -@ai.flow() -async def plan_trip(destination: str) -> str: - response = await ai.generate( - model="googleai/gemini-flash-latest", - prompt=f"Suggest activities in {destination} given the weather.", - tools=[get_weather], - ) - return response.text # => "Based on the sunny weather in Seattle..." +# Stream text responses in real-time +stream = ai.generate_stream( + model="googleai/gemini-flash-latest", + prompt="Stream a 2-line poem about space.", +) +async for chunk in stream: + print(chunk.text, end="") + +# Access final response metadata +response = await stream.response ``` -## Why Genkit? +### 2. Streaming Tool Calling & Structured Output -- **Type-Safe by Design:** Leverage Python type annotations and Pydantic models for structured inputs, outputs, and tool definitions. -- **Multi-Model Provider API:** Switch effortlessly between Google Gemini, Anthropic Claude, OpenAI, Ollama, and Vertex AI with a unified API. -- **Integrated Observability:** Built-in OpenTelemetry tracing and evaluation metrics. Inspect spans and debug flows in real-time using the Genkit Developer UI (`genkit start`). -- **Deploy Anywhere:** Expose flows as standard ASGI/WSGI applications compatible with FastAPI, Flask, Django, Cloud Run, or any serverless platform. +```python +from pydantic import BaseModel, Field +from genkit import Genkit +from genkit_google_genai import GoogleAI ---- +ai = Genkit(plugins=[GoogleAI()]) -## Repository & Development Guidelines +# Define a tool with Pydantic type annotations +class WeatherInput(BaseModel): + city: str = Field(description="Target city name") + +@ai.tool(description="Get current weather for a location") +async def get_weather(input: WeatherInput) -> str: + return f"Sunny, 72°F in {input.city}" + +# Define structured output schema +class ActivityPlan(BaseModel): + activities: list[str] + outfit: str + +# Stream response with automatic tool execution and structured output +stream = ai.generate_stream( + model="googleai/gemini-flash-latest", + prompt="Suggest activities for Seattle today.", + tools=[get_weather], + output_schema=ActivityPlan, +) +async for chunk in stream: + if chunk.text: + print(chunk.text, end="") + +# Access validated Pydantic output object +response = await stream.response +print(response.output) +# => ActivityPlan(activities=['Kayak on Lake Union', 'Discovery Park'], outfit='Light jacket') +``` + +### 3. Tool Approval Middleware & Restarts -This section covers onboarding and common development workflows for contributing to the Genkit Python SDK. +```python +from genkit import Genkit +from genkit_google_genai import GoogleAI +from genkit_middleware import Middleware, ToolApproval + +ai = Genkit(plugins=[GoogleAI(), Middleware()]) +tool_approval = ToolApproval(allowed_tools=[]) + +@ai.tool(description="Transfer money to an account") +async def transfer_money(amount: float, to_account: str) -> str: + return f"Transferred ${amount} to {to_account}" + +agent = ai.define_agent( + name="bankingAgent", + model="googleai/gemini-flash-latest", + system="Banking assistant. Call transfer_money when requested.", + tools=[transfer_money], + use=[tool_approval], +) + +chat = agent.chat() +out1 = await chat.send("Transfer $100 to account 999.") +# => Returns INTERRUPTED status because transfer_money requires approval + +# Approve pending tool interrupts and resume execution +restarts = [intr.restart(resumed_metadata={"tool_approved": True}) for intr in out1.interrupts] +out2 = await chat.resume(restart=restarts) +print(out2.text) +# => "$100 has been successfully transferred to account 999." +``` -### Prerequisites -- **Python 3.10+** -- **[uv](https://docs.astral.sh/uv/getting-started/installation/):** Fast Python package and project manager (`curl -LsSf https://astral.sh/uv/install.sh | sh`) -- **[just](https://github.com/casey/just#installation):** Modern command runner (`brew install just` or `cargo install just`) +### 4. Agent Loops & Persistent Sessions -### Workspace Structure +```python +from genkit import Genkit +from genkit_google_cloud import FirestoreSessionStore +from genkit_google_genai import GoogleAI + +ai = Genkit(plugins=[GoogleAI()]) + +# Persist multi-turn session history in Cloud Firestore +store = FirestoreSessionStore() + +# Define an agent with persistent session memory +agent = ai.define_agent( + name="supportAgent", + model="googleai/gemini-flash-latest", + system="You are a helpful customer support agent.", + store=store, +) + +# Multi-turn chat with automatic persistent session state +chat = agent.chat() +res1 = await chat.send("Hi, my name is Alex.") +res2 = await chat.send("What was my name again?") +print(res2.text) +# => "Your name is Alex!" ``` -py/ -├── bin/ # CI/CD and release automation scripts -├── docs/ # Playbooks and generated API reference templates -├── packages/ # Core framework and official integrations -├── samples/ # Runnable example applications and demos -├── scripts/ # Maintenance and verification scripts -├── tests/ # Cross-package integration test suites -├── justfile # Command runner shortcuts (just py ) -├── noxfile.py # Multi-version test automation (3.10–3.14) -├── pyproject.toml # Workspace metadata and tool dependencies -└── uv.lock # Resolved dependency lockfile + +--- + +## Key Capabilities + + + + + + + + + + + + + + + + + + +
Type-Safe by DesignLeverage native Python type annotations and Pydantic models for structured inputs, outputs, and automatic tool schema generation.
Unified Model APISwitch effortlessly between Google Gemini, Anthropic Claude, OpenAI, Ollama, and Vertex AI using a single consistent interface.
Integrated ObservabilityBuilt-in OpenTelemetry tracing. Inspect execution graphs, token usage, latency, and step inputs/outputs locally in real-time.
Production DeploymentExpose flows as standard ASGI/WSGI applications compatible with FastAPI, Flask, Django, Cloud Run, or any serverless platform.
+ +--- + +## Developer Tools & Dev UI + +Accelerate AI development with the local Genkit Developer UI and CLI. + +```bash +genkit start -- uv run main.py ``` -### Development Commands (`just py`) +Key features: +- **Playground**: Run and experiment with Genkit flows, prompts, and tools in dedicated playgrounds. +- **Trace Inspector**: Analyze detailed execution traces, including step-by-step breakdowns of complex flows. +- **Evaluations**: Review performance metrics and evaluate model outputs over time. + +Screenshot of Genkit Developer UI showing traces -From the repository root, run `just py ` (or `just ` in `py/`): +--- + +## Exploring Samples & Onboarding + +Browse runnable, real-world applications in [`samples/`](samples/): -- **`sync`** — Install workspace dependencies (`uv sync`). -- **`lint`** — Run formatters, linters, and type checkers (maps to CI `lint-and-format` / `type-check`). -- **`fmt`** — Auto-format code and fix lint errors. -- **`test`** — Run unit tests (use `test-nox` to test Python 3.10–3.14 like CI). -- **`check`** — Validate workspace version consistency. +- **[Basic Flows](samples/basic-flows)**: Text generation, structured output, and tool calling +- **[Model Providers](samples/)**: Integrations for Gemini, Anthropic, OpenAI, Ollama, and Vertex AI +- **[Agentic Workflows](samples/agents)**: Multi-turn agents, session memory, and approval interrupts +- **[Web Frameworks](samples/)**: Integrations with FastAPI, Flask, and Django -### Running Samples +### Running Samples Locally -To run example applications from `samples/`, navigate to a sample directory and launch the Genkit Developer UI: +Clone the repository and launch any sample with the interactive Dev UI: ```bash cd samples/ -genkit start -- uv run +genkit start -- uv run main.py ``` -Open the Dev UI in your browser to interact with registered flows and agents directly. +--- + +## Local Development + +If you're contributing to the Python SDK: + +1. **Prerequisites**: Python 3.10+ and [`uv`](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`) +2. **Install Dependencies**: `uv sync` +3. **Run Linters & Tests**: `just lint` and `just test` + +For coding standards and detailed guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). + +--- + +## Connect with Us -### Documentation & Maintenance -- **API Reference:** For complete class and method signatures, see [docs/index.md](docs/index.md). -- **Contributing & Standards:** For coding conventions, commit guidelines, and type-checking rules, see [CONTRIBUTING.md](../CONTRIBUTING.md). -- **Release Playbook:** For maintainer release procedures, see [docs/release_playbook.md](docs/release_playbook.md). +- [**Follow us on X/Twitter**](https://x.com/GenkitFramework) – News, updates, and tips. +- [**Join us on Reddit**](https://reddit.com/r/GenkitFramework) – Community discussion and Q&A. +- [**Join us on Discord**](https://discord.gg/qXt5zzQKpc) – Get real-time help and chat with developers. +- [**Contribute on GitHub**](https://github.com/genkit-ai/genkit-python/issues) – Report bugs, suggest features, or submit PRs. ## License Apache 2.0 diff --git a/docs/resources/genkit-logo-dark.png b/docs/resources/genkit-logo-dark.png new file mode 100644 index 00000000..fee8b3e9 Binary files /dev/null and b/docs/resources/genkit-logo-dark.png differ diff --git a/docs/resources/genkit-logo.png b/docs/resources/genkit-logo.png new file mode 100644 index 00000000..e5e5aa60 Binary files /dev/null and b/docs/resources/genkit-logo.png differ diff --git a/docs/resources/readme-ui-traces-screenshot.png b/docs/resources/readme-ui-traces-screenshot.png new file mode 100644 index 00000000..59060cb9 Binary files /dev/null and b/docs/resources/readme-ui-traces-screenshot.png differ diff --git a/packages/genkit/tests/genkit/ai/generate_test.py b/packages/genkit/tests/genkit/ai/generate_test.py index e88c7d8c..ba5cd753 100644 --- a/packages/genkit/tests/genkit/ai/generate_test.py +++ b/packages/genkit/tests/genkit/ai/generate_test.py @@ -2259,6 +2259,8 @@ async def injected_tool() -> str: specs = [] spec_path = pathlib.Path(__file__).parent / '../../../../../../tests/specs/generate.yaml' +if not spec_path.resolve().exists(): + spec_path = pathlib.Path(__file__).parent / '../../../../../tests/specs/generate.yaml' with spec_path.resolve().open() as stream: tests_spec = yaml.safe_load(stream) specs = tests_spec['tests'] diff --git a/tests/specs/generate.yaml b/tests/specs/generate.yaml new file mode 100644 index 00000000..2be564c6 --- /dev/null +++ b/tests/specs/generate.yaml @@ -0,0 +1,143 @@ +# Copyright 2025 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +# This file describes the responses of /util/generate action + +tests: + - name: simple generate call + input: + { + model: 'programmableModel', + messages: [{ role: 'user', content: [{ text: 'hi' }] }], + config: { temperature: 11 }, + } + modelResponses: + - { + finishReason: 'stop', + message: { role: 'model', content: [{ text: 'final response' }] }, + } + expectResponse: + { + custom: {}, + finishReason: 'stop', + message: { role: 'model', content: [{ text: 'final response' }] }, + request: + { + messages: [{ role: 'user', content: [{ text: 'hi' }] }], + output: {}, + tools: [], + config: { temperature: 11 }, + }, + usage: {}, + } + - name: stream responses + stream: true + input: + { + model: 'programmableModel', + messages: [{ role: 'user', content: [{ text: 'hi' }] }], + config: { temperature: 11 }, + } + streamChunks: + - [ + { index: 0, role: 'model', content: [{ text: '3' }] }, + { index: 0, role: 'model', content: [{ text: '2' }] }, + { index: 0, role: 'model', content: [{ text: '1' }] }, + ] + modelResponses: + - { + finishReason: 'stop', + message: { role: 'model', content: [{ text: 'final response' }] }, + } + expectChunks: + [ + { index: 0, role: 'model', content: [{ text: '3' }] }, + { index: 0, role: 'model', content: [{ text: '2' }] }, + { index: 0, role: 'model', content: [{ text: '1' }] }, + ] + expectResponse: + { + custom: {}, + finishReason: 'stop', + message: { role: 'model', content: [{ text: 'final response' }] }, + request: + { + messages: [{ role: 'user', content: [{ text: 'hi' }] }], + output: {}, + tools: [], + config: { temperature: 11 }, + }, + usage: {}, + } + - name: calls tools + input: + { + model: 'programmableModel', + messages: [{ role: 'user', content: [{ text: 'hi' }] }], + config: { temperature: 11 }, + tools: ['testTool'], + } + modelResponses: + - { + message: + { + role: 'model', + content: + [ + { + toolRequest: { name: 'testTool', input: {}, ref: 'ref123' }, + }, + ], + }, + } + - { message: { role: 'model', content: [{ text: 'final response' }] } } + expectResponse: + { + custom: {}, + message: { role: 'model', content: [{ text: 'final response' }] }, + request: + { + messages: + [ + { role: 'user', content: [{ text: 'hi' }] }, + { + role: 'model', + content: + [ + { + toolRequest: + { input: {}, name: 'testTool', ref: 'ref123' }, + }, + ], + }, + { + role: 'tool', + content: + [ + { + toolResponse: + { + name: 'testTool', + output: 'tool called', + ref: 'ref123', + }, + }, + ], + }, + ], + output: {}, + tools: + [ + { + description: 'description', + inputSchema: + { $schema: 'http://json-schema.org/draft-07/schema#' }, + name: 'testTool', + outputSchema: + { $schema: 'http://json-schema.org/draft-07/schema#' }, + }, + ], + config: { temperature: 11 }, + }, + usage: {}, + }