11from __future__ import annotations
22
33import re
4+ from typing import NamedTuple
45
56from together import APIError
67from together .types .beta import Model , Endpoint
1819# Logic for resolving a model + config from a user input string
1920#
2021# 1. Raw model id (e.g. ml_...)
21- # → GET /configs?referenceModelId=... for the config and model path
22+ # → retrieve from --project when possible; config via baseModelId / id
23+ # → else GET /configs?referenceModelId=... (public / reference models)
2224# 2. Full model path (projects/.../models/...)
23- # → parse the model id, then same as (1)
25+ # → retrieve that model (keep it as the deploy target), resolve config via
26+ # baseModelId (or the model id when it is itself a reference model).
27+ # Preserve an optional /revisions/... pin on the deploy path.
2428# 3. Named model (prefix/model-name)
2529# a. prefix == project slug → list private models by name, resolve config via
2630# baseModelId (path 1), but deploy the custom model path
3135# - one profile + no --config → use that profile's config
3236# - --config given → use the profile whose config matches
3337# After either path, re-validate against the user's --config when provided.
34- MODEL_PATH_RE = re .compile (r"^projects/([^/]+)/models/([^/]+)(?:/revisions/[^/]+)?$" )
38+ MODEL_PATH_RE = re .compile (r"^projects/([^/]+)/models/([^/]+)(?:/revisions/([^/]+))?$" )
39+
40+
41+ class ResolvedModelAndConfig (NamedTuple ):
42+ model : Model
43+ config : Config
44+ revision_id : str | None = None
3545
3646
3747async def resolve_model (
@@ -40,24 +50,48 @@ async def resolve_model(
4050 * ,
4151 config_id : str | None = None ,
4252) -> Model :
43- model , _config = await resolve_model_and_config (config , model_input , config_id = config_id )
44- return model
53+ resolved = await resolve_model_and_config (config , model_input , config_id = config_id )
54+ return resolved . model
4555
4656
4757async def resolve_model_and_config (
4858 config : CLIConfigParameter ,
4959 model_input : str ,
5060 * ,
5161 config_id : str | None = None ,
52- ) -> tuple [ Model , Config ] :
62+ ) -> ResolvedModelAndConfig :
5363 """Resolve a deployable model and the config revision to pair with it."""
54- # 1 / 2. Full model path or raw model id → configs list by referenceModelId
64+ # 2. Full model path → keep the user's model; config from its base/reference.
5565 path_match = MODEL_PATH_RE .match (model_input )
5666 if path_match :
57- _project_id , model_id = path_match .groups ()
58- return await _resolve_via_configs (config , model_id , config_id = config_id , model_input = model_input )
67+ project_id , model_id , revision_id = path_match .group (1 ), path_match .group (2 ), path_match .group (3 )
68+ return await _resolve_explicit_model (
69+ config ,
70+ model_id = model_id ,
71+ project_id = project_id ,
72+ config_id = config_id ,
73+ model_input = model_input ,
74+ revision_id = revision_id ,
75+ )
5976
77+ # 1. Raw model id
6078 if "/" not in model_input :
79+ if config .project_id :
80+ try :
81+ model = await config .client .beta .models .retrieve (id = model_input , project_id = config .project_id )
82+ except APIError as e :
83+ if "not found" not in e .message .lower ():
84+ raise
85+ else :
86+ reference_model_id = model .base_model_id or model .id
87+ assert reference_model_id is not None
88+ return await _resolve_config_for_model (
89+ config ,
90+ model ,
91+ reference_model_id = reference_model_id ,
92+ config_id = config_id ,
93+ model_input = model_input ,
94+ )
6195 return await _resolve_via_configs (config , model_input , config_id = config_id , model_input = model_input )
6296
6397 # 3. Named model (prefix/model-name)
@@ -70,32 +104,84 @@ async def resolve_model_and_config(
70104 reference_model_id = model .base_model_id or model .id
71105 assert reference_model_id is not None
72106 # Config comes from the base/reference model; deploy path stays the custom model.
73- _base_model , selected_config = await _resolve_via_configs (
107+ return await _resolve_config_for_model (
74108 config ,
75- reference_model_id ,
109+ model ,
110+ reference_model_id = reference_model_id ,
76111 config_id = config_id ,
77112 model_input = model_input ,
78113 )
79- return model , selected_config
80114
81115 return await _resolve_public_model_and_config (config , model_input , config_id = config_id )
82116
83117
118+ async def _resolve_explicit_model (
119+ config : CLIConfigParameter ,
120+ * ,
121+ model_id : str ,
122+ project_id : str ,
123+ config_id : str | None ,
124+ model_input : str ,
125+ revision_id : str | None = None ,
126+ ) -> ResolvedModelAndConfig :
127+ """Load the user-specified model and pair it with a compatible config."""
128+ try :
129+ model = await config .client .beta .models .retrieve (id = model_id , project_id = project_id )
130+ except APIError as e :
131+ if "not found" in e .message .lower ():
132+ raise ValueError (f"Model { model_input } not found." ) from None
133+ raise
134+
135+ reference_model_id = model .base_model_id or model .id
136+ assert reference_model_id is not None
137+ return await _resolve_config_for_model (
138+ config ,
139+ model ,
140+ reference_model_id = reference_model_id ,
141+ config_id = config_id ,
142+ model_input = model_input ,
143+ revision_id = revision_id ,
144+ )
145+
146+
147+ async def _resolve_config_for_model (
148+ config : CLIConfigParameter ,
149+ model : Model ,
150+ * ,
151+ reference_model_id : str ,
152+ config_id : str | None ,
153+ model_input : str ,
154+ revision_id : str | None = None ,
155+ ) -> ResolvedModelAndConfig :
156+ selected = resolve_config (
157+ await resolve_configs (config , reference_model_id ),
158+ config_id ,
159+ model = model_input ,
160+ )
161+ selected = validate_requested_config (selected , config_id , model = model_input )
162+ return ResolvedModelAndConfig (model = model , config = selected , revision_id = revision_id )
163+
164+
84165async def _resolve_via_configs (
85166 config : CLIConfigParameter ,
86167 reference_model_id : str ,
87168 * ,
88169 config_id : str | None ,
89170 model_input : str ,
90- ) -> tuple [Model , Config ]:
171+ ) -> ResolvedModelAndConfig :
172+ """Resolve a public/reference model id through the configs API.
173+
174+ The deploy target is the config's reference model — correct when the user
175+ passed a bare reference-model id that is not retrievable under --project.
176+ """
91177 selected = resolve_config (
92178 await resolve_configs (config , reference_model_id ),
93179 config_id ,
94180 model = model_input ,
95181 )
96182 selected = validate_requested_config (selected , config_id , model = model_input )
97183 model = await _retrieve_model_from_reference (config , selected , model_input = model_input )
98- return model , selected
184+ return ResolvedModelAndConfig ( model = model , config = selected )
99185
100186
101187async def _retrieve_model_from_reference (
@@ -108,7 +194,7 @@ async def _retrieve_model_from_reference(
108194 path = selected .reference_model or ""
109195 match = MODEL_PATH_RE .match (path )
110196 if match :
111- project_id , model_id = match .groups ( )
197+ project_id , model_id = match .group ( 1 ), match . group ( 2 )
112198 elif selected .reference_model_id and selected .project_id :
113199 project_id , model_id = selected .project_id , selected .reference_model_id
114200 else :
@@ -236,7 +322,7 @@ async def _resolve_public_model_and_config(
236322 model_input : str ,
237323 * ,
238324 config_id : str | None = None ,
239- ) -> tuple [ Model , Config ] :
325+ ) -> ResolvedModelAndConfig :
240326 supported_models = await config .client .beta .models .list_supported (search = model_input )
241327 if not supported_models .data :
242328 raise ValueError (f"Model { model_input } not found." )
@@ -255,19 +341,22 @@ async def _resolve_public_model_and_config(
255341 match = MODEL_PATH_RE .match (profile .model or "" )
256342 if not match :
257343 raise ValueError (f"Invalid model path: { profile .model } " )
258- project_id , model_id = match .groups ( )
344+ project_id , model_id , revision_id = match .group ( 1 ), match . group ( 2 ), match . group ( 3 )
259345
260346 selected_config = validate_requested_config (
261347 config_from_profile (profile ),
262348 config_id ,
263349 model = model_input ,
264350 )
265351 model = Model .construct (id = model_id , projectId = project_id , name = public_model .name or model_id )
266- return model , selected_config
352+ return ResolvedModelAndConfig ( model = model , config = selected_config , revision_id = revision_id )
267353
268354
269- def construct_model_path (model : Model ) -> str :
270- return f"projects/{ model .project_id } /models/{ model .id } "
355+ def construct_model_path (model : Model , revision_id : str | None = None ) -> str :
356+ path = f"projects/{ model .project_id } /models/{ model .id } "
357+ if revision_id :
358+ return f"{ path } /revisions/{ revision_id } "
359+ return path
271360
272361
273362async def resolve_endpoint (config : CLIConfigParameter , endpoint_id_or_name : str ) -> Endpoint :
0 commit comments