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.
Files changed (95) hide show
  1. {genkit-0.3.0.dev2 → genkit-0.3.2}/PKG-INFO +6 -6
  2. {genkit-0.3.0.dev2 → genkit-0.3.2}/README.md +5 -4
  3. {genkit-0.3.0.dev2 → genkit-0.3.2}/pyproject.toml +1 -2
  4. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_base.py +7 -4
  5. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_base_async.py +31 -19
  6. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_runtime.py +8 -8
  7. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/generate.py +17 -0
  8. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/constants.py +1 -1
  9. genkit-0.3.2/src/genkit/core/trace/__init__.py +31 -0
  10. genkit-0.3.2/src/genkit/core/trace/default_exporter.py +172 -0
  11. genkit-0.3.2/src/genkit/core/trace/types.py +108 -0
  12. genkit-0.3.2/src/genkit/core/tracing.py +127 -0
  13. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/types/__init__.py +2 -0
  14. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/generate_test.py +7 -4
  15. genkit-0.3.0.dev2/src/genkit/core/tracing.py +0 -299
  16. {genkit-0.3.0.dev2 → genkit-0.3.2}/.gitignore +0 -0
  17. {genkit-0.3.0.dev2 → genkit-0.3.2}/LICENSE +0 -0
  18. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/__init__.py +0 -0
  19. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_aio.py +0 -0
  20. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_plugin.py +0 -0
  21. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_registry.py +0 -0
  22. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/ai/_server.py +0 -0
  23. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/__init__.py +0 -0
  24. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/_compat.py +0 -0
  25. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/_util.py +0 -0
  26. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/channel.py +0 -0
  27. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/aio/loop.py +0 -0
  28. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/__init__.py +0 -0
  29. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/document.py +0 -0
  30. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/embedding.py +0 -0
  31. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/evaluator.py +0 -0
  32. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/formats/__init__.py +0 -0
  33. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/formats/json.py +0 -0
  34. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/formats/types.py +0 -0
  35. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/messages.py +0 -0
  36. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/middleware.py +0 -0
  37. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/model.py +0 -0
  38. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/prompt.py +0 -0
  39. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/retriever.py +0 -0
  40. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/blocks/tools.py +0 -0
  41. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/codec.py +0 -0
  42. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/__init__.py +0 -0
  43. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/__init__.py +0 -0
  44. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_action.py +0 -0
  45. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_key.py +0 -0
  46. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_tracing.py +0 -0
  47. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/_util.py +0 -0
  48. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/action/types.py +0 -0
  49. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/context.py +0 -0
  50. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/environment.py +0 -0
  51. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/error.py +0 -0
  52. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/extract.py +0 -0
  53. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/flows.py +0 -0
  54. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/reflection.py +0 -0
  55. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/registry.py +0 -0
  56. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/schema.py +0 -0
  57. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/status_types.py +0 -0
  58. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/core/typing.py +0 -0
  59. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/lang/__init__.py +0 -0
  60. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/lang/deprecations.py +0 -0
  61. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/plugins/.gitignore +0 -0
  62. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/py.typed +0 -0
  63. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/testing.py +0 -0
  64. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/__init__.py +0 -0
  65. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/__init__.py +0 -0
  66. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_adapters.py +0 -0
  67. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_base_server.py +0 -0
  68. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_info.py +0 -0
  69. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_manager.py +0 -0
  70. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_ports.py +0 -0
  71. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/_server.py +0 -0
  72. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/manager/signals.py +0 -0
  73. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/requests.py +0 -0
  74. {genkit-0.3.0.dev2 → genkit-0.3.2}/src/genkit/web/typing.py +0 -0
  75. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/ai/ai_registry_test.py +0 -0
  76. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/aio/channel_test.py +0 -0
  77. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/document_test.py +0 -0
  78. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/formats/json_test.py +0 -0
  79. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/message_utils_test.py +0 -0
  80. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/middleware_test.py +0 -0
  81. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/model_test.py +0 -0
  82. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/blocks/prompt_test.py +0 -0
  83. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/codec_test.py +0 -0
  84. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/action_test.py +0 -0
  85. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/endpoints/reflection_test.py +0 -0
  86. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/environment_test.py +0 -0
  87. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/error_test.py +0 -0
  88. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/extract_test.py +0 -0
  89. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/registry_test.py +0 -0
  90. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/schema_test.py +0 -0
  91. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/core/status_types_test.py +0 -0
  92. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/lang/deprecations_test.py +0 -0
  93. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/veneer/server_test.py +0 -0
  94. {genkit-0.3.0.dev2 → genkit-0.3.2}/tests/genkit/veneer/veneer_test.py +0 -0
  95. {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.0.dev2
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 GoogleGenai
71
+ from genkit.plugins.google_genai import GoogleAI
74
72
 
75
73
  ai = Genkit(
76
- plugins=[GoogleGenai()],
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
- asyncio.run(main())
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 GoogleGenai
22
+ from genkit.plugins.google_genai import GoogleAI
24
23
 
25
24
  ai = Genkit(
26
- plugins=[GoogleGenai()],
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
- asyncio.run(main())
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.0.dev2"
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, Any] | None = None) -> 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() -> Any:
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
- """Base/shared implementation for Genkit user-facing API."""
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 run(self, coro: Coroutine[Any, Any, T]) -> T:
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 reflection
98
- server and runs the user's coroutine concurrently within the same event loop,
99
- blocking until the server is stopped (e.g., via Ctrl+C).
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 `asyncio.run()`.
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 = make_reflection_server(self.registry, spec)
142
+ reflection_server = _make_reflection_server(self.registry, spec)
142
143
 
143
144
  try:
144
145
  async with RuntimeManager(spec):
145
- # We use anyio's task group because it's compatible with
146
+ # We use anyio.TaskGroup because it is compatible with
146
147
  # asyncio's event loop and works with Python 3.10
147
- # (asyncio.create_task_group was added in 3.11, and we can switch
148
- # to that if we drop support for 3.10).
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 anyio.sleep(0.2)
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 TaskGroup is cancelled (e.g. Ctrl+C)
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 cancellation)
170
+ # After the TaskGroup finishes (error or cancelation).
170
171
  if user_task_finished_event.is_set():
171
- logger.debug('User coroutine finished before TaskGroup exit.')
172
+ await logger.adebug('User coroutine finished before TaskGroup exit.')
172
173
  return user_result
173
174
  else:
174
- logger.debug('User coroutine did not finish before TaskGroup exit.')
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 make_reflection_server(registry: GenkitRegistry, spec: ServerSpec) -> uvicorn.Server:
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.runtime_dir = Path(os.getcwd()) / DEFAULT_RUNTIME_DIR_NAME
148
+ self._runtime_dir = Path(os.getcwd()) / DEFAULT_RUNTIME_DIR_NAME
149
149
  else:
150
- self.runtime_dir = Path(runtime_dir)
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.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)
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.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)
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,
@@ -17,7 +17,7 @@
17
17
  """Module containing various core constants."""
18
18
 
19
19
  # The version of Genkit sent over HTTP in the headers.
20
- DEFAULT_GENKIT_VERSION = '0.0.1'
20
+ DEFAULT_GENKIT_VERSION = '0.3.2'
21
21
 
22
22
  # TODO: make this dynamic
23
23
  GENKIT_VERSION = DEFAULT_GENKIT_VERSION
@@ -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)