11from __future__ import annotations
22
33import re
4+ from typing import NamedTuple
45
5- from together import APIError
6+ from together import NotFoundError
67from together .types .beta import Model , Endpoint
78from together .lib .cli .utils .config import CLIConfigParameter
89from together .lib .cli .utils ._console import console
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,47 @@ 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 NotFoundError :
83+ pass
84+ else :
85+ reference_model_id = model .base_model_id or model .id
86+ assert reference_model_id is not None
87+ return await _resolve_config_for_model (
88+ config ,
89+ model ,
90+ reference_model_id = reference_model_id ,
91+ config_id = config_id ,
92+ model_input = model_input ,
93+ )
6194 return await _resolve_via_configs (config , model_input , config_id = config_id , model_input = model_input )
6295
6396 # 3. Named model (prefix/model-name)
@@ -70,32 +103,82 @@ async def resolve_model_and_config(
70103 reference_model_id = model .base_model_id or model .id
71104 assert reference_model_id is not None
72105 # Config comes from the base/reference model; deploy path stays the custom model.
73- _base_model , selected_config = await _resolve_via_configs (
106+ return await _resolve_config_for_model (
74107 config ,
75- reference_model_id ,
108+ model ,
109+ reference_model_id = reference_model_id ,
76110 config_id = config_id ,
77111 model_input = model_input ,
78112 )
79- return model , selected_config
80113
81114 return await _resolve_public_model_and_config (config , model_input , config_id = config_id )
82115
83116
117+ async def _resolve_explicit_model (
118+ config : CLIConfigParameter ,
119+ * ,
120+ model_id : str ,
121+ project_id : str ,
122+ config_id : str | None ,
123+ model_input : str ,
124+ revision_id : str | None = None ,
125+ ) -> ResolvedModelAndConfig :
126+ """Load the user-specified model and pair it with a compatible config."""
127+ try :
128+ model = await config .client .beta .models .retrieve (id = model_id , project_id = project_id )
129+ except NotFoundError :
130+ raise ValueError (f"Model { model_input } not found." ) from None
131+
132+ reference_model_id = model .base_model_id or model .id
133+ assert reference_model_id is not None
134+ return await _resolve_config_for_model (
135+ config ,
136+ model ,
137+ reference_model_id = reference_model_id ,
138+ config_id = config_id ,
139+ model_input = model_input ,
140+ revision_id = revision_id ,
141+ )
142+
143+
144+ async def _resolve_config_for_model (
145+ config : CLIConfigParameter ,
146+ model : Model ,
147+ * ,
148+ reference_model_id : str ,
149+ config_id : str | None ,
150+ model_input : str ,
151+ revision_id : str | None = None ,
152+ ) -> ResolvedModelAndConfig :
153+ selected = resolve_config (
154+ await resolve_configs (config , reference_model_id ),
155+ config_id ,
156+ model = model_input ,
157+ )
158+ selected = validate_requested_config (selected , config_id , model = model_input )
159+ return ResolvedModelAndConfig (model = model , config = selected , revision_id = revision_id )
160+
161+
84162async def _resolve_via_configs (
85163 config : CLIConfigParameter ,
86164 reference_model_id : str ,
87165 * ,
88166 config_id : str | None ,
89167 model_input : str ,
90- ) -> tuple [Model , Config ]:
168+ ) -> ResolvedModelAndConfig :
169+ """Resolve a public/reference model id through the configs API.
170+
171+ The deploy target is the config's reference model — correct when the user
172+ passed a bare reference-model id that is not retrievable under --project.
173+ """
91174 selected = resolve_config (
92175 await resolve_configs (config , reference_model_id ),
93176 config_id ,
94177 model = model_input ,
95178 )
96179 selected = validate_requested_config (selected , config_id , model = model_input )
97180 model = await _retrieve_model_from_reference (config , selected , model_input = model_input )
98- return model , selected
181+ return ResolvedModelAndConfig ( model = model , config = selected )
99182
100183
101184async def _retrieve_model_from_reference (
@@ -108,18 +191,16 @@ async def _retrieve_model_from_reference(
108191 path = selected .reference_model or ""
109192 match = MODEL_PATH_RE .match (path )
110193 if match :
111- project_id , model_id = match .groups ( )
194+ project_id , model_id = match .group ( 1 ), match . group ( 2 )
112195 elif selected .reference_model_id and selected .project_id :
113196 project_id , model_id = selected .project_id , selected .reference_model_id
114197 else :
115198 raise ValueError (f"Config { selected .id } has no usable reference model path." )
116199
117200 try :
118201 return await config .client .beta .models .retrieve (id = model_id , project_id = project_id )
119- except APIError as e :
120- if "not found" in e .message .lower ():
121- raise ValueError (f"Model { model_input } not found." ) from None
122- raise
202+ except NotFoundError :
203+ raise ValueError (f"Model { model_input } not found." ) from None
123204
124205
125206async def _find_private_model_by_name (config : CLIConfigParameter , name : str ) -> Model :
@@ -236,7 +317,7 @@ async def _resolve_public_model_and_config(
236317 model_input : str ,
237318 * ,
238319 config_id : str | None = None ,
239- ) -> tuple [ Model , Config ] :
320+ ) -> ResolvedModelAndConfig :
240321 supported_models = await config .client .beta .models .list_supported (search = model_input )
241322 if not supported_models .data :
242323 raise ValueError (f"Model { model_input } not found." )
@@ -255,19 +336,22 @@ async def _resolve_public_model_and_config(
255336 match = MODEL_PATH_RE .match (profile .model or "" )
256337 if not match :
257338 raise ValueError (f"Invalid model path: { profile .model } " )
258- project_id , model_id = match .groups ( )
339+ project_id , model_id , revision_id = match .group ( 1 ), match . group ( 2 ), match . group ( 3 )
259340
260341 selected_config = validate_requested_config (
261342 config_from_profile (profile ),
262343 config_id ,
263344 model = model_input ,
264345 )
265346 model = Model .construct (id = model_id , projectId = project_id , name = public_model .name or model_id )
266- return model , selected_config
347+ return ResolvedModelAndConfig ( model = model , config = selected_config , revision_id = revision_id )
267348
268349
269- def construct_model_path (model : Model ) -> str :
270- return f"projects/{ model .project_id } /models/{ model .id } "
350+ def construct_model_path (model : Model , revision_id : str | None = None ) -> str :
351+ path = f"projects/{ model .project_id } /models/{ model .id } "
352+ if revision_id :
353+ return f"{ path } /revisions/{ revision_id } "
354+ return path
271355
272356
273357async def resolve_endpoint (config : CLIConfigParameter , endpoint_id_or_name : str ) -> Endpoint :
0 commit comments