llama-stack 0.3.5__py3-none-any.whl → 0.4.1__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 (460) 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 +235 -62
  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 +46 -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 +44 -33
  169. llama_stack/providers/registry/agents.py +8 -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 +131 -23
  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 +37 -28
  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 +37 -25
  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 +147 -30
  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 +31 -26
  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/providers/utils/vector_io/__init__.py +16 -0
  284. llama_stack/providers/utils/vector_io/vector_utils.py +36 -0
  285. llama_stack/telemetry/constants.py +27 -0
  286. llama_stack/telemetry/helpers.py +43 -0
  287. llama_stack/testing/api_recorder.py +25 -16
  288. {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/METADATA +57 -55
  289. llama_stack-0.4.1.dist-info/RECORD +588 -0
  290. llama_stack-0.4.1.dist-info/top_level.txt +2 -0
  291. llama_stack_api/__init__.py +945 -0
  292. llama_stack_api/admin/__init__.py +45 -0
  293. llama_stack_api/admin/api.py +72 -0
  294. llama_stack_api/admin/fastapi_routes.py +117 -0
  295. llama_stack_api/admin/models.py +113 -0
  296. llama_stack_api/agents.py +173 -0
  297. llama_stack_api/batches/__init__.py +40 -0
  298. llama_stack_api/batches/api.py +53 -0
  299. llama_stack_api/batches/fastapi_routes.py +113 -0
  300. llama_stack_api/batches/models.py +78 -0
  301. llama_stack_api/benchmarks/__init__.py +43 -0
  302. llama_stack_api/benchmarks/api.py +39 -0
  303. llama_stack_api/benchmarks/fastapi_routes.py +109 -0
  304. llama_stack_api/benchmarks/models.py +109 -0
  305. {llama_stack/apis → llama_stack_api}/common/content_types.py +1 -43
  306. {llama_stack/apis → llama_stack_api}/common/errors.py +0 -8
  307. {llama_stack/apis → llama_stack_api}/common/job_types.py +1 -1
  308. llama_stack_api/common/responses.py +77 -0
  309. {llama_stack/apis → llama_stack_api}/common/training_types.py +1 -1
  310. {llama_stack/apis → llama_stack_api}/common/type_system.py +2 -14
  311. llama_stack_api/connectors.py +146 -0
  312. {llama_stack/apis/conversations → llama_stack_api}/conversations.py +23 -39
  313. {llama_stack/apis/datasetio → llama_stack_api}/datasetio.py +4 -8
  314. llama_stack_api/datasets/__init__.py +61 -0
  315. llama_stack_api/datasets/api.py +35 -0
  316. llama_stack_api/datasets/fastapi_routes.py +104 -0
  317. llama_stack_api/datasets/models.py +152 -0
  318. {llama_stack/providers → llama_stack_api}/datatypes.py +166 -10
  319. {llama_stack/apis/eval → llama_stack_api}/eval.py +8 -40
  320. llama_stack_api/file_processors/__init__.py +27 -0
  321. llama_stack_api/file_processors/api.py +64 -0
  322. llama_stack_api/file_processors/fastapi_routes.py +78 -0
  323. llama_stack_api/file_processors/models.py +42 -0
  324. llama_stack_api/files/__init__.py +35 -0
  325. llama_stack_api/files/api.py +51 -0
  326. llama_stack_api/files/fastapi_routes.py +124 -0
  327. llama_stack_api/files/models.py +107 -0
  328. {llama_stack/apis/inference → llama_stack_api}/inference.py +90 -194
  329. llama_stack_api/inspect_api/__init__.py +37 -0
  330. llama_stack_api/inspect_api/api.py +25 -0
  331. llama_stack_api/inspect_api/fastapi_routes.py +76 -0
  332. llama_stack_api/inspect_api/models.py +28 -0
  333. {llama_stack/apis/agents → llama_stack_api/internal}/__init__.py +3 -1
  334. llama_stack/providers/utils/kvstore/api.py → llama_stack_api/internal/kvstore.py +5 -0
  335. llama_stack_api/internal/sqlstore.py +79 -0
  336. {llama_stack/apis/models → llama_stack_api}/models.py +11 -9
  337. {llama_stack/apis/agents → llama_stack_api}/openai_responses.py +184 -27
  338. {llama_stack/apis/post_training → llama_stack_api}/post_training.py +7 -11
  339. {llama_stack/apis/prompts → llama_stack_api}/prompts.py +3 -4
  340. llama_stack_api/providers/__init__.py +33 -0
  341. llama_stack_api/providers/api.py +16 -0
  342. llama_stack_api/providers/fastapi_routes.py +57 -0
  343. llama_stack_api/providers/models.py +24 -0
  344. {llama_stack/apis/tools → llama_stack_api}/rag_tool.py +2 -52
  345. {llama_stack/apis → llama_stack_api}/resource.py +1 -1
  346. llama_stack_api/router_utils.py +160 -0
  347. {llama_stack/apis/safety → llama_stack_api}/safety.py +6 -9
  348. {llama_stack → llama_stack_api}/schema_utils.py +94 -4
  349. {llama_stack/apis/scoring → llama_stack_api}/scoring.py +3 -3
  350. {llama_stack/apis/scoring_functions → llama_stack_api}/scoring_functions.py +9 -6
  351. {llama_stack/apis/shields → llama_stack_api}/shields.py +6 -7
  352. {llama_stack/apis/tools → llama_stack_api}/tools.py +26 -21
  353. {llama_stack/apis/vector_io → llama_stack_api}/vector_io.py +133 -152
  354. {llama_stack/apis/vector_stores → llama_stack_api}/vector_stores.py +1 -1
  355. llama_stack/apis/agents/agents.py +0 -894
  356. llama_stack/apis/batches/__init__.py +0 -9
  357. llama_stack/apis/batches/batches.py +0 -100
  358. llama_stack/apis/benchmarks/__init__.py +0 -7
  359. llama_stack/apis/benchmarks/benchmarks.py +0 -108
  360. llama_stack/apis/common/responses.py +0 -36
  361. llama_stack/apis/conversations/__init__.py +0 -31
  362. llama_stack/apis/datasets/datasets.py +0 -251
  363. llama_stack/apis/datatypes.py +0 -160
  364. llama_stack/apis/eval/__init__.py +0 -7
  365. llama_stack/apis/files/__init__.py +0 -7
  366. llama_stack/apis/files/files.py +0 -199
  367. llama_stack/apis/inference/__init__.py +0 -7
  368. llama_stack/apis/inference/event_logger.py +0 -43
  369. llama_stack/apis/inspect/__init__.py +0 -7
  370. llama_stack/apis/inspect/inspect.py +0 -94
  371. llama_stack/apis/models/__init__.py +0 -7
  372. llama_stack/apis/post_training/__init__.py +0 -7
  373. llama_stack/apis/prompts/__init__.py +0 -9
  374. llama_stack/apis/providers/__init__.py +0 -7
  375. llama_stack/apis/providers/providers.py +0 -69
  376. llama_stack/apis/safety/__init__.py +0 -7
  377. llama_stack/apis/scoring/__init__.py +0 -7
  378. llama_stack/apis/scoring_functions/__init__.py +0 -7
  379. llama_stack/apis/shields/__init__.py +0 -7
  380. llama_stack/apis/synthetic_data_generation/__init__.py +0 -7
  381. llama_stack/apis/synthetic_data_generation/synthetic_data_generation.py +0 -77
  382. llama_stack/apis/telemetry/__init__.py +0 -7
  383. llama_stack/apis/telemetry/telemetry.py +0 -423
  384. llama_stack/apis/tools/__init__.py +0 -8
  385. llama_stack/apis/vector_io/__init__.py +0 -7
  386. llama_stack/apis/vector_stores/__init__.py +0 -7
  387. llama_stack/core/server/tracing.py +0 -80
  388. llama_stack/core/ui/app.py +0 -55
  389. llama_stack/core/ui/modules/__init__.py +0 -5
  390. llama_stack/core/ui/modules/api.py +0 -32
  391. llama_stack/core/ui/modules/utils.py +0 -42
  392. llama_stack/core/ui/page/__init__.py +0 -5
  393. llama_stack/core/ui/page/distribution/__init__.py +0 -5
  394. llama_stack/core/ui/page/distribution/datasets.py +0 -18
  395. llama_stack/core/ui/page/distribution/eval_tasks.py +0 -20
  396. llama_stack/core/ui/page/distribution/models.py +0 -18
  397. llama_stack/core/ui/page/distribution/providers.py +0 -27
  398. llama_stack/core/ui/page/distribution/resources.py +0 -48
  399. llama_stack/core/ui/page/distribution/scoring_functions.py +0 -18
  400. llama_stack/core/ui/page/distribution/shields.py +0 -19
  401. llama_stack/core/ui/page/evaluations/__init__.py +0 -5
  402. llama_stack/core/ui/page/evaluations/app_eval.py +0 -143
  403. llama_stack/core/ui/page/evaluations/native_eval.py +0 -253
  404. llama_stack/core/ui/page/playground/__init__.py +0 -5
  405. llama_stack/core/ui/page/playground/chat.py +0 -130
  406. llama_stack/core/ui/page/playground/tools.py +0 -352
  407. llama_stack/distributions/dell/build.yaml +0 -33
  408. llama_stack/distributions/meta-reference-gpu/build.yaml +0 -32
  409. llama_stack/distributions/nvidia/build.yaml +0 -29
  410. llama_stack/distributions/open-benchmark/build.yaml +0 -36
  411. llama_stack/distributions/postgres-demo/__init__.py +0 -7
  412. llama_stack/distributions/postgres-demo/build.yaml +0 -23
  413. llama_stack/distributions/postgres-demo/postgres_demo.py +0 -125
  414. llama_stack/distributions/starter/build.yaml +0 -61
  415. llama_stack/distributions/starter-gpu/build.yaml +0 -61
  416. llama_stack/distributions/watsonx/build.yaml +0 -33
  417. llama_stack/providers/inline/agents/meta_reference/agent_instance.py +0 -1024
  418. llama_stack/providers/inline/agents/meta_reference/persistence.py +0 -228
  419. llama_stack/providers/inline/telemetry/__init__.py +0 -5
  420. llama_stack/providers/inline/telemetry/meta_reference/__init__.py +0 -21
  421. llama_stack/providers/inline/telemetry/meta_reference/config.py +0 -47
  422. llama_stack/providers/inline/telemetry/meta_reference/telemetry.py +0 -252
  423. llama_stack/providers/remote/inference/bedrock/models.py +0 -29
  424. llama_stack/providers/utils/kvstore/sqlite/config.py +0 -20
  425. llama_stack/providers/utils/sqlstore/__init__.py +0 -5
  426. llama_stack/providers/utils/sqlstore/api.py +0 -128
  427. llama_stack/providers/utils/telemetry/__init__.py +0 -5
  428. llama_stack/providers/utils/telemetry/trace_protocol.py +0 -142
  429. llama_stack/providers/utils/telemetry/tracing.py +0 -384
  430. llama_stack/strong_typing/__init__.py +0 -19
  431. llama_stack/strong_typing/auxiliary.py +0 -228
  432. llama_stack/strong_typing/classdef.py +0 -440
  433. llama_stack/strong_typing/core.py +0 -46
  434. llama_stack/strong_typing/deserializer.py +0 -877
  435. llama_stack/strong_typing/docstring.py +0 -409
  436. llama_stack/strong_typing/exception.py +0 -23
  437. llama_stack/strong_typing/inspection.py +0 -1085
  438. llama_stack/strong_typing/mapping.py +0 -40
  439. llama_stack/strong_typing/name.py +0 -182
  440. llama_stack/strong_typing/schema.py +0 -792
  441. llama_stack/strong_typing/serialization.py +0 -97
  442. llama_stack/strong_typing/serializer.py +0 -500
  443. llama_stack/strong_typing/slots.py +0 -27
  444. llama_stack/strong_typing/topological.py +0 -89
  445. llama_stack/ui/node_modules/flatted/python/flatted.py +0 -149
  446. llama_stack-0.3.5.dist-info/RECORD +0 -625
  447. llama_stack-0.3.5.dist-info/top_level.txt +0 -1
  448. /llama_stack/{providers/utils → core/storage}/kvstore/config.py +0 -0
  449. /llama_stack/{providers/utils → core/storage}/kvstore/mongodb/__init__.py +0 -0
  450. /llama_stack/{providers/utils → core/storage}/kvstore/postgres/__init__.py +0 -0
  451. /llama_stack/{providers/utils → core/storage}/kvstore/redis/__init__.py +0 -0
  452. /llama_stack/{providers/utils → core/storage}/kvstore/sqlite/__init__.py +0 -0
  453. /llama_stack/{apis → providers/inline/file_processor}/__init__.py +0 -0
  454. /llama_stack/{apis/common → telemetry}/__init__.py +0 -0
  455. {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/WHEEL +0 -0
  456. {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/entry_points.txt +0 -0
  457. {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/licenses/LICENSE +0 -0
  458. {llama_stack/core/ui → llama_stack_api/common}/__init__.py +0 -0
  459. {llama_stack/strong_typing → llama_stack_api}/py.typed +0 -0
  460. {llama_stack/apis → llama_stack_api}/version.py +0 -0
@@ -9,8 +9,8 @@ from datetime import datetime
9
9
  from pymongo import AsyncMongoClient
10
10
  from pymongo.asynchronous.collection import AsyncCollection
11
11
 
12
+ from llama_stack.core.storage.kvstore import KVStore
12
13
  from llama_stack.log import get_logger
13
- from llama_stack.providers.utils.kvstore import KVStore
14
14
 
15
15
  from ..config import MongoDBKVStoreConfig
16
16
 
@@ -30,14 +30,13 @@ class MongoDBKVStoreImpl(KVStore):
30
30
 
31
31
  async def initialize(self) -> None:
32
32
  try:
33
- conn_creds = {
34
- "host": self.config.host,
35
- "port": self.config.port,
36
- "username": self.config.user,
37
- "password": self.config.password,
38
- }
39
- conn_creds = {k: v for k, v in conn_creds.items() if v is not None}
40
- self.conn = AsyncMongoClient(**conn_creds)
33
+ # Pass parameters explicitly to satisfy mypy - AsyncMongoClient doesn't accept **dict
34
+ self.conn = AsyncMongoClient(
35
+ host=self.config.host if self.config.host is not None else None,
36
+ port=self.config.port if self.config.port is not None else None,
37
+ username=self.config.user if self.config.user is not None else None,
38
+ password=self.config.password if self.config.password is not None else None,
39
+ )
41
40
  except Exception as e:
42
41
  log.exception("Could not connect to MongoDB database server")
43
42
  raise RuntimeError("Could not connect to MongoDB database server") from e
@@ -79,4 +78,8 @@ class MongoDBKVStoreImpl(KVStore):
79
78
  end_key = self._namespaced_key(end_key)
80
79
  query = {"key": {"$gte": start_key, "$lt": end_key}}
81
80
  cursor = self.collection.find(query, {"key": 1, "_id": 0}).sort("key", 1)
82
- return [doc["key"] for doc in cursor]
81
+ # AsyncCursor requires async iteration
82
+ result = []
83
+ async for doc in cursor:
84
+ result.append(doc["key"])
85
+ return result
@@ -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)