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.
- llama_stack/__init__.py +0 -5
- llama_stack/cli/llama.py +3 -3
- llama_stack/cli/stack/_list_deps.py +12 -23
- llama_stack/cli/stack/list_stacks.py +37 -18
- llama_stack/cli/stack/run.py +121 -11
- llama_stack/cli/stack/utils.py +0 -127
- llama_stack/core/access_control/access_control.py +69 -28
- llama_stack/core/access_control/conditions.py +15 -5
- llama_stack/core/admin.py +267 -0
- llama_stack/core/build.py +6 -74
- llama_stack/core/client.py +1 -1
- llama_stack/core/configure.py +6 -6
- llama_stack/core/conversations/conversations.py +28 -25
- llama_stack/core/datatypes.py +271 -79
- llama_stack/core/distribution.py +15 -16
- llama_stack/core/external.py +3 -3
- llama_stack/core/inspect.py +98 -15
- llama_stack/core/library_client.py +73 -61
- llama_stack/core/prompts/prompts.py +12 -11
- llama_stack/core/providers.py +17 -11
- llama_stack/core/resolver.py +65 -56
- llama_stack/core/routers/__init__.py +8 -12
- llama_stack/core/routers/datasets.py +1 -4
- llama_stack/core/routers/eval_scoring.py +7 -4
- llama_stack/core/routers/inference.py +55 -271
- llama_stack/core/routers/safety.py +52 -24
- llama_stack/core/routers/tool_runtime.py +6 -48
- llama_stack/core/routers/vector_io.py +130 -51
- llama_stack/core/routing_tables/benchmarks.py +24 -20
- llama_stack/core/routing_tables/common.py +1 -4
- llama_stack/core/routing_tables/datasets.py +22 -22
- llama_stack/core/routing_tables/models.py +119 -6
- llama_stack/core/routing_tables/scoring_functions.py +7 -7
- llama_stack/core/routing_tables/shields.py +1 -2
- llama_stack/core/routing_tables/toolgroups.py +17 -7
- llama_stack/core/routing_tables/vector_stores.py +51 -16
- llama_stack/core/server/auth.py +5 -3
- llama_stack/core/server/auth_providers.py +36 -20
- llama_stack/core/server/fastapi_router_registry.py +84 -0
- llama_stack/core/server/quota.py +2 -2
- llama_stack/core/server/routes.py +79 -27
- llama_stack/core/server/server.py +102 -87
- llama_stack/core/stack.py +235 -62
- llama_stack/core/storage/datatypes.py +26 -3
- llama_stack/{providers/utils → core/storage}/kvstore/__init__.py +2 -0
- llama_stack/{providers/utils → core/storage}/kvstore/kvstore.py +55 -24
- llama_stack/{providers/utils → core/storage}/kvstore/mongodb/mongodb.py +13 -10
- llama_stack/{providers/utils → core/storage}/kvstore/postgres/postgres.py +28 -17
- llama_stack/{providers/utils → core/storage}/kvstore/redis/redis.py +41 -16
- llama_stack/{providers/utils → core/storage}/kvstore/sqlite/sqlite.py +1 -1
- llama_stack/core/storage/sqlstore/__init__.py +17 -0
- llama_stack/{providers/utils → core/storage}/sqlstore/authorized_sqlstore.py +69 -49
- llama_stack/{providers/utils → core/storage}/sqlstore/sqlalchemy_sqlstore.py +47 -17
- llama_stack/{providers/utils → core/storage}/sqlstore/sqlstore.py +25 -8
- llama_stack/core/store/registry.py +1 -1
- llama_stack/core/utils/config.py +8 -2
- llama_stack/core/utils/config_resolution.py +32 -29
- llama_stack/core/utils/context.py +4 -10
- llama_stack/core/utils/exec.py +9 -0
- llama_stack/core/utils/type_inspection.py +45 -0
- llama_stack/distributions/dell/{run.yaml → config.yaml} +3 -2
- llama_stack/distributions/dell/dell.py +2 -2
- llama_stack/distributions/dell/run-with-safety.yaml +3 -2
- llama_stack/distributions/meta-reference-gpu/{run.yaml → config.yaml} +3 -2
- llama_stack/distributions/meta-reference-gpu/meta_reference.py +2 -2
- llama_stack/distributions/meta-reference-gpu/run-with-safety.yaml +3 -2
- llama_stack/distributions/nvidia/{run.yaml → config.yaml} +4 -4
- llama_stack/distributions/nvidia/nvidia.py +1 -1
- llama_stack/distributions/nvidia/run-with-safety.yaml +4 -4
- llama_stack/{apis/datasetio → distributions/oci}/__init__.py +1 -1
- llama_stack/distributions/oci/config.yaml +134 -0
- llama_stack/distributions/oci/oci.py +108 -0
- llama_stack/distributions/open-benchmark/{run.yaml → config.yaml} +5 -4
- llama_stack/distributions/open-benchmark/open_benchmark.py +2 -3
- llama_stack/distributions/postgres-demo/{run.yaml → config.yaml} +4 -3
- llama_stack/distributions/starter/{run.yaml → config.yaml} +64 -13
- llama_stack/distributions/starter/run-with-postgres-store.yaml +64 -13
- llama_stack/distributions/starter/starter.py +8 -5
- llama_stack/distributions/starter-gpu/{run.yaml → config.yaml} +64 -13
- llama_stack/distributions/starter-gpu/run-with-postgres-store.yaml +64 -13
- llama_stack/distributions/template.py +13 -69
- llama_stack/distributions/watsonx/{run.yaml → config.yaml} +4 -3
- llama_stack/distributions/watsonx/watsonx.py +1 -1
- llama_stack/log.py +28 -11
- llama_stack/models/llama/checkpoint.py +6 -6
- llama_stack/models/llama/hadamard_utils.py +2 -0
- llama_stack/models/llama/llama3/generation.py +3 -1
- llama_stack/models/llama/llama3/interface.py +2 -5
- llama_stack/models/llama/llama3/multimodal/encoder_utils.py +3 -3
- llama_stack/models/llama/llama3/multimodal/image_transform.py +6 -6
- llama_stack/models/llama/llama3/prompt_templates/system_prompts.py +1 -1
- llama_stack/models/llama/llama3/tool_utils.py +2 -1
- llama_stack/models/llama/llama4/prompt_templates/system_prompts.py +1 -1
- llama_stack/providers/inline/agents/meta_reference/__init__.py +3 -3
- llama_stack/providers/inline/agents/meta_reference/agents.py +44 -261
- llama_stack/providers/inline/agents/meta_reference/config.py +6 -1
- llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py +207 -57
- llama_stack/providers/inline/agents/meta_reference/responses/streaming.py +308 -47
- llama_stack/providers/inline/agents/meta_reference/responses/tool_executor.py +162 -96
- llama_stack/providers/inline/agents/meta_reference/responses/types.py +23 -8
- llama_stack/providers/inline/agents/meta_reference/responses/utils.py +201 -33
- llama_stack/providers/inline/agents/meta_reference/safety.py +8 -13
- llama_stack/providers/inline/batches/reference/__init__.py +2 -4
- llama_stack/providers/inline/batches/reference/batches.py +78 -60
- llama_stack/providers/inline/datasetio/localfs/datasetio.py +2 -5
- llama_stack/providers/inline/eval/meta_reference/eval.py +16 -61
- llama_stack/providers/inline/files/localfs/files.py +37 -28
- llama_stack/providers/inline/inference/meta_reference/config.py +2 -2
- llama_stack/providers/inline/inference/meta_reference/generators.py +50 -60
- llama_stack/providers/inline/inference/meta_reference/inference.py +403 -19
- llama_stack/providers/inline/inference/meta_reference/model_parallel.py +7 -26
- llama_stack/providers/inline/inference/meta_reference/parallel_utils.py +2 -12
- llama_stack/providers/inline/inference/sentence_transformers/sentence_transformers.py +10 -15
- llama_stack/providers/inline/post_training/common/validator.py +1 -5
- llama_stack/providers/inline/post_training/huggingface/post_training.py +8 -8
- llama_stack/providers/inline/post_training/huggingface/recipes/finetune_single_device.py +18 -10
- llama_stack/providers/inline/post_training/huggingface/recipes/finetune_single_device_dpo.py +12 -9
- llama_stack/providers/inline/post_training/huggingface/utils.py +27 -6
- llama_stack/providers/inline/post_training/torchtune/common/checkpointer.py +1 -1
- llama_stack/providers/inline/post_training/torchtune/common/utils.py +1 -1
- llama_stack/providers/inline/post_training/torchtune/datasets/format_adapter.py +1 -1
- llama_stack/providers/inline/post_training/torchtune/post_training.py +8 -8
- llama_stack/providers/inline/post_training/torchtune/recipes/lora_finetuning_single_device.py +16 -16
- llama_stack/providers/inline/safety/code_scanner/code_scanner.py +13 -9
- llama_stack/providers/inline/safety/llama_guard/llama_guard.py +18 -15
- llama_stack/providers/inline/safety/prompt_guard/prompt_guard.py +9 -9
- llama_stack/providers/inline/scoring/basic/scoring.py +6 -13
- llama_stack/providers/inline/scoring/basic/scoring_fn/docvqa_scoring_fn.py +1 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/equality_scoring_fn.py +1 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/docvqa.py +2 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/equality.py +2 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/ifeval.py +2 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/regex_parser_math_response.py +2 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/regex_parser_multiple_choice_answer.py +2 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/fn_defs/subset_of.py +2 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/ifeval_scoring_fn.py +1 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/regex_parser_math_response_scoring_fn.py +1 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/regex_parser_scoring_fn.py +1 -2
- llama_stack/providers/inline/scoring/basic/scoring_fn/subset_of_scoring_fn.py +1 -2
- llama_stack/providers/inline/scoring/braintrust/braintrust.py +12 -15
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/answer_correctness.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/answer_relevancy.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/answer_similarity.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_entity_recall.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_precision.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_recall.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/context_relevancy.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/factuality.py +2 -2
- llama_stack/providers/inline/scoring/braintrust/scoring_fn/fn_defs/faithfulness.py +2 -2
- llama_stack/providers/inline/scoring/llm_as_judge/scoring.py +7 -14
- llama_stack/providers/inline/scoring/llm_as_judge/scoring_fn/fn_defs/llm_as_judge_405b_simpleqa.py +2 -2
- llama_stack/providers/inline/scoring/llm_as_judge/scoring_fn/fn_defs/llm_as_judge_base.py +1 -2
- llama_stack/providers/inline/scoring/llm_as_judge/scoring_fn/llm_as_judge_scoring_fn.py +1 -3
- llama_stack/providers/inline/tool_runtime/rag/__init__.py +1 -1
- llama_stack/providers/inline/tool_runtime/rag/config.py +8 -1
- llama_stack/providers/inline/tool_runtime/rag/context_retriever.py +7 -6
- llama_stack/providers/inline/tool_runtime/rag/memory.py +64 -48
- llama_stack/providers/inline/vector_io/chroma/__init__.py +1 -1
- llama_stack/providers/inline/vector_io/chroma/config.py +1 -1
- llama_stack/providers/inline/vector_io/faiss/__init__.py +1 -1
- llama_stack/providers/inline/vector_io/faiss/config.py +1 -1
- llama_stack/providers/inline/vector_io/faiss/faiss.py +46 -28
- llama_stack/providers/inline/vector_io/milvus/__init__.py +1 -1
- llama_stack/providers/inline/vector_io/milvus/config.py +1 -1
- llama_stack/providers/inline/vector_io/qdrant/__init__.py +1 -1
- llama_stack/providers/inline/vector_io/qdrant/config.py +1 -1
- llama_stack/providers/inline/vector_io/sqlite_vec/__init__.py +1 -1
- llama_stack/providers/inline/vector_io/sqlite_vec/sqlite_vec.py +44 -33
- llama_stack/providers/registry/agents.py +8 -3
- llama_stack/providers/registry/batches.py +1 -1
- llama_stack/providers/registry/datasetio.py +1 -1
- llama_stack/providers/registry/eval.py +1 -1
- llama_stack/{apis/datasets/__init__.py → providers/registry/file_processors.py} +5 -1
- llama_stack/providers/registry/files.py +11 -2
- llama_stack/providers/registry/inference.py +22 -3
- llama_stack/providers/registry/post_training.py +1 -1
- llama_stack/providers/registry/safety.py +1 -1
- llama_stack/providers/registry/scoring.py +1 -1
- llama_stack/providers/registry/tool_runtime.py +2 -2
- llama_stack/providers/registry/vector_io.py +7 -7
- llama_stack/providers/remote/datasetio/huggingface/huggingface.py +2 -5
- llama_stack/providers/remote/datasetio/nvidia/datasetio.py +1 -4
- llama_stack/providers/remote/eval/nvidia/eval.py +15 -9
- llama_stack/providers/remote/files/openai/__init__.py +19 -0
- llama_stack/providers/remote/files/openai/config.py +28 -0
- llama_stack/providers/remote/files/openai/files.py +253 -0
- llama_stack/providers/remote/files/s3/files.py +52 -30
- llama_stack/providers/remote/inference/anthropic/anthropic.py +2 -1
- llama_stack/providers/remote/inference/anthropic/config.py +1 -1
- llama_stack/providers/remote/inference/azure/azure.py +1 -3
- llama_stack/providers/remote/inference/azure/config.py +8 -7
- llama_stack/providers/remote/inference/bedrock/__init__.py +1 -1
- llama_stack/providers/remote/inference/bedrock/bedrock.py +82 -105
- llama_stack/providers/remote/inference/bedrock/config.py +24 -3
- llama_stack/providers/remote/inference/cerebras/cerebras.py +5 -5
- llama_stack/providers/remote/inference/cerebras/config.py +12 -5
- llama_stack/providers/remote/inference/databricks/config.py +13 -6
- llama_stack/providers/remote/inference/databricks/databricks.py +16 -6
- llama_stack/providers/remote/inference/fireworks/config.py +5 -5
- llama_stack/providers/remote/inference/fireworks/fireworks.py +1 -1
- llama_stack/providers/remote/inference/gemini/config.py +1 -1
- llama_stack/providers/remote/inference/gemini/gemini.py +13 -14
- llama_stack/providers/remote/inference/groq/config.py +5 -5
- llama_stack/providers/remote/inference/groq/groq.py +1 -1
- llama_stack/providers/remote/inference/llama_openai_compat/config.py +5 -5
- llama_stack/providers/remote/inference/llama_openai_compat/llama.py +8 -6
- llama_stack/providers/remote/inference/nvidia/__init__.py +1 -1
- llama_stack/providers/remote/inference/nvidia/config.py +21 -11
- llama_stack/providers/remote/inference/nvidia/nvidia.py +115 -3
- llama_stack/providers/remote/inference/nvidia/utils.py +1 -1
- llama_stack/providers/remote/inference/oci/__init__.py +17 -0
- llama_stack/providers/remote/inference/oci/auth.py +79 -0
- llama_stack/providers/remote/inference/oci/config.py +75 -0
- llama_stack/providers/remote/inference/oci/oci.py +162 -0
- llama_stack/providers/remote/inference/ollama/config.py +7 -5
- llama_stack/providers/remote/inference/ollama/ollama.py +17 -8
- llama_stack/providers/remote/inference/openai/config.py +4 -4
- llama_stack/providers/remote/inference/openai/openai.py +1 -1
- llama_stack/providers/remote/inference/passthrough/__init__.py +2 -2
- llama_stack/providers/remote/inference/passthrough/config.py +5 -10
- llama_stack/providers/remote/inference/passthrough/passthrough.py +97 -75
- llama_stack/providers/remote/inference/runpod/config.py +12 -5
- llama_stack/providers/remote/inference/runpod/runpod.py +2 -20
- llama_stack/providers/remote/inference/sambanova/config.py +5 -5
- llama_stack/providers/remote/inference/sambanova/sambanova.py +1 -1
- llama_stack/providers/remote/inference/tgi/config.py +7 -6
- llama_stack/providers/remote/inference/tgi/tgi.py +19 -11
- llama_stack/providers/remote/inference/together/config.py +5 -5
- llama_stack/providers/remote/inference/together/together.py +15 -12
- llama_stack/providers/remote/inference/vertexai/config.py +1 -1
- llama_stack/providers/remote/inference/vllm/config.py +5 -5
- llama_stack/providers/remote/inference/vllm/vllm.py +13 -14
- llama_stack/providers/remote/inference/watsonx/config.py +4 -4
- llama_stack/providers/remote/inference/watsonx/watsonx.py +21 -94
- llama_stack/providers/remote/post_training/nvidia/post_training.py +4 -4
- llama_stack/providers/remote/post_training/nvidia/utils.py +1 -1
- llama_stack/providers/remote/safety/bedrock/bedrock.py +6 -6
- llama_stack/providers/remote/safety/bedrock/config.py +1 -1
- llama_stack/providers/remote/safety/nvidia/config.py +1 -1
- llama_stack/providers/remote/safety/nvidia/nvidia.py +11 -5
- llama_stack/providers/remote/safety/sambanova/config.py +1 -1
- llama_stack/providers/remote/safety/sambanova/sambanova.py +6 -6
- llama_stack/providers/remote/tool_runtime/bing_search/bing_search.py +11 -6
- llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py +12 -7
- llama_stack/providers/remote/tool_runtime/model_context_protocol/config.py +8 -2
- llama_stack/providers/remote/tool_runtime/model_context_protocol/model_context_protocol.py +57 -15
- llama_stack/providers/remote/tool_runtime/tavily_search/tavily_search.py +11 -6
- llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py +11 -6
- llama_stack/providers/remote/vector_io/chroma/__init__.py +1 -1
- llama_stack/providers/remote/vector_io/chroma/chroma.py +131 -23
- llama_stack/providers/remote/vector_io/chroma/config.py +1 -1
- llama_stack/providers/remote/vector_io/milvus/__init__.py +1 -1
- llama_stack/providers/remote/vector_io/milvus/config.py +1 -1
- llama_stack/providers/remote/vector_io/milvus/milvus.py +37 -28
- llama_stack/providers/remote/vector_io/pgvector/__init__.py +1 -1
- llama_stack/providers/remote/vector_io/pgvector/config.py +1 -1
- llama_stack/providers/remote/vector_io/pgvector/pgvector.py +37 -25
- llama_stack/providers/remote/vector_io/qdrant/__init__.py +1 -1
- llama_stack/providers/remote/vector_io/qdrant/config.py +1 -1
- llama_stack/providers/remote/vector_io/qdrant/qdrant.py +147 -30
- llama_stack/providers/remote/vector_io/weaviate/__init__.py +1 -1
- llama_stack/providers/remote/vector_io/weaviate/config.py +1 -1
- llama_stack/providers/remote/vector_io/weaviate/weaviate.py +31 -26
- llama_stack/providers/utils/common/data_schema_validator.py +1 -5
- llama_stack/providers/utils/files/form_data.py +1 -1
- llama_stack/providers/utils/inference/embedding_mixin.py +1 -1
- llama_stack/providers/utils/inference/inference_store.py +7 -8
- llama_stack/providers/utils/inference/litellm_openai_mixin.py +79 -79
- llama_stack/providers/utils/inference/model_registry.py +1 -3
- llama_stack/providers/utils/inference/openai_compat.py +44 -1171
- llama_stack/providers/utils/inference/openai_mixin.py +68 -42
- llama_stack/providers/utils/inference/prompt_adapter.py +50 -265
- llama_stack/providers/utils/inference/stream_utils.py +23 -0
- llama_stack/providers/utils/memory/__init__.py +2 -0
- llama_stack/providers/utils/memory/file_utils.py +1 -1
- llama_stack/providers/utils/memory/openai_vector_store_mixin.py +181 -84
- llama_stack/providers/utils/memory/vector_store.py +39 -38
- llama_stack/providers/utils/pagination.py +1 -1
- llama_stack/providers/utils/responses/responses_store.py +15 -25
- llama_stack/providers/utils/scoring/aggregation_utils.py +1 -2
- llama_stack/providers/utils/scoring/base_scoring_fn.py +1 -2
- llama_stack/providers/utils/tools/mcp.py +93 -11
- llama_stack/providers/utils/vector_io/__init__.py +16 -0
- llama_stack/providers/utils/vector_io/vector_utils.py +36 -0
- llama_stack/telemetry/constants.py +27 -0
- llama_stack/telemetry/helpers.py +43 -0
- llama_stack/testing/api_recorder.py +25 -16
- {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/METADATA +57 -55
- llama_stack-0.4.1.dist-info/RECORD +588 -0
- llama_stack-0.4.1.dist-info/top_level.txt +2 -0
- llama_stack_api/__init__.py +945 -0
- llama_stack_api/admin/__init__.py +45 -0
- llama_stack_api/admin/api.py +72 -0
- llama_stack_api/admin/fastapi_routes.py +117 -0
- llama_stack_api/admin/models.py +113 -0
- llama_stack_api/agents.py +173 -0
- llama_stack_api/batches/__init__.py +40 -0
- llama_stack_api/batches/api.py +53 -0
- llama_stack_api/batches/fastapi_routes.py +113 -0
- llama_stack_api/batches/models.py +78 -0
- llama_stack_api/benchmarks/__init__.py +43 -0
- llama_stack_api/benchmarks/api.py +39 -0
- llama_stack_api/benchmarks/fastapi_routes.py +109 -0
- llama_stack_api/benchmarks/models.py +109 -0
- {llama_stack/apis → llama_stack_api}/common/content_types.py +1 -43
- {llama_stack/apis → llama_stack_api}/common/errors.py +0 -8
- {llama_stack/apis → llama_stack_api}/common/job_types.py +1 -1
- llama_stack_api/common/responses.py +77 -0
- {llama_stack/apis → llama_stack_api}/common/training_types.py +1 -1
- {llama_stack/apis → llama_stack_api}/common/type_system.py +2 -14
- llama_stack_api/connectors.py +146 -0
- {llama_stack/apis/conversations → llama_stack_api}/conversations.py +23 -39
- {llama_stack/apis/datasetio → llama_stack_api}/datasetio.py +4 -8
- llama_stack_api/datasets/__init__.py +61 -0
- llama_stack_api/datasets/api.py +35 -0
- llama_stack_api/datasets/fastapi_routes.py +104 -0
- llama_stack_api/datasets/models.py +152 -0
- {llama_stack/providers → llama_stack_api}/datatypes.py +166 -10
- {llama_stack/apis/eval → llama_stack_api}/eval.py +8 -40
- llama_stack_api/file_processors/__init__.py +27 -0
- llama_stack_api/file_processors/api.py +64 -0
- llama_stack_api/file_processors/fastapi_routes.py +78 -0
- llama_stack_api/file_processors/models.py +42 -0
- llama_stack_api/files/__init__.py +35 -0
- llama_stack_api/files/api.py +51 -0
- llama_stack_api/files/fastapi_routes.py +124 -0
- llama_stack_api/files/models.py +107 -0
- {llama_stack/apis/inference → llama_stack_api}/inference.py +90 -194
- llama_stack_api/inspect_api/__init__.py +37 -0
- llama_stack_api/inspect_api/api.py +25 -0
- llama_stack_api/inspect_api/fastapi_routes.py +76 -0
- llama_stack_api/inspect_api/models.py +28 -0
- {llama_stack/apis/agents → llama_stack_api/internal}/__init__.py +3 -1
- llama_stack/providers/utils/kvstore/api.py → llama_stack_api/internal/kvstore.py +5 -0
- llama_stack_api/internal/sqlstore.py +79 -0
- {llama_stack/apis/models → llama_stack_api}/models.py +11 -9
- {llama_stack/apis/agents → llama_stack_api}/openai_responses.py +184 -27
- {llama_stack/apis/post_training → llama_stack_api}/post_training.py +7 -11
- {llama_stack/apis/prompts → llama_stack_api}/prompts.py +3 -4
- llama_stack_api/providers/__init__.py +33 -0
- llama_stack_api/providers/api.py +16 -0
- llama_stack_api/providers/fastapi_routes.py +57 -0
- llama_stack_api/providers/models.py +24 -0
- {llama_stack/apis/tools → llama_stack_api}/rag_tool.py +2 -52
- {llama_stack/apis → llama_stack_api}/resource.py +1 -1
- llama_stack_api/router_utils.py +160 -0
- {llama_stack/apis/safety → llama_stack_api}/safety.py +6 -9
- {llama_stack → llama_stack_api}/schema_utils.py +94 -4
- {llama_stack/apis/scoring → llama_stack_api}/scoring.py +3 -3
- {llama_stack/apis/scoring_functions → llama_stack_api}/scoring_functions.py +9 -6
- {llama_stack/apis/shields → llama_stack_api}/shields.py +6 -7
- {llama_stack/apis/tools → llama_stack_api}/tools.py +26 -21
- {llama_stack/apis/vector_io → llama_stack_api}/vector_io.py +133 -152
- {llama_stack/apis/vector_stores → llama_stack_api}/vector_stores.py +1 -1
- llama_stack/apis/agents/agents.py +0 -894
- llama_stack/apis/batches/__init__.py +0 -9
- llama_stack/apis/batches/batches.py +0 -100
- llama_stack/apis/benchmarks/__init__.py +0 -7
- llama_stack/apis/benchmarks/benchmarks.py +0 -108
- llama_stack/apis/common/responses.py +0 -36
- llama_stack/apis/conversations/__init__.py +0 -31
- llama_stack/apis/datasets/datasets.py +0 -251
- llama_stack/apis/datatypes.py +0 -160
- llama_stack/apis/eval/__init__.py +0 -7
- llama_stack/apis/files/__init__.py +0 -7
- llama_stack/apis/files/files.py +0 -199
- llama_stack/apis/inference/__init__.py +0 -7
- llama_stack/apis/inference/event_logger.py +0 -43
- llama_stack/apis/inspect/__init__.py +0 -7
- llama_stack/apis/inspect/inspect.py +0 -94
- llama_stack/apis/models/__init__.py +0 -7
- llama_stack/apis/post_training/__init__.py +0 -7
- llama_stack/apis/prompts/__init__.py +0 -9
- llama_stack/apis/providers/__init__.py +0 -7
- llama_stack/apis/providers/providers.py +0 -69
- llama_stack/apis/safety/__init__.py +0 -7
- llama_stack/apis/scoring/__init__.py +0 -7
- llama_stack/apis/scoring_functions/__init__.py +0 -7
- llama_stack/apis/shields/__init__.py +0 -7
- llama_stack/apis/synthetic_data_generation/__init__.py +0 -7
- llama_stack/apis/synthetic_data_generation/synthetic_data_generation.py +0 -77
- llama_stack/apis/telemetry/__init__.py +0 -7
- llama_stack/apis/telemetry/telemetry.py +0 -423
- llama_stack/apis/tools/__init__.py +0 -8
- llama_stack/apis/vector_io/__init__.py +0 -7
- llama_stack/apis/vector_stores/__init__.py +0 -7
- llama_stack/core/server/tracing.py +0 -80
- llama_stack/core/ui/app.py +0 -55
- llama_stack/core/ui/modules/__init__.py +0 -5
- llama_stack/core/ui/modules/api.py +0 -32
- llama_stack/core/ui/modules/utils.py +0 -42
- llama_stack/core/ui/page/__init__.py +0 -5
- llama_stack/core/ui/page/distribution/__init__.py +0 -5
- llama_stack/core/ui/page/distribution/datasets.py +0 -18
- llama_stack/core/ui/page/distribution/eval_tasks.py +0 -20
- llama_stack/core/ui/page/distribution/models.py +0 -18
- llama_stack/core/ui/page/distribution/providers.py +0 -27
- llama_stack/core/ui/page/distribution/resources.py +0 -48
- llama_stack/core/ui/page/distribution/scoring_functions.py +0 -18
- llama_stack/core/ui/page/distribution/shields.py +0 -19
- llama_stack/core/ui/page/evaluations/__init__.py +0 -5
- llama_stack/core/ui/page/evaluations/app_eval.py +0 -143
- llama_stack/core/ui/page/evaluations/native_eval.py +0 -253
- llama_stack/core/ui/page/playground/__init__.py +0 -5
- llama_stack/core/ui/page/playground/chat.py +0 -130
- llama_stack/core/ui/page/playground/tools.py +0 -352
- llama_stack/distributions/dell/build.yaml +0 -33
- llama_stack/distributions/meta-reference-gpu/build.yaml +0 -32
- llama_stack/distributions/nvidia/build.yaml +0 -29
- llama_stack/distributions/open-benchmark/build.yaml +0 -36
- llama_stack/distributions/postgres-demo/__init__.py +0 -7
- llama_stack/distributions/postgres-demo/build.yaml +0 -23
- llama_stack/distributions/postgres-demo/postgres_demo.py +0 -125
- llama_stack/distributions/starter/build.yaml +0 -61
- llama_stack/distributions/starter-gpu/build.yaml +0 -61
- llama_stack/distributions/watsonx/build.yaml +0 -33
- llama_stack/providers/inline/agents/meta_reference/agent_instance.py +0 -1024
- llama_stack/providers/inline/agents/meta_reference/persistence.py +0 -228
- llama_stack/providers/inline/telemetry/__init__.py +0 -5
- llama_stack/providers/inline/telemetry/meta_reference/__init__.py +0 -21
- llama_stack/providers/inline/telemetry/meta_reference/config.py +0 -47
- llama_stack/providers/inline/telemetry/meta_reference/telemetry.py +0 -252
- llama_stack/providers/remote/inference/bedrock/models.py +0 -29
- llama_stack/providers/utils/kvstore/sqlite/config.py +0 -20
- llama_stack/providers/utils/sqlstore/__init__.py +0 -5
- llama_stack/providers/utils/sqlstore/api.py +0 -128
- llama_stack/providers/utils/telemetry/__init__.py +0 -5
- llama_stack/providers/utils/telemetry/trace_protocol.py +0 -142
- llama_stack/providers/utils/telemetry/tracing.py +0 -384
- llama_stack/strong_typing/__init__.py +0 -19
- llama_stack/strong_typing/auxiliary.py +0 -228
- llama_stack/strong_typing/classdef.py +0 -440
- llama_stack/strong_typing/core.py +0 -46
- llama_stack/strong_typing/deserializer.py +0 -877
- llama_stack/strong_typing/docstring.py +0 -409
- llama_stack/strong_typing/exception.py +0 -23
- llama_stack/strong_typing/inspection.py +0 -1085
- llama_stack/strong_typing/mapping.py +0 -40
- llama_stack/strong_typing/name.py +0 -182
- llama_stack/strong_typing/schema.py +0 -792
- llama_stack/strong_typing/serialization.py +0 -97
- llama_stack/strong_typing/serializer.py +0 -500
- llama_stack/strong_typing/slots.py +0 -27
- llama_stack/strong_typing/topological.py +0 -89
- llama_stack/ui/node_modules/flatted/python/flatted.py +0 -149
- llama_stack-0.3.5.dist-info/RECORD +0 -625
- llama_stack-0.3.5.dist-info/top_level.txt +0 -1
- /llama_stack/{providers/utils → core/storage}/kvstore/config.py +0 -0
- /llama_stack/{providers/utils → core/storage}/kvstore/mongodb/__init__.py +0 -0
- /llama_stack/{providers/utils → core/storage}/kvstore/postgres/__init__.py +0 -0
- /llama_stack/{providers/utils → core/storage}/kvstore/redis/__init__.py +0 -0
- /llama_stack/{providers/utils → core/storage}/kvstore/sqlite/__init__.py +0 -0
- /llama_stack/{apis → providers/inline/file_processor}/__init__.py +0 -0
- /llama_stack/{apis/common → telemetry}/__init__.py +0 -0
- {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/WHEEL +0 -0
- {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/entry_points.txt +0 -0
- {llama_stack-0.3.5.dist-info → llama_stack-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {llama_stack/core/ui → llama_stack_api/common}/__init__.py +0 -0
- {llama_stack/strong_typing → llama_stack_api}/py.typed +0 -0
- {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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
24
|
-
self.
|
|
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.
|
|
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.
|
|
38
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
36
|
+
client = self._client()
|
|
37
|
+
await client.set(key, value)
|
|
30
38
|
if expiration:
|
|
31
|
-
await
|
|
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
|
-
|
|
43
|
+
client = self._client()
|
|
44
|
+
value = await client.get(key)
|
|
36
45
|
if value is None:
|
|
37
46
|
return None
|
|
38
|
-
await
|
|
39
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 .
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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")
|
|
183
|
+
stored_owner_principal = row.get("owner_principal")
|
|
157
184
|
|
|
158
185
|
record_id = row.get("id", "unknown")
|
|
159
|
-
|
|
160
|
-
|
|
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,
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
|
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 .
|
|
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
|
|
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
|
|
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
|
|
244
|
-
|
|
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)
|