genkit 0.3.0.dev2__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {genkit-0.3.0.dev2 → genkit-0.3.2}/PKG-INFO +6 -6
- {genkit-0.3.0.dev2 → genkit-0.3.2}/README.md +5 -4
- {genkit-0.3.0.dev2 → genkit-0.3.2}/pyproject.toml +1 -2
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_base.py +7 -4
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_base_async.py +31 -19
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_runtime.py +8 -8
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/generate.py +17 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/constants.py +1 -1
- genkit-0.3.2/src/genkit/core/trace/__init__.py +31 -0
- genkit-0.3.2/src/genkit/core/trace/default_exporter.py +172 -0
- genkit-0.3.2/src/genkit/core/trace/types.py +108 -0
- genkit-0.3.2/src/genkit/core/tracing.py +127 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/types/__init__.py +2 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/generate_test.py +7 -4
- genkit-0.3.0.dev2/src/genkit/core/tracing.py +0 -299
- {genkit-0.3.0.dev2 → genkit-0.3.2}/.gitignore +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/LICENSE +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_aio.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_plugin.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_registry.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_server.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/_compat.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/_util.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/channel.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/loop.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/document.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/embedding.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/evaluator.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/formats/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/formats/json.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/formats/types.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/messages.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/middleware.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/model.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/prompt.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/retriever.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/tools.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/codec.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_action.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_key.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_tracing.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_util.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/types.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/context.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/environment.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/error.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/extract.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/flows.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/reflection.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/registry.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/schema.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/status_types.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/typing.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/lang/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/lang/deprecations.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/plugins/.gitignore +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/py.typed +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/testing.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/__init__.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_adapters.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_base_server.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_info.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_manager.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_ports.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_server.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/signals.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/requests.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/typing.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/ai/ai_registry_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/aio/channel_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/document_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/formats/json_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/message_utils_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/middleware_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/model_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/prompt_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/codec_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/action_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/endpoints/reflection_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/environment_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/error_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/extract_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/registry_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/schema_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/status_types_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/lang/deprecations_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/veneer/server_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/veneer/veneer_test.py +0 -0
- {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/web/manager/signals_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: genkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Genkit AI Framework
|
|
5
5
|
Author: Google
|
|
6
6
|
License: Apache-2.0
|
|
@@ -30,7 +30,6 @@ Requires-Dist: partial-json-parser>=0.2.1.1.post5
|
|
|
30
30
|
Requires-Dist: pillow
|
|
31
31
|
Requires-Dist: psutil>=7.0.0
|
|
32
32
|
Requires-Dist: pydantic>=2.10.5
|
|
33
|
-
Requires-Dist: requests>=2.32.3
|
|
34
33
|
Requires-Dist: sse-starlette>=2.2.1
|
|
35
34
|
Requires-Dist: starlette>=0.46.1
|
|
36
35
|
Requires-Dist: strenum>=0.4.15; python_version < '3.11'
|
|
@@ -66,14 +65,13 @@ pip install genkit-plugin-google-genai
|
|
|
66
65
|
```
|
|
67
66
|
|
|
68
67
|
```python
|
|
69
|
-
import asyncio
|
|
70
68
|
import json
|
|
71
69
|
from pydantic import BaseModel, Field
|
|
72
70
|
from genkit.ai import Genkit
|
|
73
|
-
from genkit.plugins.google_genai import
|
|
71
|
+
from genkit.plugins.google_genai import GoogleAI
|
|
74
72
|
|
|
75
73
|
ai = Genkit(
|
|
76
|
-
plugins=[
|
|
74
|
+
plugins=[GoogleAI()],
|
|
77
75
|
model='googleai/gemini-2.0-flash',
|
|
78
76
|
)
|
|
79
77
|
|
|
@@ -97,5 +95,7 @@ async def main() -> None:
|
|
|
97
95
|
"""Main function."""
|
|
98
96
|
print(json.dumps(await generate_character('Goblorb')))
|
|
99
97
|
|
|
100
|
-
|
|
98
|
+
ai.run_main(main())
|
|
101
99
|
```
|
|
100
|
+
|
|
101
|
+
See https://python.api.genkit.dev for more details.
|
|
@@ -16,14 +16,13 @@ pip install genkit-plugin-google-genai
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```python
|
|
19
|
-
import asyncio
|
|
20
19
|
import json
|
|
21
20
|
from pydantic import BaseModel, Field
|
|
22
21
|
from genkit.ai import Genkit
|
|
23
|
-
from genkit.plugins.google_genai import
|
|
22
|
+
from genkit.plugins.google_genai import GoogleAI
|
|
24
23
|
|
|
25
24
|
ai = Genkit(
|
|
26
|
-
plugins=[
|
|
25
|
+
plugins=[GoogleAI()],
|
|
27
26
|
model='googleai/gemini-2.0-flash',
|
|
28
27
|
)
|
|
29
28
|
|
|
@@ -47,5 +46,7 @@ async def main() -> None:
|
|
|
47
46
|
"""Main function."""
|
|
48
47
|
print(json.dumps(await generate_character('Goblorb')))
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
ai.run_main(main())
|
|
51
50
|
```
|
|
51
|
+
|
|
52
|
+
See https://python.api.genkit.dev for more details.
|
|
@@ -20,7 +20,6 @@ dependencies = [
|
|
|
20
20
|
"opentelemetry-api>=1.29.0",
|
|
21
21
|
"opentelemetry-sdk>=1.29.0",
|
|
22
22
|
"pydantic>=2.10.5",
|
|
23
|
-
"requests>=2.32.3",
|
|
24
23
|
"partial-json-parser>=0.2.1.1.post5",
|
|
25
24
|
"json5>=0.10.0",
|
|
26
25
|
"structlog>=25.2.0",
|
|
@@ -39,7 +38,7 @@ license = { text = "Apache-2.0" }
|
|
|
39
38
|
name = "genkit"
|
|
40
39
|
readme = "README.md"
|
|
41
40
|
requires-python = ">=3.10"
|
|
42
|
-
version = "0.3.
|
|
41
|
+
version = "0.3.2"
|
|
43
42
|
|
|
44
43
|
[project.optional-dependencies]
|
|
45
44
|
dev-local-vectorstore = ["genkit-plugin-dev-local-vectorstore"]
|
|
@@ -20,12 +20,13 @@ import asyncio
|
|
|
20
20
|
import threading
|
|
21
21
|
from collections.abc import Coroutine
|
|
22
22
|
from http.server import HTTPServer
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any, TypeVar
|
|
24
24
|
|
|
25
25
|
import structlog
|
|
26
26
|
|
|
27
27
|
from genkit.aio.loop import create_loop, run_async
|
|
28
28
|
from genkit.blocks.formats import built_in_formats
|
|
29
|
+
from genkit.blocks.generate import define_generate_action
|
|
29
30
|
from genkit.core.environment import is_dev_environment
|
|
30
31
|
from genkit.core.reflection import make_reflection_server
|
|
31
32
|
from genkit.web.manager import find_free_port_sync
|
|
@@ -36,6 +37,8 @@ from ._server import ServerSpec, init_default_runtime
|
|
|
36
37
|
|
|
37
38
|
logger = structlog.get_logger(__name__)
|
|
38
39
|
|
|
40
|
+
T = TypeVar('T')
|
|
41
|
+
|
|
39
42
|
|
|
40
43
|
class GenkitBase(GenkitRegistry):
|
|
41
44
|
"""Base class with shared infra for Genkit instances (sync and async)."""
|
|
@@ -57,8 +60,9 @@ class GenkitBase(GenkitRegistry):
|
|
|
57
60
|
super().__init__()
|
|
58
61
|
self._initialize_server(reflection_server_spec)
|
|
59
62
|
self._initialize_registry(model, plugins)
|
|
63
|
+
define_generate_action(self.registry)
|
|
60
64
|
|
|
61
|
-
def run_main(self, coro: Coroutine[Any, Any,
|
|
65
|
+
def run_main(self, coro: Coroutine[Any, Any, T] | None = None) -> T:
|
|
62
66
|
"""Runs the provided coroutine on an event loop.
|
|
63
67
|
|
|
64
68
|
Args:
|
|
@@ -67,7 +71,6 @@ class GenkitBase(GenkitRegistry):
|
|
|
67
71
|
Returns:
|
|
68
72
|
The result of the coroutine.
|
|
69
73
|
"""
|
|
70
|
-
|
|
71
74
|
if not coro:
|
|
72
75
|
|
|
73
76
|
async def blank_coro():
|
|
@@ -78,7 +81,7 @@ class GenkitBase(GenkitRegistry):
|
|
|
78
81
|
result = None
|
|
79
82
|
if self._loop:
|
|
80
83
|
|
|
81
|
-
async def run() ->
|
|
84
|
+
async def run() -> T:
|
|
82
85
|
return await coro
|
|
83
86
|
|
|
84
87
|
result = run_async(self._loop, run)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
|
-
"""
|
|
17
|
+
"""Asynchronous server gateway interface implementation for Genkit."""
|
|
18
18
|
|
|
19
19
|
from collections.abc import Coroutine
|
|
20
20
|
from typing import Any, TypeVar
|
|
@@ -91,15 +91,17 @@ class GenkitBase(GenkitRegistry):
|
|
|
91
91
|
else:
|
|
92
92
|
raise ValueError(f'Invalid {plugin=} provided to Genkit: must be of type `genkit.ai.Plugin`')
|
|
93
93
|
|
|
94
|
-
def
|
|
94
|
+
def run_main(self, coro: Coroutine[Any, Any, T]) -> T:
|
|
95
95
|
"""Run the user's main coroutine.
|
|
96
96
|
|
|
97
|
-
In development mode (`GENKIT_ENV=dev`), this starts the Genkit
|
|
98
|
-
server and runs the user's coroutine concurrently within the
|
|
99
|
-
blocking until the server is stopped (e.g., via
|
|
97
|
+
In development mode (`GENKIT_ENV=dev`), this starts the Genkit
|
|
98
|
+
reflection server and runs the user's coroutine concurrently within the
|
|
99
|
+
same event loop, blocking until the server is stopped (e.g., via
|
|
100
|
+
Ctrl+C).
|
|
100
101
|
|
|
101
102
|
In production mode, this simply runs the user's coroutine to completion
|
|
102
|
-
using `
|
|
103
|
+
using `uvloop.run()` for performance if available, otherwise
|
|
104
|
+
`asyncio.run()`.
|
|
103
105
|
|
|
104
106
|
Args:
|
|
105
107
|
coro: The main coroutine provided by the user.
|
|
@@ -111,7 +113,6 @@ class GenkitBase(GenkitRegistry):
|
|
|
111
113
|
logger.info('Running in production mode.')
|
|
112
114
|
return run_loop(coro)
|
|
113
115
|
|
|
114
|
-
# Development mode: Start reflection server and user coro concurrently.
|
|
115
116
|
logger.info('Running in development mode.')
|
|
116
117
|
|
|
117
118
|
spec = self._reflection_server_spec
|
|
@@ -138,24 +139,24 @@ class GenkitBase(GenkitRegistry):
|
|
|
138
139
|
finally:
|
|
139
140
|
user_task_finished_event.set()
|
|
140
141
|
|
|
141
|
-
reflection_server =
|
|
142
|
+
reflection_server = _make_reflection_server(self.registry, spec)
|
|
142
143
|
|
|
143
144
|
try:
|
|
144
145
|
async with RuntimeManager(spec):
|
|
145
|
-
# We use anyio
|
|
146
|
+
# We use anyio.TaskGroup because it is compatible with
|
|
146
147
|
# asyncio's event loop and works with Python 3.10
|
|
147
|
-
# (asyncio.
|
|
148
|
-
#
|
|
148
|
+
# (asyncio.TaskGroup was added in 3.11, and we can switch to
|
|
149
|
+
# that when we drop support for 3.10).
|
|
149
150
|
async with anyio.create_task_group() as tg:
|
|
150
151
|
# Start reflection server in the background.
|
|
151
152
|
tg.start_soon(reflection_server.serve, name='genkit-reflection-server')
|
|
152
|
-
await
|
|
153
|
-
logger.info(f'Started Genkit reflection server at {spec.scheme}://{spec.host}:{spec.port}')
|
|
153
|
+
await logger.ainfo(f'Started Genkit reflection server at {spec.url}')
|
|
154
154
|
|
|
155
155
|
# Start the (potentially short-lived) user coroutine wrapper
|
|
156
156
|
tg.start_soon(run_user_coro_wrapper, name='genkit-user-coroutine')
|
|
157
|
+
await logger.ainfo('Started Genkit user coroutine')
|
|
157
158
|
|
|
158
|
-
# Block here until the
|
|
159
|
+
# Block here until the task group is canceled (e.g. Ctrl+C)
|
|
159
160
|
# or a task raises an unhandled exception. It should not
|
|
160
161
|
# exit just because the user coroutine finishes.
|
|
161
162
|
|
|
@@ -166,19 +167,30 @@ class GenkitBase(GenkitRegistry):
|
|
|
166
167
|
logger.exception(e)
|
|
167
168
|
raise
|
|
168
169
|
|
|
169
|
-
# After the TaskGroup finishes (error or
|
|
170
|
+
# After the TaskGroup finishes (error or cancelation).
|
|
170
171
|
if user_task_finished_event.is_set():
|
|
171
|
-
logger.
|
|
172
|
+
await logger.adebug('User coroutine finished before TaskGroup exit.')
|
|
172
173
|
return user_result
|
|
173
174
|
else:
|
|
174
|
-
logger.
|
|
175
|
+
await logger.adebug('User coroutine did not finish before TaskGroup exit.')
|
|
175
176
|
return None
|
|
176
177
|
|
|
177
178
|
return anyio.run(dev_runner)
|
|
178
179
|
|
|
179
180
|
|
|
180
|
-
def
|
|
181
|
-
"""Make a reflection server for the given registry and spec.
|
|
181
|
+
def _make_reflection_server(registry: GenkitRegistry, spec: ServerSpec) -> uvicorn.Server:
|
|
182
|
+
"""Make a reflection server for the given registry and spec.
|
|
183
|
+
|
|
184
|
+
This is a helper function to make it easier to test the reflection server
|
|
185
|
+
in isolation.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
registry: The registry to use for the reflection server.
|
|
189
|
+
spec: The spec to use for the reflection server.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
A uvicorn server instance.
|
|
193
|
+
"""
|
|
182
194
|
app = create_reflection_asgi_app(registry=registry)
|
|
183
195
|
config = uvicorn.Config(app, host=spec.host, port=spec.port, loop='asyncio')
|
|
184
196
|
return uvicorn.Server(config)
|
|
@@ -145,18 +145,18 @@ class RuntimeManager:
|
|
|
145
145
|
"""
|
|
146
146
|
self.spec = spec
|
|
147
147
|
if runtime_dir is None:
|
|
148
|
-
self.
|
|
148
|
+
self._runtime_dir = Path(os.getcwd()) / DEFAULT_RUNTIME_DIR_NAME
|
|
149
149
|
else:
|
|
150
|
-
self.
|
|
150
|
+
self._runtime_dir = Path(runtime_dir)
|
|
151
151
|
|
|
152
152
|
self._runtime_file_path: Path | None = None
|
|
153
153
|
|
|
154
154
|
async def __aenter__(self) -> RuntimeManager:
|
|
155
155
|
"""Create the runtime directory and file."""
|
|
156
156
|
try:
|
|
157
|
-
await logger.adebug(f'Ensuring runtime directory exists: {self.
|
|
158
|
-
self.
|
|
159
|
-
runtime_file_path = _create_and_write_runtime_file(self.
|
|
157
|
+
await logger.adebug(f'Ensuring runtime directory exists: {self._runtime_dir}')
|
|
158
|
+
self._runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
runtime_file_path = _create_and_write_runtime_file(self._runtime_dir, self.spec)
|
|
160
160
|
_register_atexit_cleanup_handler(runtime_file_path)
|
|
161
161
|
|
|
162
162
|
except Exception as e:
|
|
@@ -186,9 +186,9 @@ class RuntimeManager:
|
|
|
186
186
|
def __enter__(self) -> RuntimeManager:
|
|
187
187
|
"""Synchronous entry point: Create the runtime directory and file."""
|
|
188
188
|
try:
|
|
189
|
-
logger.debug(f'[sync] Ensuring runtime directory exists: {self.
|
|
190
|
-
self.
|
|
191
|
-
self._runtime_file_path = _create_and_write_runtime_file(self.
|
|
189
|
+
logger.debug(f'[sync] Ensuring runtime directory exists: {self._runtime_dir}')
|
|
190
|
+
self._runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
self._runtime_file_path = _create_and_write_runtime_file(self._runtime_dir, self.spec)
|
|
192
192
|
_register_atexit_cleanup_handler(self._runtime_file_path)
|
|
193
193
|
|
|
194
194
|
except Exception as e:
|
|
@@ -56,6 +56,23 @@ StreamingCallback = Callable[[GenerateResponseChunkWrapper], None]
|
|
|
56
56
|
DEFAULT_MAX_TURNS = 5
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def define_generate_action(registry: Registry):
|
|
60
|
+
"""Registers generate action in the provided registry."""
|
|
61
|
+
async def genereate_action_fn(input: GenerateActionOptions, ctx: ActionRunContext) -> GenerateResponse:
|
|
62
|
+
return await generate_action(
|
|
63
|
+
registry=registry,
|
|
64
|
+
raw_request=input,
|
|
65
|
+
on_chunk=ctx.send_chunk if ctx.is_streaming else None,
|
|
66
|
+
context=ctx.context,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
registry.register_action(
|
|
70
|
+
kind=ActionKind.UTIL,
|
|
71
|
+
name='generate',
|
|
72
|
+
fn=genereate_action_fn,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
59
76
|
async def generate_action(
|
|
60
77
|
registry: Registry,
|
|
61
78
|
raw_request: GenerateActionOptions,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
|
|
17
|
+
"""Trace module for defining opentelemetry managing."""
|
|
18
|
+
|
|
19
|
+
from .default_exporter import (
|
|
20
|
+
TelemetryServerSpanExporter,
|
|
21
|
+
init_telemetry_server_exporter,
|
|
22
|
+
)
|
|
23
|
+
from .types import (
|
|
24
|
+
GenkitSpan,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
TelemetryServerSpanExporter.__name__,
|
|
29
|
+
init_telemetry_server_exporter.__name__,
|
|
30
|
+
GenkitSpan.__name__,
|
|
31
|
+
]
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
|
|
17
|
+
"""Telemetry and tracing default exporter for the Genkit framework.
|
|
18
|
+
|
|
19
|
+
This module provides functionality for collecting and exporting telemetry data
|
|
20
|
+
from Genkit operations. It uses OpenTelemetry for tracing and exports span
|
|
21
|
+
data to a telemetry server for monitoring and debugging purposes.
|
|
22
|
+
|
|
23
|
+
The module includes:
|
|
24
|
+
- A custom span exporter for sending trace data to a telemetry server
|
|
25
|
+
- Utility functions for converting and formatting trace attributes
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
from collections.abc import Awaitable, Sequence
|
|
33
|
+
from typing import Any
|
|
34
|
+
from urllib.parse import urljoin
|
|
35
|
+
|
|
36
|
+
import httpx
|
|
37
|
+
import structlog
|
|
38
|
+
from opentelemetry import trace as trace_api
|
|
39
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
40
|
+
from opentelemetry.sdk.trace.export import (
|
|
41
|
+
SpanExporter,
|
|
42
|
+
SpanExportResult,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
ATTR_PREFIX = 'genkit'
|
|
46
|
+
logger = structlog.get_logger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def extract_span_data(span: ReadableSpan) -> dict[str, Any]:
|
|
50
|
+
"""Extract span data from a ReadableSpan object.
|
|
51
|
+
|
|
52
|
+
This function extracts the span data from a ReadableSpan object and returns
|
|
53
|
+
a dictionary containing the span data.
|
|
54
|
+
"""
|
|
55
|
+
span_data = {'traceId': f'{span.context.trace_id}', 'spans': {}}
|
|
56
|
+
span_data['spans'][span.context.span_id] = {
|
|
57
|
+
'spanId': f'{span.context.span_id}',
|
|
58
|
+
'traceId': f'{span.context.trace_id}',
|
|
59
|
+
'startTime': span.start_time / 1000000,
|
|
60
|
+
'endTime': span.end_time / 1000000,
|
|
61
|
+
'attributes': {**span.attributes},
|
|
62
|
+
'displayName': span.name,
|
|
63
|
+
# "links": span.links,
|
|
64
|
+
'spanKind': trace_api.SpanKind(span.kind).name,
|
|
65
|
+
'parentSpanId': f'{span.parent.span_id}' if span.parent else None,
|
|
66
|
+
'status': (
|
|
67
|
+
{
|
|
68
|
+
'code': trace_api.StatusCode(span.status.status_code).value,
|
|
69
|
+
'description': span.status.description,
|
|
70
|
+
}
|
|
71
|
+
if span.status
|
|
72
|
+
else None
|
|
73
|
+
),
|
|
74
|
+
'instrumentationLibrary': {
|
|
75
|
+
'name': 'genkit-tracer',
|
|
76
|
+
'version': 'v1',
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
if not span_data['spans'][span.context.span_id]['parentSpanId']: # type: ignore
|
|
80
|
+
del span_data['spans'][span.context.span_id]['parentSpanId'] # type: ignore
|
|
81
|
+
|
|
82
|
+
if not span.parent:
|
|
83
|
+
span_data['displayName'] = span.name
|
|
84
|
+
span_data['startTime'] = span.start_time
|
|
85
|
+
span_data['endTime'] = span.end_time
|
|
86
|
+
|
|
87
|
+
return span_data
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TelemetryServerSpanExporter(SpanExporter):
|
|
91
|
+
"""Exports spans to a Genkit telemetry server.
|
|
92
|
+
|
|
93
|
+
This exporter sends span data in a specific JSON format to a telemetry server,
|
|
94
|
+
typically running locally during development, for visualization and debugging.
|
|
95
|
+
|
|
96
|
+
Attributes:
|
|
97
|
+
telemetry_server_url: The URL of the telemetry server endpoint.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, telemetry_server_url: str, telemetry_server_endpoint: str | None = None):
|
|
101
|
+
"""Initializes the TelemetryServerSpanExporter.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
telemetry_server_url: The URL of the telemetry server.
|
|
105
|
+
telemetry_server_endpoint (optional): The telemetry server's trace endpoint.
|
|
106
|
+
"""
|
|
107
|
+
self.telemetry_server_url = telemetry_server_url
|
|
108
|
+
if telemetry_server_endpoint is None:
|
|
109
|
+
self.telemetry_server_endpoint = '/api/traces'
|
|
110
|
+
else:
|
|
111
|
+
self.telemetry_server_endpoint = telemetry_server_endpoint
|
|
112
|
+
|
|
113
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
|
114
|
+
"""Exports a sequence of ReadableSpans to the configured telemetry server.
|
|
115
|
+
|
|
116
|
+
Iterates through the provided spans, extracts relevant data using
|
|
117
|
+
`extract_span_data`, converts it to JSON, and sends it via an HTTP POST
|
|
118
|
+
request to the `telemetry_server_url`.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
spans: A sequence of OpenTelemetry ReadableSpan objects to export.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
SpanExportResult.SUCCESS upon successful processing (does not guarantee
|
|
125
|
+
server-side success).
|
|
126
|
+
"""
|
|
127
|
+
with httpx.Client() as client:
|
|
128
|
+
for span in spans:
|
|
129
|
+
client.post(
|
|
130
|
+
urljoin(self.telemetry_server_url, self.telemetry_server_endpoint),
|
|
131
|
+
data=json.dumps(extract_span_data(span)),
|
|
132
|
+
headers={
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'Accept': 'application/json',
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
sys.stdout.flush()
|
|
139
|
+
|
|
140
|
+
return SpanExportResult.SUCCESS
|
|
141
|
+
|
|
142
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
143
|
+
"""Forces the exporter to flush any buffered spans.
|
|
144
|
+
|
|
145
|
+
Since this exporter sends spans immediately in the `export` method,
|
|
146
|
+
this method currently does nothing but return True.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
timeout_millis: The maximum time in milliseconds to wait for the flush.
|
|
150
|
+
This parameter is ignored in the current implementation.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True, indicating the flush operation is considered complete.
|
|
154
|
+
"""
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def init_telemetry_server_exporter() -> SpanExporter | None:
|
|
159
|
+
"""Initializes tracing with a provider and optional exporter."""
|
|
160
|
+
telemetry_server_url = os.environ.get('GENKIT_TELEMETRY_SERVER')
|
|
161
|
+
processor = None
|
|
162
|
+
|
|
163
|
+
if telemetry_server_url:
|
|
164
|
+
processor = TelemetryServerSpanExporter(
|
|
165
|
+
telemetry_server_url=telemetry_server_url,
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
logger.warn(
|
|
169
|
+
'GENKIT_TELEMETRY_SERVER is not set. If running with `genkit start`, make sure `genkit-cli` is up to date.'
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return processor
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
|
|
17
|
+
"""Telemetry and tracing types for the Genkit framework.
|
|
18
|
+
|
|
19
|
+
This module defines the core tracing types for the Genkit framework.
|
|
20
|
+
|
|
21
|
+
Genkit flows are instrumented to trace key data points, primarily the inputs
|
|
22
|
+
sent to and the outputs received from Language Learning Models (LLMs) or
|
|
23
|
+
other models. Outputs may be streamed as chunks. This module establishes
|
|
24
|
+
how these interactions are recorded and exported for observability.
|
|
25
|
+
|
|
26
|
+
Key Features:
|
|
27
|
+
|
|
28
|
+
- **GenkitSpan Class:** Provides a core class, `GenkitSpan`, for representing
|
|
29
|
+
and exporting telemetry data related to Genkit operations. This class
|
|
30
|
+
is intended for internal use by the Genkit framework.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
from collections.abc import Mapping
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
import structlog
|
|
38
|
+
from opentelemetry import trace as trace_api
|
|
39
|
+
from opentelemetry.util import types
|
|
40
|
+
from pydantic import BaseModel
|
|
41
|
+
|
|
42
|
+
ATTR_PREFIX = 'genkit'
|
|
43
|
+
|
|
44
|
+
logger = structlog.get_logger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GenkitSpan:
|
|
48
|
+
"""Light wrapper for Span, specific to Genkit."""
|
|
49
|
+
|
|
50
|
+
is_root: bool
|
|
51
|
+
_span: trace_api.Span
|
|
52
|
+
|
|
53
|
+
def __init__(self, span: trace_api.Span, labels: dict[str, str] | None = None):
|
|
54
|
+
"""Create GenkitSpan."""
|
|
55
|
+
self._span = span
|
|
56
|
+
parent = span.parent
|
|
57
|
+
self.is_root = False
|
|
58
|
+
if parent is None:
|
|
59
|
+
self.is_root = True
|
|
60
|
+
if labels is not None:
|
|
61
|
+
self.set_attributes(labels)
|
|
62
|
+
|
|
63
|
+
def __getattr__(self, name) -> Any:
|
|
64
|
+
"""Passthrough for all OpenTelemetry Span attributes."""
|
|
65
|
+
return getattr(self._span, name)
|
|
66
|
+
|
|
67
|
+
def set_genkit_attribute(self, key: str, value: types.AttributeValue) -> None:
|
|
68
|
+
"""Set Genkit specific attribute, with the `genkit` prefix."""
|
|
69
|
+
if key == 'metadata' and isinstance(value, dict) and value:
|
|
70
|
+
for meta_key, meta_value in value.items():
|
|
71
|
+
self._span.set_attribute(f'{ATTR_PREFIX}:metadata:{meta_key}', str(meta_value))
|
|
72
|
+
elif isinstance(value, dict):
|
|
73
|
+
self._span.set_attribute(f'{ATTR_PREFIX}:{key}', json.dumps(value))
|
|
74
|
+
else:
|
|
75
|
+
self._span.set_attribute(f'{ATTR_PREFIX}:{key}', str(value))
|
|
76
|
+
|
|
77
|
+
def set_genkit_attributes(self, attributes: Mapping[str, types.AttributeValue]) -> None:
|
|
78
|
+
"""Set Genkit specific attributes, with the `genkit` prefix."""
|
|
79
|
+
for key, value in attributes.items():
|
|
80
|
+
self.set_genkit_attribute(key, value)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def span_id(self) -> str:
|
|
84
|
+
"""Returns the span_id."""
|
|
85
|
+
return str(self._span.get_span_context().span_id)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def trace_id(self) -> str:
|
|
89
|
+
"""Returns the trace_id."""
|
|
90
|
+
return str(self._span.get_span_context().trace_id)
|
|
91
|
+
|
|
92
|
+
def set_input(self, input: Any) -> None:
|
|
93
|
+
"""Set Genkit Span input, visible in the trace viewer."""
|
|
94
|
+
value = None
|
|
95
|
+
if isinstance(input, BaseModel):
|
|
96
|
+
value = input.model_dump_json(by_alias=True, exclude_none=True)
|
|
97
|
+
else:
|
|
98
|
+
value = json.dumps(input)
|
|
99
|
+
self.set_genkit_attribute('input', value)
|
|
100
|
+
|
|
101
|
+
def set_output(self, output: Any) -> None:
|
|
102
|
+
"""Set Genkit Span output, visible in the trace viewer."""
|
|
103
|
+
value = None
|
|
104
|
+
if isinstance(output, BaseModel):
|
|
105
|
+
value = output.model_dump_json(by_alias=True, exclude_none=True)
|
|
106
|
+
else:
|
|
107
|
+
value = json.dumps(output)
|
|
108
|
+
self.set_genkit_attribute('output', value)
|