llama-stack 0.3.5__py3-none-any.whl → 0.4.0__py3-none-any.whl

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 (458) hide show
  1. llama_stack/__init__.py +0 -5
  2. llama_stack/cli/llama.py +3 -3
  3. llama_stack/cli/stack/_list_deps.py +12 -23
  4. llama_stack/cli/stack/list_stacks.py +37 -18
  5. llama_stack/cli/stack/run.py +121 -11
  6. llama_stack/cli/stack/utils.py +0 -127
  7. llama_stack/core/access_control/access_control.py +69 -28
  8. llama_stack/core/access_control/conditions.py +15 -5
  9. llama_stack/core/admin.py +267 -0
  10. llama_stack/core/build.py +6 -74
  11. llama_stack/core/client.py +1 -1
  12. llama_stack/core/configure.py +6 -6
  13. llama_stack/core/conversations/conversations.py +28 -25
  14. llama_stack/core/datatypes.py +271 -79
  15. llama_stack/core/distribution.py +15 -16
  16. llama_stack/core/external.py +3 -3
  17. llama_stack/core/inspect.py +98 -15
  18. llama_stack/core/library_client.py +73 -61
  19. llama_stack/core/prompts/prompts.py +12 -11
  20. llama_stack/core/providers.py +17 -11
  21. llama_stack/core/resolver.py +65 -56
  22. llama_stack/core/routers/__init__.py +8 -12
  23. llama_stack/core/routers/datasets.py +1 -4
  24. llama_stack/core/routers/eval_scoring.py +7 -4
  25. llama_stack/core/routers/inference.py +55 -271
  26. llama_stack/core/routers/safety.py +52 -24
  27. llama_stack/core/routers/tool_runtime.py +6 -48
  28. llama_stack/core/routers/vector_io.py +130 -51
  29. llama_stack/core/routing_tables/benchmarks.py +24 -20
  30. llama_stack/core/routing_tables/common.py +1 -4
  31. llama_stack/core/routing_tables/datasets.py +22 -22
  32. llama_stack/core/routing_tables/models.py +119 -6
  33. llama_stack/core/routing_tables/scoring_functions.py +7 -7
  34. llama_stack/core/routing_tables/shields.py +1 -2
  35. llama_stack/core/routing_tables/toolgroups.py +17 -7
  36. llama_stack/core/routing_tables/vector_stores.py +51 -16
  37. llama_stack/core/server/auth.py +5 -3
  38. llama_stack/core/server/auth_providers.py +36 -20
  39. llama_stack/core/server/fastapi_router_registry.py +84 -0
  40. llama_stack/core/server/quota.py +2 -2
  41. llama_stack/core/server/routes.py +79 -27
  42. llama_stack/core/server/server.py +102 -87
  43. llama_stack/core/stack.py +201 -58
  44. llama_stack/core/storage/datatypes.py +26 -3
  45. llama_stack/{providers/utils → core/storage}/kvstore/__init__.py +2 -0
  46. llama_stack/{providers/utils → core/storage}/kvstore/kvstore.py +55 -24
  47. llama_stack/{providers/utils → core/storage}/kvstore/mongodb/mongodb.py +13 -10
  48. llama_stack/{providers/utils → core/storage}/kvstore/postgres/postgres.py +28 -17
  49. llama_stack/{providers/utils → core/storage}/kvstore/redis/redis.py +41 -16
  50. llama_stack/{providers/utils → core/storage}/kvstore/sqlite/sqlite.py +1 -1
  51. llama_stack/core/storage/sqlstore/__init__.py +17 -0
  52. llama_stack/{providers/utils → core/storage}/sqlstore/authorized_sqlstore.py +69 -49
  53. llama_stack/{providers/utils → core/storage}/sqlstore/sqlalchemy_sqlstore.py +47 -17
  54. llama_stack/{providers/utils → core/storage}/sqlstore/sqlstore.py +25 -8
  55. llama_stack/core/store/registry.py +1 -1
  56. llama_stack/core/utils/config.py +8 -2
  57. llama_stack/core/utils/config_resolution.py +32 -29
  58. llama_stack/core/utils/context.py +4 -10
  59. llama_stack/core/utils/exec.py +9 -0
  60. llama_stack/core/utils/type_inspection.py +45 -0
  61. llama_stack/distributions/dell/{run.yaml → config.yaml} +3 -2
  62. llama_stack/distributions/dell/dell.py +2 -2
  63. llama_stack/distributions/dell/run-with-safety.yaml +3 -2
  64. llama_stack/distributions/meta-reference-gpu/{run.yaml → config.yaml} +3 -2
  65. llama_stack/distributions/meta-reference-gpu/meta_reference.py +2 -2
  66. llama_stack/distributions/meta-reference-gpu/run-with-safety.yaml +3 -2
  67. llama_stack/distributions/nvidia/{run.yaml → config.yaml} +4 -4
  68. llama_stack/distributions/nvidia/nvidia.py +1 -1
  69. llama_stack/distributions/nvidia/run-with-safety.yaml +4 -4
  70. llama_stack/{apis/datasetio → distributions/oci}/__init__.py +1 -1
  71. llama_stack/distributions/oci/config.yaml +134 -0
  72. llama_stack/distributions/oci/oci.py +108 -0
  73. llama_stack/distributions/open-benchmark/{run.yaml → config.yaml} +5 -4
  74. llama_stack/distributions/open-benchmark/open_benchmark.py +2 -3
  75. llama_stack/distributions/postgres-demo/{run.yaml → config.yaml} +4 -3
  76. llama_stack/distributions/starter/{run.yaml → config.yaml} +64 -13
  77. llama_stack/distributions/starter/run-with-postgres-store.yaml +64 -13
  78. llama_stack/distributions/starter/starter.py +8 -5
  79. llama_stack/distributions/starter-gpu/{run.yaml → config.yaml} +64 -13
  80. llama_stack/distributions/starter-gpu/run-with-postgres-store.yaml +64 -13
  81. llama_stack/distributions/template.py +13 -69
  82. llama_stack/distributions/watsonx/{run.yaml → config.yaml} +4 -3
  83. llama_stack/distributions/watsonx/watsonx.py +1 -1
  84. llama_stack/log.py +28 -11
  85. llama_stack/models/llama/checkpoint.py +6 -6
  86. llama_stack/models/llama/hadamard_utils.py +2 -0
  87. llama_stack/models/llama/llama3/generation.py +3 -1
  88. llama_stack/models/llama/llama3/interface.py +2 -5
  89. llama_stack/models/llama/llama3/multimodal/encoder_utils.py +3 -3
  90. llama_stack/models/llama/llama3/multimodal/image_transform.py +6 -6
  91. llama_stack/models/llama/llama3/prompt_templates/system_prompts.py +1 -1
  92. llama_stack/models/llama/llama3/tool_utils.py +2 -1
  93. llama_stack/models/llama/llama4/prompt_templates/system_prompts.py +1 -1
  94. llama_stack/providers/inline/agents/meta_reference/__init__.py +3 -3
  95. llama_stack/providers/inline/agents/meta_reference/agents.py +44 -261
  96. llama_stack/providers/inline/agents/meta_reference/config.py +6 -1
  97. llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py +207 -57
  98. llama_stack/providers/inline/agents/meta_reference/responses/streaming.py +308 -47
  99. llama_stack/providers/inline/agents/meta_reference/responses/tool_executor.py +162 -96
  100. llama_stack/providers/inline/agents/meta_reference/responses/types.py +23 -8
  101. llama_stack/providers/inline/agents/meta_reference/responses/utils.py +201 -33
  102. llama_stack/providers/inline/agents/meta_reference/safety.py +8 -13
  103. llama_stack/providers/inline/batches/reference/__init__.py +2 -4
  104. llama_stack/providers/inline/batches/reference/batches.py +78 -60
  105. llama_stack/providers/inline/datasetio/localfs/datasetio.py +2 -5
  106. llama_stack/providers/inline/eval/meta_reference/eval.py +16 -61
  107. llama_stack/providers/inline/files/localfs/files.py +37 -28
  108. llama_stack/providers/inline/inference/meta_reference/config.py +2 -2
  109. llama_stack/providers/inline/inference/meta_reference/generators.py +50 -60
  110. llama_stack/providers/inline/inference/meta_reference/inference.py +403 -19
  111. llama_stack/providers/inline/inference/meta_reference/model_parallel.py +7 -26
  112. llama_stack/providers/inline/inference/meta_reference/parallel_utils.py +2 -12
  113. llama_stack/providers/inline/inference/sentence_transformers/sentence_transformers.py +10 -15
  114. llama_stack/providers/inline/post_training/common/validator.py +1 -5
  115. llama_stack/providers/inline/post_training/huggingface/post_training.py +8 -8
  116. llama_stack/providers/inline/post_training/huggingface/recipes/finetune_single_device.py +18 -10
  117. llama_stack/providers/inline/post_training/huggingface/recipes/finetune_single_device_dpo.py +12 -9
  118. llama_stack/providers/inline/post_training/huggingface/utils.py +27 -6
  119. llama_stack/providers/inline/post_training/torchtune/common/checkpointer.py +1 -1
  120. llama_stack/providers/inline/post_training/torchtune/common/utils.py +1 -1
  121. llama_stack/providers/inline/post_training/torchtune/datasets/format_adapter.py +1 -1
  122. llama_stack/providers/inline/post_training/torchtune/post_training.py +8 -8
  123. llama_stack/providers/inline/post_training/torchtune/recipes/lora_finetuning_single_device.py +16 -16
  124. llama_stack/providers/inline/safety/code_scanner/code_scanner.py +13 -9
  125. llama_stack/providers/inline/safety/llama_guard/llama_guard.py +18 -15
  126. llama_stack/providers/inline/safety/prompt_guard/prompt_guard.py +9 -9
  127. llama_stack/providers/inline/scoring/basic/scoring.py +6 -13
  128. llama_stack/providers/inline/scoring/basic/scoring_fn/docvqa_scoring_fn.py +1 -2
  129. llama_stack/providers/inline/scoring/basic/scoring_fn/equality_scoring_fn.py +1 -2
  130. llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/docvqa.py +2 -2
  131. llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/equality.py +2 -2
  132. llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/ifeval.py +2 -2
  133. llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/regex_parser_math_response.py +2 -2
  134. llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/regex_parser_multiple_choice_answer.py +2 -2
  135. llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/subset_of.py +2 -2
  136. llama_stack/providers/inline/scoring/basic/scoring_fn/ifeval_scoring_fn.py +1 -2
  137. llama_stack/providers/inline/scoring/basic/scoring_fn/regex_parser_math_response_scoring_fn.py +1 -2
  138. llama_stack/providers/inline/scoring/basic/scoring_fn/regex_parser_scoring_fn.py +1 -2
  139. llama_stack/providers/inline/scoring/basic/scoring_fn/subset_of_scoring_fn.py +1 -2
  140. llama_stack/providers/inline/scoring/braintrust/braintrust.py +12 -15
  141. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/answer_correctness.py +2 -2
  142. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/answer_relevancy.py +2 -2
  143. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/answer_similarity.py +2 -2
  144. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_entity_recall.py +2 -2
  145. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_precision.py +2 -2
  146. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_recall.py +2 -2
  147. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_relevancy.py +2 -2
  148. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/factuality.py +2 -2
  149. llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/faithfulness.py +2 -2
  150. llama_stack/providers/inline/scoring/llm_as_judge/scoring.py +7 -14
  151. llama_stack/providers/inline/scoring/llm_as_judge/scoring_fn/fn_defs/llm_as_judge_405b_simpleqa.py +2 -2
  152. llama_stack/providers/inline/scoring/llm_as_judge/scoring_fn/fn_defs/llm_as_judge_base.py +1 -2
  153. llama_stack/providers/inline/scoring/llm_as_judge/scoring_fn/llm_as_judge_scoring_fn.py +1 -3
  154. llama_stack/providers/inline/tool_runtime/rag/__init__.py +1 -1
  155. llama_stack/providers/inline/tool_runtime/rag/config.py +8 -1
  156. llama_stack/providers/inline/tool_runtime/rag/context_retriever.py +7 -6
  157. llama_stack/providers/inline/tool_runtime/rag/memory.py +64 -48
  158. llama_stack/providers/inline/vector_io/chroma/__init__.py +1 -1
  159. llama_stack/providers/inline/vector_io/chroma/config.py +1 -1
  160. llama_stack/providers/inline/vector_io/faiss/__init__.py +1 -1
  161. llama_stack/providers/inline/vector_io/faiss/config.py +1 -1
  162. llama_stack/providers/inline/vector_io/faiss/faiss.py +43 -28
  163. llama_stack/providers/inline/vector_io/milvus/__init__.py +1 -1
  164. llama_stack/providers/inline/vector_io/milvus/config.py +1 -1
  165. llama_stack/providers/inline/vector_io/qdrant/__init__.py +1 -1
  166. llama_stack/providers/inline/vector_io/qdrant/config.py +1 -1
  167. llama_stack/providers/inline/vector_io/sqlite_vec/__init__.py +1 -1
  168. llama_stack/providers/inline/vector_io/sqlite_vec/sqlite_vec.py +40 -33
  169. llama_stack/providers/registry/agents.py +7 -3
  170. llama_stack/providers/registry/batches.py +1 -1
  171. llama_stack/providers/registry/datasetio.py +1 -1
  172. llama_stack/providers/registry/eval.py +1 -1
  173. llama_stack/{apis/datasets/__init__.py → providers/registry/file_processors.py} +5 -1
  174. llama_stack/providers/registry/files.py +11 -2
  175. llama_stack/providers/registry/inference.py +22 -3
  176. llama_stack/providers/registry/post_training.py +1 -1
  177. llama_stack/providers/registry/safety.py +1 -1
  178. llama_stack/providers/registry/scoring.py +1 -1
  179. llama_stack/providers/registry/tool_runtime.py +2 -2
  180. llama_stack/providers/registry/vector_io.py +7 -7
  181. llama_stack/providers/remote/datasetio/huggingface/huggingface.py +2 -5
  182. llama_stack/providers/remote/datasetio/nvidia/datasetio.py +1 -4
  183. llama_stack/providers/remote/eval/nvidia/eval.py +15 -9
  184. llama_stack/providers/remote/files/openai/__init__.py +19 -0
  185. llama_stack/providers/remote/files/openai/config.py +28 -0
  186. llama_stack/providers/remote/files/openai/files.py +253 -0
  187. llama_stack/providers/remote/files/s3/files.py +52 -30
  188. llama_stack/providers/remote/inference/anthropic/anthropic.py +2 -1
  189. llama_stack/providers/remote/inference/anthropic/config.py +1 -1
  190. llama_stack/providers/remote/inference/azure/azure.py +1 -3
  191. llama_stack/providers/remote/inference/azure/config.py +8 -7
  192. llama_stack/providers/remote/inference/bedrock/__init__.py +1 -1
  193. llama_stack/providers/remote/inference/bedrock/bedrock.py +82 -105
  194. llama_stack/providers/remote/inference/bedrock/config.py +24 -3
  195. llama_stack/providers/remote/inference/cerebras/cerebras.py +5 -5
  196. llama_stack/providers/remote/inference/cerebras/config.py +12 -5
  197. llama_stack/providers/remote/inference/databricks/config.py +13 -6
  198. llama_stack/providers/remote/inference/databricks/databricks.py +16 -6
  199. llama_stack/providers/remote/inference/fireworks/config.py +5 -5
  200. llama_stack/providers/remote/inference/fireworks/fireworks.py +1 -1
  201. llama_stack/providers/remote/inference/gemini/config.py +1 -1
  202. llama_stack/providers/remote/inference/gemini/gemini.py +13 -14
  203. llama_stack/providers/remote/inference/groq/config.py +5 -5
  204. llama_stack/providers/remote/inference/groq/groq.py +1 -1
  205. llama_stack/providers/remote/inference/llama_openai_compat/config.py +5 -5
  206. llama_stack/providers/remote/inference/llama_openai_compat/llama.py +8 -6
  207. llama_stack/providers/remote/inference/nvidia/__init__.py +1 -1
  208. llama_stack/providers/remote/inference/nvidia/config.py +21 -11
  209. llama_stack/providers/remote/inference/nvidia/nvidia.py +115 -3
  210. llama_stack/providers/remote/inference/nvidia/utils.py +1 -1
  211. llama_stack/providers/remote/inference/oci/__init__.py +17 -0
  212. llama_stack/providers/remote/inference/oci/auth.py +79 -0
  213. llama_stack/providers/remote/inference/oci/config.py +75 -0
  214. llama_stack/providers/remote/inference/oci/oci.py +162 -0
  215. llama_stack/providers/remote/inference/ollama/config.py +7 -5
  216. llama_stack/providers/remote/inference/ollama/ollama.py +17 -8
  217. llama_stack/providers/remote/inference/openai/config.py +4 -4
  218. llama_stack/providers/remote/inference/openai/openai.py +1 -1
  219. llama_stack/providers/remote/inference/passthrough/__init__.py +2 -2
  220. llama_stack/providers/remote/inference/passthrough/config.py +5 -10
  221. llama_stack/providers/remote/inference/passthrough/passthrough.py +97 -75
  222. llama_stack/providers/remote/inference/runpod/config.py +12 -5
  223. llama_stack/providers/remote/inference/runpod/runpod.py +2 -20
  224. llama_stack/providers/remote/inference/sambanova/config.py +5 -5
  225. llama_stack/providers/remote/inference/sambanova/sambanova.py +1 -1
  226. llama_stack/providers/remote/inference/tgi/config.py +7 -6
  227. llama_stack/providers/remote/inference/tgi/tgi.py +19 -11
  228. llama_stack/providers/remote/inference/together/config.py +5 -5
  229. llama_stack/providers/remote/inference/together/together.py +15 -12
  230. llama_stack/providers/remote/inference/vertexai/config.py +1 -1
  231. llama_stack/providers/remote/inference/vllm/config.py +5 -5
  232. llama_stack/providers/remote/inference/vllm/vllm.py +13 -14
  233. llama_stack/providers/remote/inference/watsonx/config.py +4 -4
  234. llama_stack/providers/remote/inference/watsonx/watsonx.py +21 -94
  235. llama_stack/providers/remote/post_training/nvidia/post_training.py +4 -4
  236. llama_stack/providers/remote/post_training/nvidia/utils.py +1 -1
  237. llama_stack/providers/remote/safety/bedrock/bedrock.py +6 -6
  238. llama_stack/providers/remote/safety/bedrock/config.py +1 -1
  239. llama_stack/providers/remote/safety/nvidia/config.py +1 -1
  240. llama_stack/providers/remote/safety/nvidia/nvidia.py +11 -5
  241. llama_stack/providers/remote/safety/sambanova/config.py +1 -1
  242. llama_stack/providers/remote/safety/sambanova/sambanova.py +6 -6
  243. llama_stack/providers/remote/tool_runtime/bing_search/bing_search.py +11 -6
  244. llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py +12 -7
  245. llama_stack/providers/remote/tool_runtime/model_context_protocol/config.py +8 -2
  246. llama_stack/providers/remote/tool_runtime/model_context_protocol/model_context_protocol.py +57 -15
  247. llama_stack/providers/remote/tool_runtime/tavily_search/tavily_search.py +11 -6
  248. llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py +11 -6
  249. llama_stack/providers/remote/vector_io/chroma/__init__.py +1 -1
  250. llama_stack/providers/remote/vector_io/chroma/chroma.py +125 -20
  251. llama_stack/providers/remote/vector_io/chroma/config.py +1 -1
  252. llama_stack/providers/remote/vector_io/milvus/__init__.py +1 -1
  253. llama_stack/providers/remote/vector_io/milvus/config.py +1 -1
  254. llama_stack/providers/remote/vector_io/milvus/milvus.py +27 -21
  255. llama_stack/providers/remote/vector_io/pgvector/__init__.py +1 -1
  256. llama_stack/providers/remote/vector_io/pgvector/config.py +1 -1
  257. llama_stack/providers/remote/vector_io/pgvector/pgvector.py +26 -18
  258. llama_stack/providers/remote/vector_io/qdrant/__init__.py +1 -1
  259. llama_stack/providers/remote/vector_io/qdrant/config.py +1 -1
  260. llama_stack/providers/remote/vector_io/qdrant/qdrant.py +141 -24
  261. llama_stack/providers/remote/vector_io/weaviate/__init__.py +1 -1
  262. llama_stack/providers/remote/vector_io/weaviate/config.py +1 -1
  263. llama_stack/providers/remote/vector_io/weaviate/weaviate.py +26 -21
  264. llama_stack/providers/utils/common/data_schema_validator.py +1 -5
  265. llama_stack/providers/utils/files/form_data.py +1 -1
  266. llama_stack/providers/utils/inference/embedding_mixin.py +1 -1
  267. llama_stack/providers/utils/inference/inference_store.py +7 -8
  268. llama_stack/providers/utils/inference/litellm_openai_mixin.py +79 -79
  269. llama_stack/providers/utils/inference/model_registry.py +1 -3
  270. llama_stack/providers/utils/inference/openai_compat.py +44 -1171
  271. llama_stack/providers/utils/inference/openai_mixin.py +68 -42
  272. llama_stack/providers/utils/inference/prompt_adapter.py +50 -265
  273. llama_stack/providers/utils/inference/stream_utils.py +23 -0
  274. llama_stack/providers/utils/memory/__init__.py +2 -0
  275. llama_stack/providers/utils/memory/file_utils.py +1 -1
  276. llama_stack/providers/utils/memory/openai_vector_store_mixin.py +181 -84
  277. llama_stack/providers/utils/memory/vector_store.py +39 -38
  278. llama_stack/providers/utils/pagination.py +1 -1
  279. llama_stack/providers/utils/responses/responses_store.py +15 -25
  280. llama_stack/providers/utils/scoring/aggregation_utils.py +1 -2
  281. llama_stack/providers/utils/scoring/base_scoring_fn.py +1 -2
  282. llama_stack/providers/utils/tools/mcp.py +93 -11
  283. llama_stack/telemetry/constants.py +27 -0
  284. llama_stack/telemetry/helpers.py +43 -0
  285. llama_stack/testing/api_recorder.py +25 -16
  286. {llama_stack-0.3.5.dist-info → llama_stack-0.4.0.dist-info}/METADATA +56 -54
  287. llama_stack-0.4.0.dist-info/RECORD +588 -0
  288. llama_stack-0.4.0.dist-info/top_level.txt +2 -0
  289. llama_stack_api/__init__.py +945 -0
  290. llama_stack_api/admin/__init__.py +45 -0
  291. llama_stack_api/admin/api.py +72 -0
  292. llama_stack_api/admin/fastapi_routes.py +117 -0
  293. llama_stack_api/admin/models.py +113 -0
  294. llama_stack_api/agents.py +173 -0
  295. llama_stack_api/batches/__init__.py +40 -0
  296. llama_stack_api/batches/api.py +53 -0
  297. llama_stack_api/batches/fastapi_routes.py +113 -0
  298. llama_stack_api/batches/models.py +78 -0
  299. llama_stack_api/benchmarks/__init__.py +43 -0
  300. llama_stack_api/benchmarks/api.py +39 -0
  301. llama_stack_api/benchmarks/fastapi_routes.py +109 -0
  302. llama_stack_api/benchmarks/models.py +109 -0
  303. {llama_stack/apis → llama_stack_api}/common/content_types.py +1 -43
  304. {llama_stack/apis → llama_stack_api}/common/errors.py +0 -8
  305. {llama_stack/apis → llama_stack_api}/common/job_types.py +1 -1
  306. llama_stack_api/common/responses.py +77 -0
  307. {llama_stack/apis → llama_stack_api}/common/training_types.py +1 -1
  308. {llama_stack/apis → llama_stack_api}/common/type_system.py +2 -14
  309. llama_stack_api/connectors.py +146 -0
  310. {llama_stack/apis/conversations → llama_stack_api}/conversations.py +23 -39
  311. {llama_stack/apis/datasetio → llama_stack_api}/datasetio.py +4 -8
  312. llama_stack_api/datasets/__init__.py +61 -0
  313. llama_stack_api/datasets/api.py +35 -0
  314. llama_stack_api/datasets/fastapi_routes.py +104 -0
  315. llama_stack_api/datasets/models.py +152 -0
  316. {llama_stack/providers → llama_stack_api}/datatypes.py +166 -10
  317. {llama_stack/apis/eval → llama_stack_api}/eval.py +8 -40
  318. llama_stack_api/file_processors/__init__.py +27 -0
  319. llama_stack_api/file_processors/api.py +64 -0
  320. llama_stack_api/file_processors/fastapi_routes.py +78 -0
  321. llama_stack_api/file_processors/models.py +42 -0
  322. llama_stack_api/files/__init__.py +35 -0
  323. llama_stack_api/files/api.py +51 -0
  324. llama_stack_api/files/fastapi_routes.py +124 -0
  325. llama_stack_api/files/models.py +107 -0
  326. {llama_stack/apis/inference → llama_stack_api}/inference.py +90 -194
  327. llama_stack_api/inspect_api/__init__.py +37 -0
  328. llama_stack_api/inspect_api/api.py +25 -0
  329. llama_stack_api/inspect_api/fastapi_routes.py +76 -0
  330. llama_stack_api/inspect_api/models.py +28 -0
  331. {llama_stack/apis/agents → llama_stack_api/internal}/__init__.py +3 -1
  332. llama_stack/providers/utils/kvstore/api.py → llama_stack_api/internal/kvstore.py +5 -0
  333. llama_stack_api/internal/sqlstore.py +79 -0
  334. {llama_stack/apis/models → llama_stack_api}/models.py +11 -9
  335. {llama_stack/apis/agents → llama_stack_api}/openai_responses.py +184 -27
  336. {llama_stack/apis/post_training → llama_stack_api}/post_training.py +7 -11
  337. {llama_stack/apis/prompts → llama_stack_api}/prompts.py +3 -4
  338. llama_stack_api/providers/__init__.py +33 -0
  339. llama_stack_api/providers/api.py +16 -0
  340. llama_stack_api/providers/fastapi_routes.py +57 -0
  341. llama_stack_api/providers/models.py +24 -0
  342. {llama_stack/apis/tools → llama_stack_api}/rag_tool.py +2 -52
  343. {llama_stack/apis → llama_stack_api}/resource.py +1 -1
  344. llama_stack_api/router_utils.py +160 -0
  345. {llama_stack/apis/safety → llama_stack_api}/safety.py +6 -9
  346. {llama_stack → llama_stack_api}/schema_utils.py +94 -4
  347. {llama_stack/apis/scoring → llama_stack_api}/scoring.py +3 -3
  348. {llama_stack/apis/scoring_functions → llama_stack_api}/scoring_functions.py +9 -6
  349. {llama_stack/apis/shields → llama_stack_api}/shields.py +6 -7
  350. {llama_stack/apis/tools → llama_stack_api}/tools.py +26 -21
  351. {llama_stack/apis/vector_io → llama_stack_api}/vector_io.py +133 -152
  352. {llama_stack/apis/vector_stores → llama_stack_api}/vector_stores.py +1 -1
  353. llama_stack/apis/agents/agents.py +0 -894
  354. llama_stack/apis/batches/__init__.py +0 -9
  355. llama_stack/apis/batches/batches.py +0 -100
  356. llama_stack/apis/benchmarks/__init__.py +0 -7
  357. llama_stack/apis/benchmarks/benchmarks.py +0 -108
  358. llama_stack/apis/common/responses.py +0 -36
  359. llama_stack/apis/conversations/__init__.py +0 -31
  360. llama_stack/apis/datasets/datasets.py +0 -251
  361. llama_stack/apis/datatypes.py +0 -160
  362. llama_stack/apis/eval/__init__.py +0 -7
  363. llama_stack/apis/files/__init__.py +0 -7
  364. llama_stack/apis/files/files.py +0 -199
  365. llama_stack/apis/inference/__init__.py +0 -7
  366. llama_stack/apis/inference/event_logger.py +0 -43
  367. llama_stack/apis/inspect/__init__.py +0 -7
  368. llama_stack/apis/inspect/inspect.py +0 -94
  369. llama_stack/apis/models/__init__.py +0 -7
  370. llama_stack/apis/post_training/__init__.py +0 -7
  371. llama_stack/apis/prompts/__init__.py +0 -9
  372. llama_stack/apis/providers/__init__.py +0 -7
  373. llama_stack/apis/providers/providers.py +0 -69
  374. llama_stack/apis/safety/__init__.py +0 -7
  375. llama_stack/apis/scoring/__init__.py +0 -7
  376. llama_stack/apis/scoring_functions/__init__.py +0 -7
  377. llama_stack/apis/shields/__init__.py +0 -7
  378. llama_stack/apis/synthetic_data_generation/__init__.py +0 -7
  379. llama_stack/apis/synthetic_data_generation/synthetic_data_generation.py +0 -77
  380. llama_stack/apis/telemetry/__init__.py +0 -7
  381. llama_stack/apis/telemetry/telemetry.py +0 -423
  382. llama_stack/apis/tools/__init__.py +0 -8
  383. llama_stack/apis/vector_io/__init__.py +0 -7
  384. llama_stack/apis/vector_stores/__init__.py +0 -7
  385. llama_stack/core/server/tracing.py +0 -80
  386. llama_stack/core/ui/app.py +0 -55
  387. llama_stack/core/ui/modules/__init__.py +0 -5
  388. llama_stack/core/ui/modules/api.py +0 -32
  389. llama_stack/core/ui/modules/utils.py +0 -42
  390. llama_stack/core/ui/page/__init__.py +0 -5
  391. llama_stack/core/ui/page/distribution/__init__.py +0 -5
  392. llama_stack/core/ui/page/distribution/datasets.py +0 -18
  393. llama_stack/core/ui/page/distribution/eval_tasks.py +0 -20
  394. llama_stack/core/ui/page/distribution/models.py +0 -18
  395. llama_stack/core/ui/page/distribution/providers.py +0 -27
  396. llama_stack/core/ui/page/distribution/resources.py +0 -48
  397. llama_stack/core/ui/page/distribution/scoring_functions.py +0 -18
  398. llama_stack/core/ui/page/distribution/shields.py +0 -19
  399. llama_stack/core/ui/page/evaluations/__init__.py +0 -5
  400. llama_stack/core/ui/page/evaluations/app_eval.py +0 -143
  401. llama_stack/core/ui/page/evaluations/native_eval.py +0 -253
  402. llama_stack/core/ui/page/playground/__init__.py +0 -5
  403. llama_stack/core/ui/page/playground/chat.py +0 -130
  404. llama_stack/core/ui/page/playground/tools.py +0 -352
  405. llama_stack/distributions/dell/build.yaml +0 -33
  406. llama_stack/distributions/meta-reference-gpu/build.yaml +0 -32
  407. llama_stack/distributions/nvidia/build.yaml +0 -29
  408. llama_stack/distributions/open-benchmark/build.yaml +0 -36
  409. llama_stack/distributions/postgres-demo/__init__.py +0 -7
  410. llama_stack/distributions/postgres-demo/build.yaml +0 -23
  411. llama_stack/distributions/postgres-demo/postgres_demo.py +0 -125
  412. llama_stack/distributions/starter/build.yaml +0 -61
  413. llama_stack/distributions/starter-gpu/build.yaml +0 -61
  414. llama_stack/distributions/watsonx/build.yaml +0 -33
  415. llama_stack/providers/inline/agents/meta_reference/agent_instance.py +0 -1024
  416. llama_stack/providers/inline/agents/meta_reference/persistence.py +0 -228
  417. llama_stack/providers/inline/telemetry/__init__.py +0 -5
  418. llama_stack/providers/inline/telemetry/meta_reference/__init__.py +0 -21
  419. llama_stack/providers/inline/telemetry/meta_reference/config.py +0 -47
  420. llama_stack/providers/inline/telemetry/meta_reference/telemetry.py +0 -252
  421. llama_stack/providers/remote/inference/bedrock/models.py +0 -29
  422. llama_stack/providers/utils/kvstore/sqlite/config.py +0 -20
  423. llama_stack/providers/utils/sqlstore/__init__.py +0 -5
  424. llama_stack/providers/utils/sqlstore/api.py +0 -128
  425. llama_stack/providers/utils/telemetry/__init__.py +0 -5
  426. llama_stack/providers/utils/telemetry/trace_protocol.py +0 -142
  427. llama_stack/providers/utils/telemetry/tracing.py +0 -384
  428. llama_stack/strong_typing/__init__.py +0 -19
  429. llama_stack/strong_typing/auxiliary.py +0 -228
  430. llama_stack/strong_typing/classdef.py +0 -440
  431. llama_stack/strong_typing/core.py +0 -46
  432. llama_stack/strong_typing/deserializer.py +0 -877
  433. llama_stack/strong_typing/docstring.py +0 -409
  434. llama_stack/strong_typing/exception.py +0 -23
  435. llama_stack/strong_typing/inspection.py +0 -1085
  436. llama_stack/strong_typing/mapping.py +0 -40
  437. llama_stack/strong_typing/name.py +0 -182
  438. llama_stack/strong_typing/schema.py +0 -792
  439. llama_stack/strong_typing/serialization.py +0 -97
  440. llama_stack/strong_typing/serializer.py +0 -500
  441. llama_stack/strong_typing/slots.py +0 -27
  442. llama_stack/strong_typing/topological.py +0 -89
  443. llama_stack/ui/node_modules/flatted/python/flatted.py +0 -149
  444. llama_stack-0.3.5.dist-info/RECORD +0 -625
  445. llama_stack-0.3.5.dist-info/top_level.txt +0 -1
  446. /llama_stack/{providers/utils → core/storage}/kvstore/config.py +0 -0
  447. /llama_stack/{providers/utils → core/storage}/kvstore/mongodb/__init__.py +0 -0
  448. /llama_stack/{providers/utils → core/storage}/kvstore/postgres/__init__.py +0 -0
  449. /llama_stack/{providers/utils → core/storage}/kvstore/redis/__init__.py +0 -0
  450. /llama_stack/{providers/utils → core/storage}/kvstore/sqlite/__init__.py +0 -0
  451. /llama_stack/{apis → providers/inline/file_processor}/__init__.py +0 -0
  452. /llama_stack/{apis/common → telemetry}/__init__.py +0 -0
  453. {llama_stack-0.3.5.dist-info → llama_stack-0.4.0.dist-info}/WHEEL +0 -0
  454. {llama_stack-0.3.5.dist-info → llama_stack-0.4.0.dist-info}/entry_points.txt +0 -0
  455. {llama_stack-0.3.5.dist-info → llama_stack-0.4.0.dist-info}/licenses/LICENSE +0 -0
  456. {llama_stack/core/ui → llama_stack_api/common}/__init__.py +0 -0
  457. {llama_stack/strong_typing → llama_stack_api}/py.typed +0 -0
  458. {llama_stack/apis → llama_stack_api}/version.py +0 -0
@@ -6,12 +6,13 @@
6
6
 
7
7
  from datetime import datetime
8
8
 
9
- import psycopg2
10
- from psycopg2.extras import DictCursor
9
+ import psycopg2 # type: ignore[import-not-found]
10
+ from psycopg2.extensions import connection as PGConnection # type: ignore[import-not-found]
11
+ from psycopg2.extras import DictCursor # type: ignore[import-not-found]
11
12
 
12
13
  from llama_stack.log import get_logger
14
+ from llama_stack_api.internal.kvstore import KVStore
13
15
 
14
- from ..api import KVStore
15
16
  from ..config import PostgresKVStoreConfig
16
17
 
17
18
  log = get_logger(name=__name__, category="providers::utils")
@@ -20,12 +21,12 @@ log = get_logger(name=__name__, category="providers::utils")
20
21
  class PostgresKVStoreImpl(KVStore):
21
22
  def __init__(self, config: PostgresKVStoreConfig):
22
23
  self.config = config
23
- self.conn = None
24
- self.cursor = None
24
+ self._conn: PGConnection | None = None
25
+ self._cursor: DictCursor | None = None
25
26
 
26
27
  async def initialize(self) -> None:
27
28
  try:
28
- self.conn = psycopg2.connect(
29
+ self._conn = psycopg2.connect(
29
30
  host=self.config.host,
30
31
  port=self.config.port,
31
32
  database=self.config.db,
@@ -34,11 +35,11 @@ class PostgresKVStoreImpl(KVStore):
34
35
  sslmode=self.config.ssl_mode,
35
36
  sslrootcert=self.config.ca_cert_path,
36
37
  )
37
- self.conn.autocommit = True
38
- self.cursor = self.conn.cursor(cursor_factory=DictCursor)
38
+ self._conn.autocommit = True
39
+ self._cursor = self._conn.cursor(cursor_factory=DictCursor)
39
40
 
40
41
  # Create table if it doesn't exist
41
- self.cursor.execute(
42
+ self._cursor.execute(
42
43
  f"""
43
44
  CREATE TABLE IF NOT EXISTS {self.config.table_name} (
44
45
  key TEXT PRIMARY KEY,
@@ -51,6 +52,11 @@ class PostgresKVStoreImpl(KVStore):
51
52
  log.exception("Could not connect to PostgreSQL database server")
52
53
  raise RuntimeError("Could not connect to PostgreSQL database server") from e
53
54
 
55
+ def _cursor_or_raise(self) -> DictCursor:
56
+ if self._cursor is None:
57
+ raise RuntimeError("Postgres client not initialized")
58
+ return self._cursor
59
+
54
60
  def _namespaced_key(self, key: str) -> str:
55
61
  if not self.config.namespace:
56
62
  return key
@@ -58,7 +64,8 @@ class PostgresKVStoreImpl(KVStore):
58
64
 
59
65
  async def set(self, key: str, value: str, expiration: datetime | None = None) -> None:
60
66
  key = self._namespaced_key(key)
61
- self.cursor.execute(
67
+ cursor = self._cursor_or_raise()
68
+ cursor.execute(
62
69
  f"""
63
70
  INSERT INTO {self.config.table_name} (key, value, expiration)
64
71
  VALUES (%s, %s, %s)
@@ -70,7 +77,8 @@ class PostgresKVStoreImpl(KVStore):
70
77
 
71
78
  async def get(self, key: str) -> str | None:
72
79
  key = self._namespaced_key(key)
73
- self.cursor.execute(
80
+ cursor = self._cursor_or_raise()
81
+ cursor.execute(
74
82
  f"""
75
83
  SELECT value FROM {self.config.table_name}
76
84
  WHERE key = %s
@@ -78,12 +86,13 @@ class PostgresKVStoreImpl(KVStore):
78
86
  """,
79
87
  (key,),
80
88
  )
81
- result = self.cursor.fetchone()
89
+ result = cursor.fetchone()
82
90
  return result[0] if result else None
83
91
 
84
92
  async def delete(self, key: str) -> None:
85
93
  key = self._namespaced_key(key)
86
- self.cursor.execute(
94
+ cursor = self._cursor_or_raise()
95
+ cursor.execute(
87
96
  f"DELETE FROM {self.config.table_name} WHERE key = %s",
88
97
  (key,),
89
98
  )
@@ -92,7 +101,8 @@ class PostgresKVStoreImpl(KVStore):
92
101
  start_key = self._namespaced_key(start_key)
93
102
  end_key = self._namespaced_key(end_key)
94
103
 
95
- self.cursor.execute(
104
+ cursor = self._cursor_or_raise()
105
+ cursor.execute(
96
106
  f"""
97
107
  SELECT value FROM {self.config.table_name}
98
108
  WHERE key >= %s AND key < %s
@@ -101,14 +111,15 @@ class PostgresKVStoreImpl(KVStore):
101
111
  """,
102
112
  (start_key, end_key),
103
113
  )
104
- return [row[0] for row in self.cursor.fetchall()]
114
+ return [row[0] for row in cursor.fetchall()]
105
115
 
106
116
  async def keys_in_range(self, start_key: str, end_key: str) -> list[str]:
107
117
  start_key = self._namespaced_key(start_key)
108
118
  end_key = self._namespaced_key(end_key)
109
119
 
110
- self.cursor.execute(
120
+ cursor = self._cursor_or_raise()
121
+ cursor.execute(
111
122
  f"SELECT key FROM {self.config.table_name} WHERE key >= %s AND key < %s",
112
123
  (start_key, end_key),
113
124
  )
114
- return [row[0] for row in self.cursor.fetchall()]
125
+ return [row[0] for row in cursor.fetchall()]
@@ -6,18 +6,25 @@
6
6
 
7
7
  from datetime import datetime
8
8
 
9
- from redis.asyncio import Redis
9
+ from redis.asyncio import Redis # type: ignore[import-not-found]
10
+
11
+ from llama_stack_api.internal.kvstore import KVStore
10
12
 
11
- from ..api import KVStore
12
13
  from ..config import RedisKVStoreConfig
13
14
 
14
15
 
15
16
  class RedisKVStoreImpl(KVStore):
16
17
  def __init__(self, config: RedisKVStoreConfig):
17
18
  self.config = config
19
+ self._redis: Redis | None = None
18
20
 
19
21
  async def initialize(self) -> None:
20
- self.redis = Redis.from_url(self.config.url)
22
+ self._redis = Redis.from_url(self.config.url)
23
+
24
+ def _client(self) -> Redis:
25
+ if self._redis is None:
26
+ raise RuntimeError("Redis client not initialized")
27
+ return self._redis
21
28
 
22
29
  def _namespaced_key(self, key: str) -> str:
23
30
  if not self.config.namespace:
@@ -26,30 +33,37 @@ class RedisKVStoreImpl(KVStore):
26
33
 
27
34
  async def set(self, key: str, value: str, expiration: datetime | None = None) -> None:
28
35
  key = self._namespaced_key(key)
29
- await self.redis.set(key, value)
36
+ client = self._client()
37
+ await client.set(key, value)
30
38
  if expiration:
31
- await self.redis.expireat(key, expiration)
39
+ await client.expireat(key, expiration)
32
40
 
33
41
  async def get(self, key: str) -> str | None:
34
42
  key = self._namespaced_key(key)
35
- value = await self.redis.get(key)
43
+ client = self._client()
44
+ value = await client.get(key)
36
45
  if value is None:
37
46
  return None
38
- await self.redis.ttl(key)
39
- return value
47
+ await client.ttl(key)
48
+ if isinstance(value, bytes):
49
+ return value.decode("utf-8")
50
+ if isinstance(value, str):
51
+ return value
52
+ return str(value)
40
53
 
41
54
  async def delete(self, key: str) -> None:
42
55
  key = self._namespaced_key(key)
43
- await self.redis.delete(key)
56
+ await self._client().delete(key)
44
57
 
45
58
  async def values_in_range(self, start_key: str, end_key: str) -> list[str]:
46
59
  start_key = self._namespaced_key(start_key)
47
60
  end_key = self._namespaced_key(end_key)
61
+ client = self._client()
48
62
  cursor = 0
49
63
  pattern = start_key + "*" # Match all keys starting with start_key prefix
50
- matching_keys = []
64
+ matching_keys: list[str | bytes] = []
51
65
  while True:
52
- cursor, keys = await self.redis.scan(cursor, match=pattern, count=1000)
66
+ cursor, keys = await client.scan(cursor, match=pattern, count=1000)
53
67
 
54
68
  for key in keys:
55
69
  key_str = key.decode("utf-8") if isinstance(key, bytes) else key
@@ -61,7 +75,7 @@ class RedisKVStoreImpl(KVStore):
61
75
 
62
76
  # Then fetch all values in a single MGET call
63
77
  if matching_keys:
64
- values = await self.redis.mget(matching_keys)
78
+ values = await client.mget(matching_keys)
65
79
  return [
66
80
  value.decode("utf-8") if isinstance(value, bytes) else value for value in values if value is not None
67
81
  ]
@@ -70,7 +84,18 @@ class RedisKVStoreImpl(KVStore):
70
84
 
71
85
  async def keys_in_range(self, start_key: str, end_key: str) -> list[str]:
72
86
  """Get all keys in the given range."""
73
- matching_keys = await self.redis.zrangebylex(self.namespace, f"[{start_key}", f"[{end_key}")
74
- if not matching_keys:
75
- return []
76
- return [k.decode("utf-8") for k in matching_keys]
87
+ start_key = self._namespaced_key(start_key)
88
+ end_key = self._namespaced_key(end_key)
89
+ client = self._client()
90
+ cursor = 0
91
+ pattern = start_key + "*"
92
+ result: list[str] = []
93
+ while True:
94
+ cursor, keys = await client.scan(cursor, match=pattern, count=1000)
95
+ for key in keys:
96
+ key_str = key.decode("utf-8") if isinstance(key, bytes) else str(key)
97
+ if start_key <= key_str <= end_key:
98
+ result.append(key_str)
99
+ if cursor == 0:
100
+ break
101
+ return result
@@ -10,8 +10,8 @@ from datetime import datetime
10
10
  import aiosqlite
11
11
 
12
12
  from llama_stack.log import get_logger
13
+ from llama_stack_api.internal.kvstore import KVStore
13
14
 
14
- from ..api import KVStore
15
15
  from ..config import SqliteKVStoreConfig
16
16
 
17
17
  logger = get_logger(name=__name__, category="providers::utils")
@@ -0,0 +1,17 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the terms described in the LICENSE file in
5
+ # the root directory of this source tree.
6
+
7
+ from llama_stack_api.internal.sqlstore import (
8
+ ColumnDefinition as ColumnDefinition,
9
+ )
10
+ from llama_stack_api.internal.sqlstore import (
11
+ ColumnType as ColumnType,
12
+ )
13
+ from llama_stack_api.internal.sqlstore import (
14
+ SqlStore as SqlStore,
15
+ )
16
+
17
+ from .sqlstore import * # noqa: F401,F403
@@ -14,8 +14,8 @@ from llama_stack.core.datatypes import User
14
14
  from llama_stack.core.request_headers import get_authenticated_user
15
15
  from llama_stack.core.storage.datatypes import StorageBackendType
16
16
  from llama_stack.log import get_logger
17
-
18
- from .api import ColumnDefinition, ColumnType, PaginatedResponse, SqlStore
17
+ from llama_stack_api import PaginatedResponse
18
+ from llama_stack_api.internal.sqlstore import ColumnDefinition, ColumnType, SqlStore
19
19
 
20
20
  logger = get_logger(name=__name__, category="providers::utils")
21
21
 
@@ -23,17 +23,26 @@ logger = get_logger(name=__name__, category="providers::utils")
23
23
  # WARNING: If default_policy() changes, this constant must be updated accordingly
24
24
  # or SQL filtering will fall back to conservative mode (safe but less performant)
25
25
  #
26
- # This policy represents: "Permit all actions when user is in owners list for ALL attribute categories"
26
+ # This policy represents: "Permit all actions when user is in owners list for ANY attribute category"
27
27
  # The corresponding SQL logic is implemented in _build_default_policy_where_clause():
28
28
  # - Public records (no access_attributes) are always accessible
29
- # - Records with access_attributes require user to match ALL categories that exist in the resource
30
- # - Missing categories in the resource are treated as "no restriction" (allow)
29
+ # - Records with access_attributes require user to match ANY category that exists in the resource
31
30
  # - Within each category, user needs ANY matching value (OR logic)
32
- # - Between categories, user needs ALL categories to match (AND logic)
31
+ # - Between categories, user needs ANY category to match (OR logic)
33
32
  SQL_OPTIMIZED_POLICY = [
34
33
  AccessRule(
35
34
  permit=Scope(actions=list(Action)),
36
- when=["user in owners roles", "user in owners teams", "user in owners projects", "user in owners namespaces"],
35
+ when=["user in owners " + name],
36
+ )
37
+ for name in ["roles", "teams", "projects", "namespaces"]
38
+ ] + [
39
+ AccessRule(
40
+ permit=Scope(actions=list(Action)),
41
+ when=["user is owner"],
42
+ ),
43
+ AccessRule(
44
+ permit=Scope(actions=list(Action)),
45
+ when=["resource is unowned"],
37
46
  ),
38
47
  ]
39
48
 
@@ -56,7 +65,7 @@ def _enhance_item_with_access_control(item: Mapping[str, Any], current_user: Use
56
65
 
57
66
 
58
67
  class SqlRecord(ProtectedResource):
59
- def __init__(self, record_id: str, table_name: str, owner: User):
68
+ def __init__(self, record_id: str, table_name: str, owner: User | None):
60
69
  self.type = f"sql_record::{table_name}"
61
70
  self.identifier = record_id
62
71
  self.owner = owner
@@ -129,6 +138,23 @@ class AuthorizedSqlStore:
129
138
  enhanced_data = [_enhance_item_with_access_control(item, current_user) for item in data]
130
139
  await self.sql_store.insert(table, enhanced_data)
131
140
 
141
+ async def upsert(
142
+ self,
143
+ table: str,
144
+ data: Mapping[str, Any],
145
+ conflict_columns: list[str],
146
+ update_columns: list[str] | None = None,
147
+ ) -> None:
148
+ """Upsert a row with automatic access control attribute capture."""
149
+ current_user = get_authenticated_user()
150
+ enhanced_data = _enhance_item_with_access_control(data, current_user)
151
+ await self.sql_store.upsert(
152
+ table=table,
153
+ data=enhanced_data,
154
+ conflict_columns=conflict_columns,
155
+ update_columns=update_columns,
156
+ )
157
+
132
158
  async def fetch_all(
133
159
  self,
134
160
  table: str,
@@ -136,6 +162,7 @@ class AuthorizedSqlStore:
136
162
  limit: int | None = None,
137
163
  order_by: list[tuple[str, Literal["asc", "desc"]]] | None = None,
138
164
  cursor: tuple[str, str] | None = None,
165
+ action: Action = Action.READ,
139
166
  ) -> PaginatedResponse:
140
167
  """Fetch all rows with automatic access control filtering."""
141
168
  access_where = self._build_access_control_where_clause(self.policy)
@@ -153,14 +180,18 @@ class AuthorizedSqlStore:
153
180
 
154
181
  for row in rows.data:
155
182
  stored_access_attrs = row.get("access_attributes")
156
- stored_owner_principal = row.get("owner_principal") or ""
183
+ stored_owner_principal = row.get("owner_principal")
157
184
 
158
185
  record_id = row.get("id", "unknown")
159
- sql_record = SqlRecord(
160
- str(record_id), table, User(principal=stored_owner_principal, attributes=stored_access_attrs)
186
+ # Create owner as None if owner_principal is empty/missing, matching ResourceWithOwner behavior
187
+ owner = (
188
+ User(principal=stored_owner_principal, attributes=stored_access_attrs)
189
+ if stored_owner_principal
190
+ else None
161
191
  )
192
+ sql_record = SqlRecord(str(record_id), table, owner)
162
193
 
163
- if is_action_allowed(self.policy, Action.READ, sql_record, current_user):
194
+ if is_action_allowed(self.policy, action, sql_record, current_user):
164
195
  filtered_rows.append(row)
165
196
 
166
197
  return PaginatedResponse(
@@ -173,6 +204,7 @@ class AuthorizedSqlStore:
173
204
  table: str,
174
205
  where: Mapping[str, Any] | None = None,
175
206
  order_by: list[tuple[str, Literal["asc", "desc"]]] | None = None,
207
+ action: Action = Action.READ,
176
208
  ) -> dict[str, Any] | None:
177
209
  """Fetch one row with automatic access control checking."""
178
210
  results = await self.fetch_all(
@@ -180,6 +212,7 @@ class AuthorizedSqlStore:
180
212
  where=where,
181
213
  limit=1,
182
214
  order_by=order_by,
215
+ action=action,
183
216
  )
184
217
 
185
218
  return results.data[0] if results.data else None
@@ -255,53 +288,40 @@ class AuthorizedSqlStore:
255
288
 
256
289
  Public records are those with:
257
290
  - owner_principal = '' (empty string)
258
- - access_attributes is either SQL NULL or JSON null
259
291
 
260
- Note: Different databases serialize None differently:
261
- - SQLite: None → JSON null (text = 'null')
262
- - Postgres: None → SQL NULL (IS NULL)
292
+ The policy "resource is unowned" only checks if owner_principal is empty,
293
+ regardless of access_attributes.
263
294
  """
264
- conditions = ["owner_principal = ''"]
265
- if self.database_type == StorageBackendType.SQL_POSTGRES.value:
266
- # Accept both SQL NULL and JSON null for Postgres compatibility
267
- # This handles both old rows (SQL NULL) and new rows (JSON null)
268
- conditions.append("(access_attributes IS NULL OR access_attributes::text = 'null')")
269
- elif self.database_type == StorageBackendType.SQL_SQLITE.value:
270
- # SQLite serializes None as JSON null
271
- conditions.append("(access_attributes IS NULL OR access_attributes = 'null')")
272
- else:
273
- raise ValueError(f"Unsupported database type: {self.database_type}")
274
- return conditions
295
+ return ["owner_principal = ''"]
275
296
 
276
297
  def _build_default_policy_where_clause(self, current_user: User | None) -> str:
277
298
  """Build SQL WHERE clause for the default policy.
278
299
 
279
300
  Default policy: permit all actions when user in owners [roles, teams, projects, namespaces]
280
- This means user must match ALL attribute categories that exist in the resource.
301
+ This means user must match ANY attribute category that exists in the resource (OR logic).
281
302
  """
282
303
  base_conditions = self._get_public_access_conditions()
283
- user_attr_conditions = []
284
-
285
- if current_user and current_user.attributes:
286
- for attr_key, user_values in current_user.attributes.items():
287
- if user_values:
288
- value_conditions = []
289
- for value in user_values:
290
- # Check if JSON array contains the value
291
- escaped_value = value.replace("'", "''")
292
- json_text = self._json_extract_text("access_attributes", attr_key)
293
- value_conditions.append(f"({json_text} LIKE '%\"{escaped_value}\"%')")
294
-
295
- if value_conditions:
296
- # Check if the category is missing (NULL)
297
- category_missing = f"{self._json_extract('access_attributes', attr_key)} IS NULL"
298
- user_matches_category = f"({' OR '.join(value_conditions)})"
299
- user_attr_conditions.append(f"({category_missing} OR {user_matches_category})")
300
-
301
- if user_attr_conditions:
302
- all_requirements_met = f"({' AND '.join(user_attr_conditions)})"
303
- base_conditions.append(all_requirements_met)
304
304
 
305
+ if current_user:
306
+ # Add "user is owner" condition - user's principal matches owner_principal
307
+ escaped_principal = current_user.principal.replace("'", "''")
308
+ base_conditions.append(f"owner_principal = '{escaped_principal}'")
309
+
310
+ # Add "user in owners" conditions for attribute matching
311
+ if current_user.attributes:
312
+ for attr_key, user_values in current_user.attributes.items():
313
+ if user_values:
314
+ value_conditions = []
315
+ for value in user_values:
316
+ # Check if JSON array contains the value
317
+ escaped_value = value.replace("'", "''")
318
+ json_text = self._json_extract_text("access_attributes", attr_key)
319
+ value_conditions.append(f"({json_text} LIKE '%\"{escaped_value}\"%')")
320
+
321
+ if value_conditions:
322
+ # User matches this category if any of their values match
323
+ user_matches_category = f"({' OR '.join(value_conditions)})"
324
+ base_conditions.append(user_matches_category)
305
325
  return f"({' OR '.join(base_conditions)})"
306
326
 
307
327
  def _build_conservative_where_clause(self) -> str:
@@ -4,7 +4,7 @@
4
4
  # This source code is licensed under the terms described in the LICENSE file in
5
5
  # the root directory of this source tree.
6
6
  from collections.abc import Mapping, Sequence
7
- from typing import Any, Literal
7
+ from typing import Any, Literal, cast
8
8
 
9
9
  from sqlalchemy import (
10
10
  JSON,
@@ -26,11 +26,10 @@ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
26
26
  from sqlalchemy.ext.asyncio.engine import AsyncEngine
27
27
  from sqlalchemy.sql.elements import ColumnElement
28
28
 
29
- from llama_stack.apis.common.responses import PaginatedResponse
30
29
  from llama_stack.core.storage.datatypes import SqlAlchemySqlStoreConfig
31
30
  from llama_stack.log import get_logger
32
-
33
- from .api import ColumnDefinition, ColumnType, SqlStore
31
+ from llama_stack_api import PaginatedResponse
32
+ from llama_stack_api.internal.sqlstore import ColumnDefinition, ColumnType, SqlStore
34
33
 
35
34
  logger = get_logger(name=__name__, category="providers::utils")
36
35
 
@@ -56,29 +55,30 @@ def _build_where_expr(column: ColumnElement, value: Any) -> ColumnElement:
56
55
  raise ValueError(f"Operator mapping must have a single operator, got: {value}")
57
56
  op, operand = next(iter(value.items()))
58
57
  if op == "==" or op == "=":
59
- return column == operand
58
+ return cast(ColumnElement[Any], column == operand)
60
59
  if op == ">":
61
- return column > operand
60
+ return cast(ColumnElement[Any], column > operand)
62
61
  if op == "<":
63
- return column < operand
62
+ return cast(ColumnElement[Any], column < operand)
64
63
  if op == ">=":
65
- return column >= operand
64
+ return cast(ColumnElement[Any], column >= operand)
66
65
  if op == "<=":
67
- return column <= operand
66
+ return cast(ColumnElement[Any], column <= operand)
68
67
  raise ValueError(f"Unsupported operator '{op}' in where mapping")
69
- return column == value
68
+ return cast(ColumnElement[Any], column == value)
70
69
 
71
70
 
72
71
  class SqlAlchemySqlStoreImpl(SqlStore):
73
72
  def __init__(self, config: SqlAlchemySqlStoreConfig):
74
73
  self.config = config
74
+ self._is_sqlite_backend = "sqlite" in self.config.engine_str
75
75
  self.async_session = async_sessionmaker(self.create_engine())
76
76
  self.metadata = MetaData()
77
77
 
78
78
  def create_engine(self) -> AsyncEngine:
79
79
  # Configure connection args for better concurrency support
80
80
  connect_args = {}
81
- if "sqlite" in self.config.engine_str:
81
+ if self._is_sqlite_backend:
82
82
  # SQLite-specific optimizations for concurrent access
83
83
  # With WAL mode, most locks resolve in milliseconds, but allow up to 5s for edge cases
84
84
  connect_args["timeout"] = 5.0
@@ -91,7 +91,7 @@ class SqlAlchemySqlStoreImpl(SqlStore):
91
91
  )
92
92
 
93
93
  # Enable WAL mode for SQLite to support concurrent readers and writers
94
- if "sqlite" in self.config.engine_str:
94
+ if self._is_sqlite_backend:
95
95
 
96
96
  @event.listens_for(engine.sync_engine, "connect")
97
97
  def set_sqlite_pragma(dbapi_conn, connection_record):
@@ -151,6 +151,29 @@ class SqlAlchemySqlStoreImpl(SqlStore):
151
151
  await session.execute(self.metadata.tables[table].insert(), data)
152
152
  await session.commit()
153
153
 
154
+ async def upsert(
155
+ self,
156
+ table: str,
157
+ data: Mapping[str, Any],
158
+ conflict_columns: list[str],
159
+ update_columns: list[str] | None = None,
160
+ ) -> None:
161
+ table_obj = self.metadata.tables[table]
162
+ dialect_insert = self._get_dialect_insert(table_obj)
163
+ insert_stmt = dialect_insert.values(**data)
164
+
165
+ if update_columns is None:
166
+ update_columns = [col for col in data.keys() if col not in conflict_columns]
167
+
168
+ update_mapping = {col: getattr(insert_stmt.excluded, col) for col in update_columns}
169
+ conflict_cols = [table_obj.c[col] for col in conflict_columns]
170
+
171
+ stmt = insert_stmt.on_conflict_do_update(index_elements=conflict_cols, set_=update_mapping)
172
+
173
+ async with self.async_session() as session:
174
+ await session.execute(stmt)
175
+ await session.commit()
176
+
154
177
  async def fetch_all(
155
178
  self,
156
179
  table: str,
@@ -240,10 +263,8 @@ class SqlAlchemySqlStoreImpl(SqlStore):
240
263
  query = query.limit(fetch_limit)
241
264
 
242
265
  result = await session.execute(query)
243
- if result.rowcount == 0:
244
- rows = []
245
- else:
246
- rows = [dict(row._mapping) for row in result]
266
+ # Iterate directly - if no rows, list comprehension yields empty list
267
+ rows = [dict(row._mapping) for row in result]
247
268
 
248
269
  # Always return pagination result
249
270
  has_more = False
@@ -335,9 +356,18 @@ class SqlAlchemySqlStoreImpl(SqlStore):
335
356
  add_column_sql = text(f"ALTER TABLE {table} ADD COLUMN {column_name} {compiled_type}{nullable_clause}")
336
357
 
337
358
  await conn.execute(add_column_sql)
338
-
339
359
  except Exception as e:
340
360
  # If any error occurs during migration, log it but don't fail
341
361
  # The table creation will handle adding the column
342
362
  logger.error(f"Error adding column {column_name} to table {table}: {e}")
343
363
  pass
364
+
365
+ def _get_dialect_insert(self, table: Table):
366
+ if self._is_sqlite_backend:
367
+ from sqlalchemy.dialects.sqlite import insert as sqlite_insert
368
+
369
+ return sqlite_insert(table)
370
+ else:
371
+ from sqlalchemy.dialects.postgresql import insert as pg_insert
372
+
373
+ return pg_insert(table)
@@ -4,6 +4,7 @@
4
4
  # This source code is licensed under the terms described in the LICENSE file in
5
5
  # the root directory of this source tree.
6
6
 
7
+ from threading import Lock
7
8
  from typing import Annotated, cast
8
9
 
9
10
  from pydantic import Field
@@ -15,12 +16,13 @@ from llama_stack.core.storage.datatypes import (
15
16
  StorageBackendConfig,
16
17
  StorageBackendType,
17
18
  )
18
-
19
- from .api import SqlStore
19
+ from llama_stack_api.internal.sqlstore import SqlStore
20
20
 
21
21
  sql_store_pip_packages = ["sqlalchemy[asyncio]", "aiosqlite", "asyncpg"]
22
22
 
23
23
  _SQLSTORE_BACKENDS: dict[str, StorageBackendConfig] = {}
24
+ _SQLSTORE_INSTANCES: dict[str, SqlStore] = {}
25
+ _SQLSTORE_LOCKS: dict[str, Lock] = {}
24
26
 
25
27
 
26
28
  SqlStoreConfig = Annotated[
@@ -52,19 +54,34 @@ def sqlstore_impl(reference: SqlStoreReference) -> SqlStore:
52
54
  f"Unknown SQL store backend '{backend_name}'. Registered backends: {sorted(_SQLSTORE_BACKENDS)}"
53
55
  )
54
56
 
55
- if isinstance(backend_config, SqliteSqlStoreConfig | PostgresSqlStoreConfig):
56
- from .sqlalchemy_sqlstore import SqlAlchemySqlStoreImpl
57
+ existing = _SQLSTORE_INSTANCES.get(backend_name)
58
+ if existing:
59
+ return existing
57
60
 
58
- config = cast(SqliteSqlStoreConfig | PostgresSqlStoreConfig, backend_config).model_copy()
59
- return SqlAlchemySqlStoreImpl(config)
60
- else:
61
- raise ValueError(f"Unknown sqlstore type {backend_config.type}")
61
+ lock = _SQLSTORE_LOCKS.setdefault(backend_name, Lock())
62
+ with lock:
63
+ existing = _SQLSTORE_INSTANCES.get(backend_name)
64
+ if existing:
65
+ return existing
66
+
67
+ if isinstance(backend_config, SqliteSqlStoreConfig | PostgresSqlStoreConfig):
68
+ from .sqlalchemy_sqlstore import SqlAlchemySqlStoreImpl
69
+
70
+ config = cast(SqliteSqlStoreConfig | PostgresSqlStoreConfig, backend_config).model_copy()
71
+ instance = SqlAlchemySqlStoreImpl(config)
72
+ _SQLSTORE_INSTANCES[backend_name] = instance
73
+ return instance
74
+ else:
75
+ raise ValueError(f"Unknown sqlstore type {backend_config.type}")
62
76
 
63
77
 
64
78
  def register_sqlstore_backends(backends: dict[str, StorageBackendConfig]) -> None:
65
79
  """Register the set of available SQL store backends for reference resolution."""
66
80
  global _SQLSTORE_BACKENDS
81
+ global _SQLSTORE_INSTANCES
67
82
 
68
83
  _SQLSTORE_BACKENDS.clear()
84
+ _SQLSTORE_INSTANCES.clear()
85
+ _SQLSTORE_LOCKS.clear()
69
86
  for name, cfg in backends.items():
70
87
  _SQLSTORE_BACKENDS[name] = cfg
@@ -12,8 +12,8 @@ import pydantic
12
12
 
13
13
  from llama_stack.core.datatypes import RoutableObjectWithProvider
14
14
  from llama_stack.core.storage.datatypes import KVStoreReference
15
+ from llama_stack.core.storage.kvstore import KVStore, kvstore_impl
15
16
  from llama_stack.log import get_logger
16
- from llama_stack.providers.utils.kvstore import KVStore, kvstore_impl
17
17
 
18
18
  logger = get_logger(__name__, category="core::registry")
19
19