qtype 0.1.13__py3-none-any.whl → 0.1.14__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.
- qtype/base/__init__.py +8 -2
- qtype/base/logging.py +0 -17
- qtype/base/resources.py +193 -0
- qtype/cli.py +5 -9
- qtype/commands/generate.py +6 -1
- qtype/commands/run.py +37 -10
- qtype/docs/Gallery/dataflow_pipelines.md +15 -2
- qtype/docs/Gallery/recipe_chatbot.md +103 -0
- qtype/docs/Gallery/recipe_chatbot.mermaid +62 -0
- qtype/docs/Gallery/recipe_chatbot.png +0 -0
- qtype/docs/Gallery/research_assistant.md +1 -1
- qtype/docs/How To/Command Line Usage/pass_inputs_on_the_cli.md +4 -1
- qtype/docs/How To/Data Processing/load_documents.md +74 -0
- qtype/docs/How To/Data Processing/read_sql_databases.md +2 -0
- qtype/docs/Reference/cli.md +3 -2
- qtype/docs/Reference/plugins.md +0 -4
- qtype/docs/Reference/semantic-validation-rules.md +1 -6
- qtype/docs/Tutorials/01-first-qtype-application.md +1 -1
- qtype/docs/Tutorials/03-structured-data.md +1 -1
- qtype/docs/Tutorials/04-tools-and-function-calling.md +1 -1
- qtype/examples/conversational_ai/simple_chatbot_with_auth.qtype.yaml +48 -0
- qtype/examples/data_processing/load_documents.qtype.yaml +31 -0
- qtype/examples/invoke_models/invoke_embedding_aws.qtype.yaml +45 -0
- qtype/examples/rag/recipe_chatbot.qtype.yaml +216 -0
- qtype/interpreter/auth/aws.py +94 -17
- qtype/interpreter/auth/generic.py +11 -12
- qtype/interpreter/base/secrets.py +4 -2
- qtype/interpreter/conversions.py +15 -14
- qtype/interpreter/converters.py +1 -1
- qtype/interpreter/executors/bedrock_reranker_executor.py +17 -28
- qtype/interpreter/executors/document_embedder_executor.py +1 -12
- qtype/interpreter/executors/invoke_embedding_executor.py +23 -33
- qtype/interpreter/executors/llm_inference_executor.py +2 -0
- qtype/interpreter/executors/sql_source_executor.py +6 -2
- qtype/interpreter/flow.py +11 -1
- qtype/mcp/server.py +11 -158
- qtype/semantic/visualize.py +10 -3
- {qtype-0.1.13.dist-info → qtype-0.1.14.dist-info}/METADATA +2 -2
- {qtype-0.1.13.dist-info → qtype-0.1.14.dist-info}/RECORD +42 -33
- {qtype-0.1.13.dist-info → qtype-0.1.14.dist-info}/WHEEL +0 -0
- {qtype-0.1.13.dist-info → qtype-0.1.14.dist-info}/entry_points.txt +0 -0
- {qtype-0.1.13.dist-info → qtype-0.1.14.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Query relational databases and process results row-by-row using the `SQLSource` step, which supports any database accessible via SQLAlchemy connection strings.
|
|
4
4
|
|
|
5
|
+
**Note:** SQLSource is a source step that generates data from database queries, so flows using it typically require no inputs.
|
|
6
|
+
|
|
5
7
|
### QType YAML
|
|
6
8
|
|
|
7
9
|
```yaml
|
qtype/docs/Reference/cli.md
CHANGED
|
@@ -34,14 +34,15 @@ qtype run [options] spec
|
|
|
34
34
|
#### Options
|
|
35
35
|
|
|
36
36
|
- **`-f FLOW, --flow FLOW`** - The name of the flow to run. If not specified, runs the first flow found
|
|
37
|
-
- **`-i INPUT, --input INPUT`** - JSON blob of input values for the flow (
|
|
37
|
+
- **`-i INPUT, --input INPUT`** - JSON blob of input values for the flow (optional - omit for flows with source steps that generate their own data)
|
|
38
38
|
- **`-I INPUT_FILE, --input-file INPUT_FILE`** - Path to a file (e.g., CSV, JSON, Parquet) with input data for batch processing
|
|
39
39
|
- **`-o OUTPUT, --output OUTPUT`** - Path to save output data. If input is a DataFrame, output will be saved as parquet. If single result, saved as JSON
|
|
40
40
|
- **`--progress`** - Show progress bars during flow execution
|
|
41
|
+
- **`--show-output`** - Display full output data in console (default: summary only)
|
|
41
42
|
|
|
42
43
|
#### Examples
|
|
43
44
|
|
|
44
|
-
Run a simple application:
|
|
45
|
+
Run a simple application with no inputs (e.g., flows with source steps):
|
|
45
46
|
```bash
|
|
46
47
|
qtype run app.qtype.yaml
|
|
47
48
|
```
|
qtype/docs/Reference/plugins.md
CHANGED
|
@@ -176,9 +176,4 @@ This document lists all semantic validation rules enforced by QType. These rules
|
|
|
176
176
|
## VectorSearch
|
|
177
177
|
|
|
178
178
|
- Must have exactly 1 input of type `text`
|
|
179
|
-
- Must have exactly 1 output of type `list[RAGSearchResult]`
|
|
180
|
-
|
|
181
|
-
## See Also
|
|
182
|
-
|
|
183
|
-
- [Validate QType YAML](../How%20To/Observability%20%26%20Debugging/validate_qtype_yaml.md)
|
|
184
|
-
- [CLI Reference](cli.md)
|
|
179
|
+
- Must have exactly 1 output of type `list[RAGSearchResult]`
|
|
@@ -180,7 +180,7 @@ Replace `sk-your-key-here` with your actual OpenAI API key.
|
|
|
180
180
|
Run your application:
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
-
qtype run -i '{"question":"What is 2+2?"}' 01_hello_world.qtype.yaml
|
|
183
|
+
qtype run -i '{"question":"What is 2+2?"}' --show-output 01_hello_world.qtype.yaml
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
**What you should see:**
|
|
@@ -316,7 +316,7 @@ Should pass ✅
|
|
|
316
316
|
### Run It!
|
|
317
317
|
|
|
318
318
|
```bash
|
|
319
|
-
qtype run -i '{"review_text":"These headphones are amazing! Great sound quality and super comfortable. Battery lasts all day."}' 03_structured_data.qtype.yaml
|
|
319
|
+
qtype run -i '{"review_text":"These headphones are amazing! Great sound quality and super comfortable. Battery lasts all day."}' --show-output 03_structured_data.qtype.yaml
|
|
320
320
|
```
|
|
321
321
|
|
|
322
322
|
**Expected output:**
|
|
@@ -305,7 +305,7 @@ Should pass ✅
|
|
|
305
305
|
### Run the Application
|
|
306
306
|
|
|
307
307
|
```bash
|
|
308
|
-
qtype run -i '{"days_until_due": 3}' 04_tools_and_function_calling.qtype.yaml
|
|
308
|
+
qtype run -i '{"days_until_due": 3}' --show-output 04_tools_and_function_calling.qtype.yaml
|
|
309
309
|
```
|
|
310
310
|
|
|
311
311
|
**Expected output:**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
id: simple_chatbot
|
|
2
|
+
description: A friendly chatbot with conversation memory using AWS Bedrock
|
|
3
|
+
|
|
4
|
+
# AWS Authentication for Bedrock
|
|
5
|
+
auths:
|
|
6
|
+
- type: aws
|
|
7
|
+
id: aws_auth
|
|
8
|
+
profile_name: ${AWS_PROFILE}
|
|
9
|
+
|
|
10
|
+
models:
|
|
11
|
+
- type: Model
|
|
12
|
+
id: nova_lite
|
|
13
|
+
provider: aws-bedrock
|
|
14
|
+
model_id: amazon.nova-lite-v1:0
|
|
15
|
+
inference_params:
|
|
16
|
+
temperature: 0.7
|
|
17
|
+
max_tokens: 512
|
|
18
|
+
auth: aws_auth
|
|
19
|
+
memories:
|
|
20
|
+
- id: conversation_memory
|
|
21
|
+
token_limit: 10000
|
|
22
|
+
flows:
|
|
23
|
+
- type: Flow
|
|
24
|
+
id: chat_flow
|
|
25
|
+
interface:
|
|
26
|
+
type: Conversational
|
|
27
|
+
variables:
|
|
28
|
+
- id: user_message
|
|
29
|
+
type: ChatMessage
|
|
30
|
+
- id: assistant_response
|
|
31
|
+
type: ChatMessage
|
|
32
|
+
inputs:
|
|
33
|
+
- user_message
|
|
34
|
+
outputs:
|
|
35
|
+
- assistant_response
|
|
36
|
+
steps:
|
|
37
|
+
- id: generate_response
|
|
38
|
+
type: LLMInference
|
|
39
|
+
model: nova_lite
|
|
40
|
+
system_message: |
|
|
41
|
+
You are a friendly and helpful chatbot. You have a warm, conversational
|
|
42
|
+
tone and enjoy helping users with their questions. You remember context
|
|
43
|
+
from previous messages in the conversation.
|
|
44
|
+
memory: conversation_memory
|
|
45
|
+
inputs:
|
|
46
|
+
- user_message
|
|
47
|
+
outputs:
|
|
48
|
+
- assistant_response
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Load all markdown files from docs directory using DocumentSource
|
|
2
|
+
#
|
|
3
|
+
# This example demonstrates using DocumentSource with SimpleDirectoryReader
|
|
4
|
+
# to load documents from a local directory with file filtering.
|
|
5
|
+
|
|
6
|
+
id: load_documents_example
|
|
7
|
+
description: Load markdown files from docs directory
|
|
8
|
+
|
|
9
|
+
flows:
|
|
10
|
+
- type: Flow
|
|
11
|
+
id: load_md_files
|
|
12
|
+
description: Load all markdown files from docs directory
|
|
13
|
+
|
|
14
|
+
variables:
|
|
15
|
+
- id: document
|
|
16
|
+
type: RAGDocument
|
|
17
|
+
|
|
18
|
+
inputs: []
|
|
19
|
+
outputs:
|
|
20
|
+
- document
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- type: DocumentSource
|
|
24
|
+
id: md_docs
|
|
25
|
+
reader_module: llama_index.core.SimpleDirectoryReader
|
|
26
|
+
args:
|
|
27
|
+
input_dir: docs
|
|
28
|
+
required_exts: [".md"]
|
|
29
|
+
recursive: true
|
|
30
|
+
outputs:
|
|
31
|
+
- document
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
id: invoke_embedding_test
|
|
2
|
+
description: Test InvokeEmbedding step with AWS Bedrock embeddings
|
|
3
|
+
|
|
4
|
+
# AWS Authentication for Bedrock
|
|
5
|
+
auths:
|
|
6
|
+
- type: aws
|
|
7
|
+
id: aws_auth
|
|
8
|
+
profile_name: ${AWS_PROFILE}
|
|
9
|
+
|
|
10
|
+
models:
|
|
11
|
+
# Embedding model using AWS Bedrock
|
|
12
|
+
- type: EmbeddingModel
|
|
13
|
+
id: titan_embed
|
|
14
|
+
provider: aws-bedrock
|
|
15
|
+
model_id: amazon.titan-embed-text-v2:0
|
|
16
|
+
dimensions: 1024
|
|
17
|
+
auth: aws_auth
|
|
18
|
+
|
|
19
|
+
flows:
|
|
20
|
+
- type: Flow
|
|
21
|
+
id: test_invoke_embedding
|
|
22
|
+
description: Generate embeddings for text using AWS Bedrock
|
|
23
|
+
|
|
24
|
+
variables:
|
|
25
|
+
- id: input_text
|
|
26
|
+
type: text
|
|
27
|
+
ui:
|
|
28
|
+
widget: textarea
|
|
29
|
+
- id: embedding
|
|
30
|
+
type: Embedding
|
|
31
|
+
|
|
32
|
+
inputs:
|
|
33
|
+
- input_text
|
|
34
|
+
|
|
35
|
+
outputs:
|
|
36
|
+
- embedding
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- id: generate_embedding
|
|
40
|
+
type: InvokeEmbedding
|
|
41
|
+
model: titan_embed
|
|
42
|
+
inputs:
|
|
43
|
+
- input_text
|
|
44
|
+
outputs:
|
|
45
|
+
- embedding
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
id: recipe_rag_chatbot
|
|
2
|
+
description: |
|
|
3
|
+
RAG chatbot for the Chowdown recipe collection from GitHub.
|
|
4
|
+
|
|
5
|
+
This application provides two flows:
|
|
6
|
+
|
|
7
|
+
1. recipe_chat: Conversational chatbot that answers questions about recipes
|
|
8
|
+
- Uses RAG to find relevant recipes based on user questions
|
|
9
|
+
- Maintains conversation history with memory
|
|
10
|
+
- Provides cooking advice, recipe recommendations, and ingredient information
|
|
11
|
+
|
|
12
|
+
2. recipe_ingestion: Ingests recipe markdown files into vector database
|
|
13
|
+
- Clones/fetches recipes from GitHub (clarklab/chowdown)
|
|
14
|
+
- Splits recipe documents into searchable chunks
|
|
15
|
+
- Generates embeddings using AWS Bedrock Titan
|
|
16
|
+
- Stores in Qdrant vector database for fast similarity search
|
|
17
|
+
|
|
18
|
+
Prerequisites:
|
|
19
|
+
- AWS credentials configured (AWS_PROFILE environment variable)
|
|
20
|
+
- Qdrant running locally on port 6333 (or update args for Qdrant Cloud)
|
|
21
|
+
- Clone the recipe repo: git clone https://github.com/clarklab/chowdown.git
|
|
22
|
+
|
|
23
|
+
To ingest recipes:
|
|
24
|
+
qtype run recipe_chatbot.qtype.yaml --flow recipe_ingestion
|
|
25
|
+
|
|
26
|
+
To start chatbot:
|
|
27
|
+
qtype serve recipe_chatbot.qtype.yaml --flow recipe_chat
|
|
28
|
+
|
|
29
|
+
# AWS Authentication for Bedrock
|
|
30
|
+
auths:
|
|
31
|
+
- type: aws
|
|
32
|
+
id: aws_auth
|
|
33
|
+
profile_name: ${AWS_PROFILE}
|
|
34
|
+
|
|
35
|
+
# Models
|
|
36
|
+
models:
|
|
37
|
+
# Embedding model for vectorizing recipes and queries
|
|
38
|
+
- type: EmbeddingModel
|
|
39
|
+
id: titan_embed
|
|
40
|
+
provider: aws-bedrock
|
|
41
|
+
model_id: amazon.titan-embed-text-v2:0
|
|
42
|
+
dimensions: 1024
|
|
43
|
+
auth: aws_auth
|
|
44
|
+
|
|
45
|
+
# Chat model for conversational responses
|
|
46
|
+
- type: Model
|
|
47
|
+
id: claude_sonnet
|
|
48
|
+
provider: aws-bedrock
|
|
49
|
+
model_id: us.anthropic.claude-3-5-sonnet-20241022-v2:0
|
|
50
|
+
inference_params:
|
|
51
|
+
temperature: 0.7
|
|
52
|
+
max_tokens: 4096
|
|
53
|
+
auth: aws_auth
|
|
54
|
+
|
|
55
|
+
# Vector index for recipe embeddings
|
|
56
|
+
indexes:
|
|
57
|
+
- type: VectorIndex
|
|
58
|
+
module: llama_index.vector_stores.qdrant.QdrantVectorStore
|
|
59
|
+
id: recipe_index
|
|
60
|
+
name: chowdown_recipes
|
|
61
|
+
embedding_model: titan_embed
|
|
62
|
+
args:
|
|
63
|
+
collection_name: chowdown_recipes
|
|
64
|
+
url: http://localhost:6333
|
|
65
|
+
api_key: "" # Empty for local Qdrant
|
|
66
|
+
|
|
67
|
+
# Memory for maintaining conversation context
|
|
68
|
+
memories:
|
|
69
|
+
- id: recipe_chat_memory
|
|
70
|
+
token_limit: 10000
|
|
71
|
+
chat_history_token_ratio: 0.7
|
|
72
|
+
|
|
73
|
+
# Flows
|
|
74
|
+
flows:
|
|
75
|
+
# Conversational chatbot flow
|
|
76
|
+
- type: Flow
|
|
77
|
+
id: recipe_chat
|
|
78
|
+
description: Chat with the recipe collection using RAG
|
|
79
|
+
|
|
80
|
+
interface:
|
|
81
|
+
type: Conversational
|
|
82
|
+
|
|
83
|
+
variables:
|
|
84
|
+
- id: user_message
|
|
85
|
+
type: ChatMessage
|
|
86
|
+
- id: user_question
|
|
87
|
+
type: text
|
|
88
|
+
- id: search_results
|
|
89
|
+
type: list[RAGSearchResult]
|
|
90
|
+
- id: context_prompt
|
|
91
|
+
type: text
|
|
92
|
+
- id: assistant_response
|
|
93
|
+
type: ChatMessage
|
|
94
|
+
|
|
95
|
+
inputs:
|
|
96
|
+
- user_message
|
|
97
|
+
|
|
98
|
+
outputs:
|
|
99
|
+
- assistant_response
|
|
100
|
+
|
|
101
|
+
steps:
|
|
102
|
+
# Extract text from user's chat message
|
|
103
|
+
- id: extract_question
|
|
104
|
+
type: FieldExtractor
|
|
105
|
+
json_path: "$.blocks[?(@.type == 'text')].content"
|
|
106
|
+
inputs:
|
|
107
|
+
- user_message
|
|
108
|
+
outputs:
|
|
109
|
+
- user_question
|
|
110
|
+
|
|
111
|
+
# Search recipe vector index for relevant recipes
|
|
112
|
+
- id: search_recipes
|
|
113
|
+
type: VectorSearch
|
|
114
|
+
index: recipe_index
|
|
115
|
+
default_top_k: 5
|
|
116
|
+
inputs:
|
|
117
|
+
- user_question
|
|
118
|
+
outputs:
|
|
119
|
+
- search_results
|
|
120
|
+
|
|
121
|
+
# Build prompt with recipe context
|
|
122
|
+
- id: build_context_prompt
|
|
123
|
+
type: PromptTemplate
|
|
124
|
+
template: |
|
|
125
|
+
You are a helpful cooking assistant with access to a collection of recipes from Chowdown.
|
|
126
|
+
|
|
127
|
+
Here are the most relevant recipes based on the user's question:
|
|
128
|
+
|
|
129
|
+
{search_results}
|
|
130
|
+
|
|
131
|
+
User question: {user_question}
|
|
132
|
+
|
|
133
|
+
Please provide a helpful answer based on the recipes above. If you're suggesting a recipe,
|
|
134
|
+
include key ingredients and brief cooking instructions. If the recipes don't contain
|
|
135
|
+
relevant information, politely say so and offer general cooking advice if appropriate.
|
|
136
|
+
inputs:
|
|
137
|
+
- search_results
|
|
138
|
+
- user_question
|
|
139
|
+
outputs:
|
|
140
|
+
- context_prompt
|
|
141
|
+
|
|
142
|
+
# Generate conversational response using LLM with memory
|
|
143
|
+
- id: generate_response
|
|
144
|
+
type: LLMInference
|
|
145
|
+
model: claude_sonnet
|
|
146
|
+
memory: recipe_chat_memory
|
|
147
|
+
system_message: |
|
|
148
|
+
You are a friendly and knowledgeable cooking assistant. You help users find recipes,
|
|
149
|
+
answer questions about ingredients, suggest substitutions, and provide cooking tips.
|
|
150
|
+
Base your answers on the provided recipe context, but feel free to add general
|
|
151
|
+
cooking knowledge when helpful. Be conversational and enthusiastic about food!
|
|
152
|
+
inputs:
|
|
153
|
+
- context_prompt
|
|
154
|
+
outputs:
|
|
155
|
+
- assistant_response
|
|
156
|
+
|
|
157
|
+
# Recipe ingestion flow
|
|
158
|
+
- type: Flow
|
|
159
|
+
id: recipe_ingestion
|
|
160
|
+
description: Load recipes from local GitHub clone, chunk, embed, and index
|
|
161
|
+
|
|
162
|
+
variables:
|
|
163
|
+
- id: recipe_document
|
|
164
|
+
type: RAGDocument
|
|
165
|
+
- id: recipe_chunk
|
|
166
|
+
type: RAGChunk
|
|
167
|
+
- id: embedded_chunk
|
|
168
|
+
type: RAGChunk
|
|
169
|
+
|
|
170
|
+
outputs:
|
|
171
|
+
- embedded_chunk
|
|
172
|
+
|
|
173
|
+
steps:
|
|
174
|
+
# Load recipe markdown files from local clone
|
|
175
|
+
- id: load_recipes
|
|
176
|
+
type: DocumentSource
|
|
177
|
+
reader_module: llama_index.core.SimpleDirectoryReader
|
|
178
|
+
args:
|
|
179
|
+
input_dir: "./chowdown/_recipes"
|
|
180
|
+
recursive: false
|
|
181
|
+
required_exts: [".md"]
|
|
182
|
+
outputs:
|
|
183
|
+
- recipe_document
|
|
184
|
+
|
|
185
|
+
# Split recipes into chunks for better retrieval
|
|
186
|
+
- id: split_recipes
|
|
187
|
+
type: DocumentSplitter
|
|
188
|
+
splitter_name: "SentenceSplitter"
|
|
189
|
+
chunk_size: 512
|
|
190
|
+
chunk_overlap: 50
|
|
191
|
+
inputs:
|
|
192
|
+
- recipe_document
|
|
193
|
+
outputs:
|
|
194
|
+
- recipe_chunk
|
|
195
|
+
|
|
196
|
+
# Generate embeddings for each chunk
|
|
197
|
+
- id: embed_chunks
|
|
198
|
+
type: DocumentEmbedder
|
|
199
|
+
model: titan_embed
|
|
200
|
+
concurrency_config:
|
|
201
|
+
num_workers: 5
|
|
202
|
+
inputs:
|
|
203
|
+
- recipe_chunk
|
|
204
|
+
outputs:
|
|
205
|
+
- embedded_chunk
|
|
206
|
+
|
|
207
|
+
# Store embedded chunks in Qdrant
|
|
208
|
+
- id: index_recipes
|
|
209
|
+
type: IndexUpsert
|
|
210
|
+
index: recipe_index
|
|
211
|
+
batch_config:
|
|
212
|
+
batch_size: 25
|
|
213
|
+
inputs:
|
|
214
|
+
- embedded_chunk
|
|
215
|
+
outputs:
|
|
216
|
+
- embedded_chunk
|
qtype/interpreter/auth/aws.py
CHANGED
|
@@ -8,6 +8,7 @@ AWSAuthProvider configuration from the semantic model.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from contextlib import contextmanager
|
|
11
|
+
from dataclasses import asdict, dataclass
|
|
11
12
|
from typing import Any, Generator
|
|
12
13
|
|
|
13
14
|
import boto3 # type: ignore[import-untyped]
|
|
@@ -27,6 +28,30 @@ class AWSAuthenticationError(Exception):
|
|
|
27
28
|
pass
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
@dataclass
|
|
32
|
+
class AWSCredentials:
|
|
33
|
+
"""Resolved AWS credentials for creating boto3/aioboto3 clients.
|
|
34
|
+
|
|
35
|
+
This dataclass holds the resolved AWS credentials that can be passed
|
|
36
|
+
directly to both boto3 and aioboto3 constructors, ensuring async
|
|
37
|
+
operations work correctly without botocore session issues.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
aws_access_key_id: str | None = None
|
|
41
|
+
aws_secret_access_key: str | None = None
|
|
42
|
+
aws_session_token: str | None = None
|
|
43
|
+
region_name: str | None = None
|
|
44
|
+
profile_name: str | None = None
|
|
45
|
+
|
|
46
|
+
def as_kwargs(self) -> dict[str, Any]:
|
|
47
|
+
"""Return non-None credentials as kwargs dict.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dictionary of credential parameters with None values filtered out.
|
|
51
|
+
"""
|
|
52
|
+
return {k: v for k, v in asdict(self).items() if v is not None}
|
|
53
|
+
|
|
54
|
+
|
|
30
55
|
def _is_session_valid(session: boto3.Session) -> bool:
|
|
31
56
|
"""
|
|
32
57
|
Check if a boto3 session is still valid by testing credential access.
|
|
@@ -60,14 +85,18 @@ def _is_session_valid(session: boto3.Session) -> bool:
|
|
|
60
85
|
def aws(
|
|
61
86
|
aws_provider: AWSAuthProvider,
|
|
62
87
|
secret_manager: SecretManagerBase,
|
|
63
|
-
) -> Generator[
|
|
88
|
+
) -> Generator[AWSCredentials, None, None]:
|
|
64
89
|
"""
|
|
65
|
-
|
|
90
|
+
Resolve AWS credentials from provider configuration.
|
|
91
|
+
|
|
92
|
+
This context manager resolves AWS credentials based on the authentication
|
|
93
|
+
method specified in the AWSAuthProvider and returns them as an
|
|
94
|
+
AWSCredentials dataclass. The credentials can be passed directly to both
|
|
95
|
+
boto3 and aioboto3 constructors, ensuring async operations work correctly.
|
|
66
96
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
via the AUTH_CACHE_MAX_SIZE environment variable (default: 128).
|
|
97
|
+
Sessions are cached using an LRU cache to avoid recreating them
|
|
98
|
+
unnecessarily. The cache size can be configured via the AUTH_CACHE_MAX_SIZE
|
|
99
|
+
environment variable (default: 128).
|
|
71
100
|
|
|
72
101
|
It supports:
|
|
73
102
|
- Direct credentials (access key + secret key + optional session token)
|
|
@@ -81,13 +110,17 @@ def aws(
|
|
|
81
110
|
- Invalid or expired sessions are evicted and recreated
|
|
82
111
|
|
|
83
112
|
Args:
|
|
84
|
-
aws_provider: AWSAuthProvider instance containing authentication
|
|
113
|
+
aws_provider: AWSAuthProvider instance containing authentication
|
|
114
|
+
configuration
|
|
115
|
+
secret_manager: SecretManagerBase for resolving SecretReferences
|
|
85
116
|
|
|
86
117
|
Yields:
|
|
87
|
-
|
|
118
|
+
AWSCredentials: Resolved credentials ready for creating AWS service
|
|
119
|
+
clients
|
|
88
120
|
|
|
89
121
|
Raises:
|
|
90
|
-
AWSAuthenticationError: When authentication fails or configuration is
|
|
122
|
+
AWSAuthenticationError: When authentication fails or configuration is
|
|
123
|
+
invalid
|
|
91
124
|
|
|
92
125
|
Example:
|
|
93
126
|
```python
|
|
@@ -102,9 +135,13 @@ def aws(
|
|
|
102
135
|
region="us-east-1"
|
|
103
136
|
)
|
|
104
137
|
|
|
105
|
-
with aws(aws_auth) as
|
|
106
|
-
|
|
107
|
-
|
|
138
|
+
with aws(aws_auth, secret_manager) as creds:
|
|
139
|
+
# Use with boto3
|
|
140
|
+
client = boto3.client("s3", **creds.as_kwargs())
|
|
141
|
+
|
|
142
|
+
# Or with llama-index Bedrock
|
|
143
|
+
from llama_index.llms.bedrock_converse import BedrockConverse
|
|
144
|
+
llm = BedrockConverse(**creds.as_kwargs(), model="...")
|
|
108
145
|
```
|
|
109
146
|
"""
|
|
110
147
|
try:
|
|
@@ -112,8 +149,8 @@ def aws(
|
|
|
112
149
|
cached_session = get_cached_auth(aws_provider)
|
|
113
150
|
|
|
114
151
|
if cached_session is not None and _is_session_valid(cached_session):
|
|
115
|
-
# Cache hit with valid session
|
|
116
|
-
yield cached_session
|
|
152
|
+
# Cache hit with valid session - extract credentials from it
|
|
153
|
+
yield _extract_credentials(cached_session, aws_provider)
|
|
117
154
|
return
|
|
118
155
|
|
|
119
156
|
# Cache miss or invalid session - create new session
|
|
@@ -123,13 +160,15 @@ def aws(
|
|
|
123
160
|
credentials = session.get_credentials()
|
|
124
161
|
if credentials is None:
|
|
125
162
|
raise AWSAuthenticationError(
|
|
126
|
-
f"Failed to obtain AWS credentials for provider
|
|
163
|
+
f"Failed to obtain AWS credentials for provider "
|
|
164
|
+
f"'{aws_provider.id}'"
|
|
127
165
|
)
|
|
128
166
|
|
|
129
167
|
# Cache the valid session using provider object as key
|
|
130
168
|
cache_auth(aws_provider, session)
|
|
131
169
|
|
|
132
|
-
yield
|
|
170
|
+
# Extract and yield credentials
|
|
171
|
+
yield _extract_credentials(session, aws_provider)
|
|
133
172
|
|
|
134
173
|
except (ClientError, NoCredentialsError) as e:
|
|
135
174
|
raise AWSAuthenticationError(
|
|
@@ -137,10 +176,48 @@ def aws(
|
|
|
137
176
|
) from e
|
|
138
177
|
except Exception as e:
|
|
139
178
|
raise AWSAuthenticationError(
|
|
140
|
-
f"Unexpected error during AWS authentication for provider
|
|
179
|
+
f"Unexpected error during AWS authentication for provider "
|
|
180
|
+
f"'{aws_provider.id}': {e}"
|
|
141
181
|
) from e
|
|
142
182
|
|
|
143
183
|
|
|
184
|
+
def _extract_credentials(
|
|
185
|
+
session: boto3.Session, aws_provider: AWSAuthProvider
|
|
186
|
+
) -> AWSCredentials:
|
|
187
|
+
"""
|
|
188
|
+
Extract credentials from a boto3 Session.
|
|
189
|
+
|
|
190
|
+
This function extracts the actual credential values from a boto3.Session,
|
|
191
|
+
including temporary credentials from role assumption. This allows the
|
|
192
|
+
credentials to be passed directly to boto3/aioboto3 constructors.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
session: The boto3 session to extract credentials from
|
|
196
|
+
aws_provider: AWSAuthProvider for region/profile information
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
AWSCredentials with the extracted credential values
|
|
200
|
+
"""
|
|
201
|
+
credentials = session.get_credentials()
|
|
202
|
+
|
|
203
|
+
if credentials is None:
|
|
204
|
+
# No explicit credentials - use profile or environment
|
|
205
|
+
return AWSCredentials(
|
|
206
|
+
profile_name=aws_provider.profile_name,
|
|
207
|
+
region_name=session.region_name or aws_provider.region,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Extract frozen credentials (works for both static and temporary)
|
|
211
|
+
frozen = credentials.get_frozen_credentials()
|
|
212
|
+
|
|
213
|
+
return AWSCredentials(
|
|
214
|
+
aws_access_key_id=frozen.access_key,
|
|
215
|
+
aws_secret_access_key=frozen.secret_key,
|
|
216
|
+
aws_session_token=frozen.token,
|
|
217
|
+
region_name=session.region_name or aws_provider.region,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
144
221
|
def _create_session(
|
|
145
222
|
aws_provider: AWSAuthProvider,
|
|
146
223
|
secret_manager: SecretManagerBase,
|
|
@@ -44,9 +44,7 @@ from __future__ import annotations
|
|
|
44
44
|
from contextlib import contextmanager
|
|
45
45
|
from typing import Generator
|
|
46
46
|
|
|
47
|
-
import
|
|
48
|
-
|
|
49
|
-
from qtype.interpreter.auth.aws import aws
|
|
47
|
+
from qtype.interpreter.auth.aws import AWSCredentials, aws
|
|
50
48
|
from qtype.interpreter.base.secrets import SecretManagerBase
|
|
51
49
|
from qtype.semantic.model import (
|
|
52
50
|
APIKeyAuthProvider,
|
|
@@ -111,13 +109,13 @@ def resolve_provider_secrets(
|
|
|
111
109
|
def auth(
|
|
112
110
|
auth_provider: AuthorizationProvider,
|
|
113
111
|
secret_manager: SecretManagerBase,
|
|
114
|
-
) -> Generator[
|
|
112
|
+
) -> Generator[AWSCredentials | AuthorizationProvider, None, None]:
|
|
115
113
|
"""
|
|
116
|
-
Create
|
|
114
|
+
Create appropriate credentials or provider instance based on auth provider type.
|
|
117
115
|
|
|
118
116
|
This context manager dispatches to the appropriate authentication handler based
|
|
119
117
|
on the type of AuthorizationProvider:
|
|
120
|
-
- AWSAuthProvider: Returns
|
|
118
|
+
- AWSAuthProvider: Returns AWSCredentials with resolved credentials
|
|
121
119
|
- APIKeyAuthProvider: Returns the provider instance (contains the API key)
|
|
122
120
|
|
|
123
121
|
Args:
|
|
@@ -125,7 +123,7 @@ def auth(
|
|
|
125
123
|
secret_manager: Secret manager for resolving SecretReferences
|
|
126
124
|
|
|
127
125
|
Yields:
|
|
128
|
-
|
|
126
|
+
AWSCredentials | AuthorizationProvider: The appropriate credentials or provider instance
|
|
129
127
|
|
|
130
128
|
Raises:
|
|
131
129
|
UnsupportedAuthProviderError: When an unsupported provider type is used
|
|
@@ -135,7 +133,7 @@ def auth(
|
|
|
135
133
|
from qtype.semantic.model import AWSAuthProvider, APIKeyAuthProvider
|
|
136
134
|
from qtype.interpreter.auth.generic import auth
|
|
137
135
|
|
|
138
|
-
# AWS provider - returns
|
|
136
|
+
# AWS provider - returns AWSCredentials
|
|
139
137
|
aws_auth = AWSAuthProvider(
|
|
140
138
|
id="my-aws-auth",
|
|
141
139
|
type="aws",
|
|
@@ -144,8 +142,9 @@ def auth(
|
|
|
144
142
|
region="us-east-1"
|
|
145
143
|
)
|
|
146
144
|
|
|
147
|
-
with auth(aws_auth) as
|
|
148
|
-
|
|
145
|
+
with auth(aws_auth, secret_manager) as creds:
|
|
146
|
+
import boto3
|
|
147
|
+
s3_client = boto3.client("s3", **creds.as_kwargs())
|
|
149
148
|
|
|
150
149
|
# API Key provider - returns the provider itself
|
|
151
150
|
api_auth = APIKeyAuthProvider(
|
|
@@ -161,8 +160,8 @@ def auth(
|
|
|
161
160
|
"""
|
|
162
161
|
if isinstance(auth_provider, AWSAuthProvider):
|
|
163
162
|
# Use AWS-specific context manager
|
|
164
|
-
with aws(auth_provider, secret_manager) as
|
|
165
|
-
yield
|
|
163
|
+
with aws(auth_provider, secret_manager) as creds:
|
|
164
|
+
yield creds
|
|
166
165
|
|
|
167
166
|
elif isinstance(auth_provider, (APIKeyAuthProvider, OAuth2AuthProvider)):
|
|
168
167
|
# For non-AWS providers, resolve secrets and yield modified copy
|
|
@@ -255,10 +255,12 @@ class AWSSecretManager(SecretManagerBase):
|
|
|
255
255
|
json.JSONDecodeError: If secret is not valid JSON when key
|
|
256
256
|
is specified
|
|
257
257
|
"""
|
|
258
|
+
import boto3
|
|
259
|
+
|
|
258
260
|
from qtype.interpreter.auth.aws import aws
|
|
259
261
|
|
|
260
|
-
with aws(self.config.auth) as
|
|
261
|
-
client =
|
|
262
|
+
with aws(self.config.auth) as creds: # type: ignore
|
|
263
|
+
client = boto3.client("secretsmanager", **creds.as_kwargs())
|
|
262
264
|
response = client.get_secret_value(SecretId=secret_ref.secret_name)
|
|
263
265
|
|
|
264
266
|
if "SecretString" not in response:
|