@@ -204,10 +204,16 @@ func NewRunCommand(cfg *command.Config) *cobra.Command {
204204 If you know which model you want to run inference with, you can run the request in a single command
205205 as %[1]sgh models run [model] [prompt]%[1]s
206206
207+ When using prompt files, you can pass template variables using the %[1]s--var%[1]s flag:
208+ %[1]sgh models run --file prompt.yml --var name=Alice --var topic=AI%[1]s
209+
207210 The return value will be the response to your prompt from the selected model.
208211 ` , "`" ),
209- Example : "gh models run openai/gpt-4o-mini \" how many types of hyena are there?\" " ,
210- Args : cobra .ArbitraryArgs ,
212+ Example : heredoc .Doc (`
213+ gh models run openai/gpt-4o-mini "how many types of hyena are there?"
214+ gh models run --file prompt.yml --var name=Alice --var topic="machine learning"
215+ ` ),
216+ Args : cobra .ArbitraryArgs ,
211217 RunE : func (cmd * cobra.Command , args []string ) error {
212218 filePath , _ := cmd .Flags ().GetString ("file" )
213219 var pf * prompt.File
@@ -223,6 +229,12 @@ func NewRunCommand(cfg *command.Config) *cobra.Command {
223229 }
224230 }
225231
232+ // Parse template variables from flags
233+ templateVars , err := parseTemplateVariables (cmd .Flags ())
234+ if err != nil {
235+ return err
236+ }
237+
226238 cmdHandler := newRunCommandHandler (cmd , cfg , args )
227239 if cmdHandler == nil {
228240 return nil
@@ -270,16 +282,22 @@ func NewRunCommand(cfg *command.Config) *cobra.Command {
270282 }
271283
272284 // If there is no prompt file, add the initialPrompt to the conversation.
273- // If a prompt file is passed, load the messages from the file, templating {{input}}
274- // using the initialPrompt.
285+ // If a prompt file is passed, load the messages from the file, templating variables
286+ // using the provided template variables and initialPrompt.
275287 if pf == nil {
276288 conversation .AddMessage (azuremodels .ChatMessageRoleUser , initialPrompt )
277289 } else {
278290 interactiveMode = false
279291
280- // Template the messages with the input
281- templateData := map [string ]interface {}{
282- "input" : initialPrompt ,
292+ // Template the messages with the variables
293+ templateData := make (map [string ]interface {})
294+
295+ // Add the input variable (backward compatibility)
296+ templateData ["input" ] = initialPrompt
297+
298+ // Add custom variables
299+ for key , value := range templateVars {
300+ templateData [key ] = value
283301 }
284302
285303 for _ , m := range pf .Messages {
@@ -385,6 +403,7 @@ func NewRunCommand(cfg *command.Config) *cobra.Command {
385403 }
386404
387405 cmd .Flags ().String ("file" , "" , "Path to a .prompt.yml file." )
406+ cmd .Flags ().StringSlice ("var" , []string {}, "Template variables for prompt files (can be used multiple times: --var name=value)" )
388407 cmd .Flags ().String ("max-tokens" , "" , "Limit the maximum tokens for the model response." )
389408 cmd .Flags ().String ("temperature" , "" , "Controls randomness in the response, use lower to be more deterministic." )
390409 cmd .Flags ().String ("top-p" , "" , "Controls text diversity by selecting the most probable words until a set probability is reached." )
@@ -393,6 +412,43 @@ func NewRunCommand(cfg *command.Config) *cobra.Command {
393412 return cmd
394413}
395414
415+ // parseTemplateVariables parses template variables from the --var flags
416+ func parseTemplateVariables (flags * pflag.FlagSet ) (map [string ]string , error ) {
417+ varFlags , err := flags .GetStringSlice ("var" )
418+ if err != nil {
419+ return nil , err
420+ }
421+
422+ templateVars := make (map [string ]string )
423+ for _ , varFlag := range varFlags {
424+ // Handle empty strings
425+ if strings .TrimSpace (varFlag ) == "" {
426+ continue
427+ }
428+
429+ parts := strings .SplitN (varFlag , "=" , 2 )
430+ if len (parts ) != 2 {
431+ return nil , fmt .Errorf ("invalid variable format '%s', expected 'key=value'" , varFlag )
432+ }
433+
434+ key := strings .TrimSpace (parts [0 ])
435+ value := parts [1 ] // Don't trim value to preserve intentional whitespace
436+
437+ if key == "" {
438+ return nil , fmt .Errorf ("variable key cannot be empty in '%s'" , varFlag )
439+ }
440+
441+ // Check for duplicate keys
442+ if _ , exists := templateVars [key ]; exists {
443+ return nil , fmt .Errorf ("duplicate variable key '%s'" , key )
444+ }
445+
446+ templateVars [key ] = value
447+ }
448+
449+ return templateVars , nil
450+ }
451+
396452type runCommandHandler struct {
397453 ctx context.Context
398454 cfg * command.Config
@@ -445,7 +501,7 @@ func (h *runCommandHandler) getModelNameFromArgs(models []*azuremodels.ModelSumm
445501}
446502
447503func validateModelName (modelName string , models []* azuremodels.ModelSummary ) (string , error ) {
448- noMatchErrorMessage := "The specified model name is not found. Run 'gh models list' to see available models or 'gh models run' to select interactively."
504+ noMatchErrorMessage := fmt . Sprintf ( "The specified model '%s' is not found. Run 'gh models list' to see available models or 'gh models run' to select interactively." , modelName )
449505
450506 if modelName == "" {
451507 return "" , errors .New (noMatchErrorMessage )
0 commit comments