kaxil commented on code in PR #72002:
URL: https://github.com/apache/airflow/pull/72002#discussion_r4004747745


##########
providers/common/ai/src/airflow/providers/common/ai/hooks/llamaindex.py:
##########
@@ -172,14 +181,42 @@ def get_embedding_model(self) -> BaseEmbedding:
         except ImportError as e:
             raise AirflowOptionalProviderFeatureException(e)
 
+        reserved_keys = sorted(self.embedding_kwargs.keys() & {"model", 
"model_name"})
+        if reserved_keys:
+            raise ValueError(
+                f"embedding_kwargs must not contain reserved keys 
{reserved_keys}; use embed_model instead"
+            )
+        additional_kwargs = self.embedding_kwargs.get("additional_kwargs")
+        if isinstance(additional_kwargs, dict):
+            reserved_request_keys = sorted(additional_kwargs.keys() & 
{"input", "model", "model_name"})
+            if reserved_request_keys:
+                raise ValueError(
+                    "embedding_kwargs['additional_kwargs'] must not contain 
reserved keys "
+                    f"{reserved_request_keys}; model identity and input are 
managed by the hook"
+                )
+
         conn = self.get_connection(self.embed_conn_id)
         model_id = self._resolve_model(
             conn.extra_dejson,
             constructor_value=self.embed_model,
             extra_key="embed_model",
             kind="embedding",
         )
-        return OpenAIEmbedding(model=model_id, **self._connection_kwargs(conn))
+        connection_kwargs = self._connection_kwargs(conn)
+        overridden_keys = sorted(self.embedding_kwargs.keys() & 
connection_kwargs.keys())

Review Comment:
   This warning is keyed on `self.embedding_kwargs.keys() & 
connection_kwargs.keys()`, and `connection_kwargs` only ever holds 
`api_key`/`api_base` -- so the one key that actually takes the connection's 
credential away never intersects it. Measured at the declared 
`llama-index-embeddings-openai==0.6.0` floor behind a mock transport, with the 
connection password as `sk-CONNECTION`: `embedding_kwargs={"default_headers": 
{"Authorization": "Bearer sk-OTHER"}}` puts `Authorization: Bearer sk-OTHER` on 
the wire, and all three guards stay quiet. `default_headers` is a declared 
field, so the unsupported-key warning doesn't fire; it isn't 
`api_key`/`api_base`, so this warning doesn't fire; it isn't 
`model`/`model_name`, so the raise doesn't fire. The control matters for the 
fix: `{"X-Tenant": "team-a"}` through the same key leaves `Authorization` 
untouched, which is the legitimate gateway-routing use, so the key can't simply 
be reserved.
   
   Mechanism: `_get_credential_kwargs` hands `default_headers` to the `OpenAI` 
client, and the client composes its headers as `{..., **self.auth_headers, 
**self._custom_headers}` -- caller headers merge last, over the bearer token. 
`langchain.py:189` is the identical warning with the identical blind spot, and 
that hook has no unsupported-key warning at all, so nothing catches it there 
either. Both are new here: on `origin/main` only `_connection_kwargs` could 
reach either constructor.
   
   It isn't the only spelling. `embedding_kwargs={"additional_kwargs": 
{"extra_body": {"model": "Qwen/Qwen3-Embedding-0.6B"}}}` succeeds, returns a 
clean `list[float]`, puts the substituted model on the wire, and leaves 
`embed_model` reading `text-embedding-3-small` in the rendered template fields 
and the UI -- while the reserved spelling `additional_kwargs={"model": ...}` 
raises loudly. `embeddings.create` accepts `extra_body` and merges it over the 
named arguments (`make_request_options` maps it to `options["extra_json"]`, 
then `_merge_mappings(json_data, options.extra_json)`, whose docstring reads 
"In cases with duplicate keys the second mapping takes precedence"), and 
`additional_kwargs={"extra_headers": {"Authorization": ...}}` does the same to 
the credential. All of it behaves identically on `openai` 1.1.0, the declared 
floor, and on current 3.x, so none of it is a recent SDK change.
   
   Three channels, no shared name, and the benign uses sit in the same keys as 
the harmful ones (`{"X-Tenant": ...}`, `{"truncate_prompt_tokens": 512}`, and 
`extra_query`/`timeout`, which were measured additive-only), so no list of 
names separates them. That's the case for validating contents instead: warning 
when a headers mapping carries an `Authorization` key, naming 
`self.embed_conn_id` which is already in scope on this line, would cover the 
credential case. The alternative is to drop the denylist and say plainly in the 
docs that `embedding_kwargs` is forwarded to the constructor and can override 
anything the hook sets. Three docstrings and four rst pages currently promise 
the opposite, and that's the part worth fixing either way. Two small things 
riding along with whichever you pick: this warning doesn't say which connection 
lost, and `self.embed_conn_id` is right there; and the two operator pages carry 
the reserved-key list without the precedence rule the hook pages state, so t
 hat row currently reads as if the reserved names were the whole policy.



##########
providers/common/ai/docs/operators/llamaindex_embedding.rst:
##########
@@ -86,22 +86,38 @@ Parameters
        binding ``loader.output`` resolves to the native list before
        execute.
    * - ``embed_model``
-     - String model name OR pre-built ``BaseEmbedding`` instance.
+     - String model name OR pre-built ``BaseEmbedding`` instance. Templated.
    * - ``llm_conn_id``
      - Airflow connection ID used when ``embed_model`` is a string. Falls
        back to ``LlamaIndexHook.default_conn_name`` (``llamaindex_default``)
-       when ``None``.
+       when ``None``. Templated.
    * - ``embed_conn_id``
      - Optional separate connection ID for the embedding provider. Falls
-       back to ``llm_conn_id`` when ``None``.
+       back to ``llm_conn_id`` when ``None``. Templated.
+   * - ``embedding_kwargs``
+     - Additional keyword arguments passed to the embedding model constructor
+       when ``embed_model`` is a string or omitted, for example
+       ``{"dimensions": 128}``. Supports templating; set the Dag's
+       ``render_template_as_native_obj=True`` when templating typed values such
+       as ``dimensions`` so they remain integers instead of strings. When

Review Comment:
   Since `embedding_kwargs` is a template field, this row can point at the 
cheaper route the same table already documents three rows up: `documents` says 
"binding ``loader.output`` resolves to the native list before execute", and 
`embedding_kwargs` gets that for free, because `render_template` returns 
`resolve(context)` for anything carrying a `.resolve` (`templater.py:264-265`). 
An upstream `@task` returning `{"dimensions": 128}` stays a dict with no flag 
set anywhere.
   
   The Dag flag is a blunter instrument than the sentence suggests: 
`render_template` sends every `str` template field through 
`jinja_env.from_string` with no "contains `{{`" guard (`templater.py:254-258`), 
and the environment is built once per Dag with `native=use_native` 
(`dag.py:852-854`), so flipping it changes the rendered type of every string 
template field of every task in that Dag, not just this one. The per-operator 
`render_template_as_native_obj` would scope it, but it isn't available here: it 
first appears in task-sdk 1.2.0 (Airflow 3.2.0) and this provider floors at 
`apache-airflow>=3.0.0`, which is what makes the XComArg route the one that 
works on every supported version. Same sentence at 
`llamaindex_retrieval.rst:94`. This is me correcting advice I gave you in round 
1, so treat it as a suggestion rather than a defect.



##########
providers/common/ai/tests/unit/common/ai/operators/test_llamaindex_embedding.py:
##########
@@ -132,11 +135,15 @@ def test_byo_embed_model_bypasses_hook(self, _li):
             task_id="test",
             documents=[{"text": "doc"}],
             embed_model=byo,
+            embedding_kwargs={"dimensions": 128},
         )
         result = op.execute(context=MagicMock())
 
         byo.get_text_embedding_batch.assert_called_once()
         assert result["chunks"][0]["vector"] == [0.5]
+        assert (
+            "embedding_kwargs is ignored when embed_model is a pre-built 
embedding model" in caplog.messages

Review Comment:
   This is the only BYO construction in the file that passes 
`embedding_kwargs`, and it asserts the warning is present but nothing asserts 
when it should be absent -- the three other BYO constructions (`:171`, `:192`, 
`:208`) say nothing about the log. I ran that mutation: delete the `if 
self.embedding_kwargs:` at `llamaindex_embedding.py:211` and all 12 tests in 
this file still pass, while every documented BYO task now logs a spurious 
warning on every run. The sibling file already pins both directions on the 
identical guard: `test_llamaindex_retrieval.py:150-157` parametrises `[(None, 
False), ({"dimensions": 128}, True)]`, so this is a four-line copy.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to