lex-llm-azure-foundry 0.2.13 → 0.2.15
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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e53b1d826dd7b500516b5d81d4d1502f4cc0c3edbe9453fe80a2436343fdece9
|
|
4
|
+
data.tar.gz: 4b9206ecc5905a2325b4a626901ea4e2cdb5166dba0c9b679a91668a584d5fd1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b8fce48bc00ff40a0d8e04f731ee2773b0f70bf0fa4e78e212065b063b9631a5f1af639f2ed9e11ba73945f8af47e36008f9b4c62782fe82669f7b6e9e91c24
|
|
7
|
+
data.tar.gz: 6d1a10eb83f5d62c833c51ef3ed4499b71f0d942938cca357747fbfc4bf935a5d46be0c3a0e0f052fbe42cdfcd6d9fa32610a63967ff291ff9a6b2cb18cb0774
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.15] - 2026-07-09
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Offerings now populate `limits[:context_window]` (and `max_output_tokens`), so the router sees real capacity for Azure lanes instead of nil/unbounded. `build_offering` previously never set `limits` — deployment config context sizes landed in `metadata` and were invisible to routing (a request could then mis-route to an Azure lane the router thought had unlimited context). `context_window` is sourced from live catalog when the endpoint reports it, else per-deployment instance config (`context_window`/`max_input_tokens`), else nil (a genuine per-instance gap — never a hardcoded guess). Azure's inference-plane endpoints (`model_inference GET /info`, `openai_v1 GET /models`) do not report per-model context length, mirroring the OpenAI/Bedrock cloud providers. `Model::Info#context_length` (models API) is populated the same way.
|
|
7
|
+
|
|
8
|
+
## [0.2.14] - 2026-07-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Emit relative request paths from `path_for` (no leading slash). `Connection` builds Faraday with `api_base` as the base URL; on the `openai_v1` surface that base carries the `/openai/v1` path, and a leading-slash path was treated as absolute and dropped it — 404ing live discovery (empty offerings) and chat. Paths are now relative so the base path survives on both surfaces.
|
|
12
|
+
- Resolve `models_url`/`health_url` per surface: `models` on `openai_v1`, `models/info` on `model_inference`. Previously always `info`, which 404s on the `openai_v1` surface.
|
|
13
|
+
|
|
3
14
|
## [0.2.13] - 2026-06-20
|
|
4
15
|
|
|
5
16
|
### Fixed
|
|
@@ -15,6 +15,9 @@ module Legion
|
|
|
15
15
|
DEFAULT_API_VERSION = '2024-05-01-preview'
|
|
16
16
|
MODEL_INFERENCE_SURFACE = :model_inference
|
|
17
17
|
OPENAI_V1_SURFACE = :openai_v1
|
|
18
|
+
# Keys a live model catalog might use to report context length. Azure's own
|
|
19
|
+
# endpoints rarely do (see deployment_limits), so this is a best-effort read.
|
|
20
|
+
CATALOG_CONTEXT_KEYS = %i[context_window max_input_tokens context_length].freeze
|
|
18
21
|
|
|
19
22
|
class << self
|
|
20
23
|
def slug = 'azure_foundry'
|
|
@@ -143,7 +146,7 @@ module Legion
|
|
|
143
146
|
def completion_url = path_for('chat/completions')
|
|
144
147
|
def chat_url = completion_url
|
|
145
148
|
def stream_url = completion_url
|
|
146
|
-
def models_url = path_for('info')
|
|
149
|
+
def models_url = surface == MODEL_INFERENCE_SURFACE ? path_for('info') : path_for('models')
|
|
147
150
|
def embedding_url(**) = path_for('embeddings')
|
|
148
151
|
def health_url = models_url
|
|
149
152
|
|
|
@@ -161,7 +164,8 @@ module Legion
|
|
|
161
164
|
model_family: normalize_family(model_family || configured_family || infer_model_family(model_id)),
|
|
162
165
|
canonical_model_alias: canonical_model_alias || configured_alias,
|
|
163
166
|
usage_type: usage_type || value_for(deployment, :usage_type) || usage_type_for(model_id),
|
|
164
|
-
metadata: metadata.merge(deployment_metadata(deployment))
|
|
167
|
+
metadata: metadata.merge(deployment_metadata(deployment)),
|
|
168
|
+
limits: deployment_limits(deployment)
|
|
165
169
|
)
|
|
166
170
|
end
|
|
167
171
|
|
|
@@ -275,6 +279,7 @@ module Legion
|
|
|
275
279
|
provider: :azure_foundry,
|
|
276
280
|
family: offering.metadata[:model_family],
|
|
277
281
|
capabilities: capabilities,
|
|
282
|
+
context_length: offering.context_window,
|
|
278
283
|
modalities_input: modalities[:input],
|
|
279
284
|
modalities_output: modalities[:output],
|
|
280
285
|
metadata: offering.to_h
|
|
@@ -285,10 +290,14 @@ module Legion
|
|
|
285
290
|
config.azure_foundry_api_version || DEFAULT_API_VERSION
|
|
286
291
|
end
|
|
287
292
|
|
|
293
|
+
# Paths MUST be relative (no leading slash). Faraday builds the
|
|
294
|
+
# connection with api_base as the base URL — on the openai_v1 surface
|
|
295
|
+
# that base carries the /openai/v1 path, and a leading-slash path would
|
|
296
|
+
# be treated as absolute and drop it, 404ing discovery and chat.
|
|
288
297
|
def path_for(path)
|
|
289
|
-
prefix = surface == MODEL_INFERENCE_SURFACE ? '/
|
|
298
|
+
prefix = surface == MODEL_INFERENCE_SURFACE ? 'models/' : ''
|
|
290
299
|
suffix = surface == MODEL_INFERENCE_SURFACE ? "?api-version=#{api_version}" : ''
|
|
291
|
-
"#{prefix}
|
|
300
|
+
"#{prefix}#{path}#{suffix}"
|
|
292
301
|
end
|
|
293
302
|
|
|
294
303
|
def bearer_header
|
|
@@ -358,7 +367,8 @@ module Legion
|
|
|
358
367
|
)
|
|
359
368
|
end
|
|
360
369
|
|
|
361
|
-
def build_offering(model:, model_family:, usage_type:, instance_id:, canonical_model_alias:,
|
|
370
|
+
def build_offering(model:, model_family:, usage_type:, instance_id:, canonical_model_alias:, # rubocop:disable Metrics/ParameterLists
|
|
371
|
+
metadata:, limits: {})
|
|
362
372
|
policy = resolve_capability_policy(model, usage_type)
|
|
363
373
|
Legion::Extensions::Llm::Routing::ModelOffering.new(
|
|
364
374
|
provider_family: :azure_foundry,
|
|
@@ -369,6 +379,7 @@ module Legion
|
|
|
369
379
|
usage_type: usage_type.to_sym,
|
|
370
380
|
capabilities: policy[:capabilities],
|
|
371
381
|
capability_sources: policy[:sources],
|
|
382
|
+
limits: limits,
|
|
372
383
|
metadata: metadata.merge(
|
|
373
384
|
model_family: model_family,
|
|
374
385
|
canonical_model_alias: canonical_model_alias,
|
|
@@ -380,14 +391,29 @@ module Legion
|
|
|
380
391
|
def with_live_metadata(offering)
|
|
381
392
|
response = connection.get(models_url)
|
|
382
393
|
metadata = offering.metadata.merge(model_info: response.body)
|
|
383
|
-
|
|
394
|
+
# Prefer a context_window the live catalog actually reports; fall back to
|
|
395
|
+
# whatever the deployment config already resolved (Azure endpoints usually
|
|
396
|
+
# omit context length, so config remains authoritative).
|
|
397
|
+
limits = offering.to_h[:limits].to_h
|
|
398
|
+
catalog_window = catalog_context_window(response.body)
|
|
399
|
+
limits = limits.merge(context_window: catalog_window) if catalog_window
|
|
400
|
+
with_health(offering, ready: true, checked: true, metadata:, limits:)
|
|
384
401
|
end
|
|
385
402
|
|
|
386
|
-
def
|
|
403
|
+
def catalog_context_window(body)
|
|
404
|
+
return nil unless body.is_a?(Hash)
|
|
405
|
+
|
|
406
|
+
value = CATALOG_CONTEXT_KEYS.filter_map { |key| body[key] || body[key.to_s] }.first
|
|
407
|
+
value&.to_i
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def with_health(offering, ready:, checked:, error: nil, metadata: offering.metadata, limits: nil) # rubocop:disable Metrics/ParameterLists
|
|
387
411
|
health = { ready: ready, checked: checked }
|
|
388
412
|
health = health.merge(error: error.class.name, message: error.message) if error
|
|
389
413
|
|
|
390
|
-
|
|
414
|
+
data = offering.to_h.merge(health:, metadata:)
|
|
415
|
+
data[:limits] = limits if limits
|
|
416
|
+
Legion::Extensions::Llm::Routing::ModelOffering.new(data)
|
|
391
417
|
end
|
|
392
418
|
|
|
393
419
|
def filter_offerings(offerings, model_family: nil, usage_type: nil, **)
|
|
@@ -398,6 +424,24 @@ module Legion
|
|
|
398
424
|
end
|
|
399
425
|
end
|
|
400
426
|
|
|
427
|
+
# Azure's inference-plane endpoints do NOT report per-model context length
|
|
428
|
+
# (model_inference GET /info returns only model_name/model_type/
|
|
429
|
+
# model_provider_name; the openai_v1 GET /models is an OpenAI-style
|
|
430
|
+
# id/created/owned_by list). So context_window is sourced from the
|
|
431
|
+
# per-deployment instance config (keys :context_window / :max_input_tokens);
|
|
432
|
+
# live discovery (with_live_metadata) can override it when a catalog entry
|
|
433
|
+
# actually carries it. Absent both, the window is nil — a per-instance gap,
|
|
434
|
+
# never a hardcoded guess.
|
|
435
|
+
def deployment_limits(deployment)
|
|
436
|
+
return {} unless deployment
|
|
437
|
+
|
|
438
|
+
context_window = value_for(deployment, :context_window) || value_for(deployment, :max_input_tokens)
|
|
439
|
+
{
|
|
440
|
+
context_window: context_window&.to_i,
|
|
441
|
+
max_output_tokens: value_for(deployment, :max_output_tokens)&.to_i
|
|
442
|
+
}.compact
|
|
443
|
+
end
|
|
444
|
+
|
|
401
445
|
def deployment_metadata(deployment)
|
|
402
446
|
return {} unless deployment
|
|
403
447
|
|