Module: CompletionKit::McpTools::Runs

Extended by:
Base
Defined in:
app/services/completion_kit/mcp_tools/runs.rb

Constant Summary collapse

TOOLS =
{
  "runs_list" => {
    description: "List all runs",
    inputSchema: {type: "object", properties: {}, required: []},
    handler: :list
  },
  "runs_get" => {
    description: "Get a run by ID",
    inputSchema: {type: "object", properties: {id: {type: "integer"}}, required: ["id"]},
    handler: :get
  },
  "runs_create" => {
    description: "Create a run. Omit prompt_id and provide output_column to score existing outputs by grading a pre-existing dataset column instead of generating new ones.",
    inputSchema: {
      type: "object",
      properties: {
        name: {type: "string"}, prompt_id: {type: "integer"},
        dataset_id: {type: "integer"}, judge_model: {type: "string"},
        output_column: {type: "string", description: "Dataset column to grade when prompt_id is omitted; defaults to \"actual_output\"."},
        expected_column: {type: "string", description: "Dataset column holding each row's answer key / ground truth, graded by checks with compare_to \"expected\" and passed to the judge; defaults to \"expected_output\"."},
        metric_ids: {type: "array", items: {type: "integer"}},
        metric_group_id: {type: "integer", description: "Attach the metrics belonging to this metric group (its current metric_ids). Ignored when metric_ids is also given."},
        tag_names: {type: "array", items: {type: "string"}}
      },
      required: ["name"]
    },
    handler: :create
  },
  "runs_update" => {
    description: "Update a run",
    inputSchema: {
      type: "object",
      properties: {
        id: {type: "integer"}, name: {type: "string"},
        dataset_id: {type: "integer"}, judge_model: {type: "string"},
        output_column: {type: "string"},
        expected_column: {type: "string"},
        metric_ids: {type: "array", items: {type: "integer"}},
        metric_group_id: {type: "integer", description: "Replace the run's metrics with those belonging to this metric group. Ignored when metric_ids is also given."},
        tag_names: {type: "array", items: {type: "string"}}
      },
      required: ["id"]
    },
    handler: :update
  },
  "runs_delete" => {
    description: "Delete a run",
    inputSchema: {type: "object", properties: {id: {type: "integer"}}, required: ["id"]},
    handler: :delete
  },
  "runs_generate" => {
    description: "Start a run. Required for every run, including score-only runs (no prompt): generates responses with the prompt when there is one, otherwise copies the graded dataset column and grades it.",
    inputSchema: {type: "object", properties: {id: {type: "integer"}}, required: ["id"]},
    handler: :generate
  },
  "runs_regrade" => {
    description: "Re-grade a run's existing responses with its currently attached metrics, without regenerating. Use after attaching or editing metrics on an already-generated run.",
    inputSchema: {type: "object", properties: {id: {type: "integer"}}, required: ["id"]},
    handler: :regrade
  },
  "runs_rerun" => {
    description: "Create and start a fresh copy of a run with the same prompt, dataset, metrics, and settings. Use when the judge changed and you want a clean run instead of mixing versions.",
    inputSchema: {type: "object", properties: {id: {type: "integer"}}, required: ["id"]},
    handler: :rerun
  },
  "runs_retry_failures" => {
    description: "Re-run only the failed responses of a run, optionally limited to specific response ids via \"only\".",
    inputSchema: {type: "object", properties: {id: {type: "integer"}, only: {type: "array", items: {type: "integer"}}}, required: ["id"]},
    handler: :retry_failures
  }
}.freeze

Class Method Summary collapse

Methods included from Base

call, definitions, error_result, text_result

Class Method Details

.create(args) ⇒ Object



86
87
88
89
90
91
92
93
94
95
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 86

def self.create(args)
  run = Run.new(args.slice("name", "prompt_id", "dataset_id", "judge_model", "output_column", "expected_column"))
  if run.save
    run.replace_metrics!(resolve_metric_ids(args))
    run.update!(tag_names: args["tag_names"]) if args.key?("tag_names")
    text_result(run_payload(run.reload))
  else
    error_result(run.errors.full_messages.join(", "))
  end
end

.delete(args) ⇒ Object



108
109
110
111
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 108

def self.delete(args)
  Run.find(args["id"]).destroy!
  text_result("Run #{args["id"]} deleted")
end

.generate(args) ⇒ Object



113
114
115
116
117
118
119
120
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 113

def self.generate(args)
  run = Run.find(args["id"])
  if run.start!
    text_result(run_payload(run.reload))
  else
    text_result(run.failure_summary || run.errors.full_messages.to_sentence)
  end
end

.get(args) ⇒ Object



82
83
84
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 82

def self.get(args)
  text_result(run_payload(Run.find(args["id"])))
end

.list(_args) ⇒ Object



78
79
80
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 78

def self.list(_args)
  text_result(Run.display_scoped.order(created_at: :desc).map(&:as_json))
end

.regrade(args) ⇒ Object



122
123
124
125
126
127
128
129
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 122

def self.regrade(args)
  run = Run.find(args["id"])
  if run.regrade!
    text_result(run_payload(run.reload))
  else
    error_result("Nothing to re-grade. The run has no succeeded responses or no metrics attached.")
  end
end

.rerun(args) ⇒ Object



131
132
133
134
135
136
137
138
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 131

def self.rerun(args)
  new_run = Run.find(args["id"]).rerun!
  if new_run.start!
    text_result(run_payload(new_run.reload))
  else
    error_result(new_run.failure_summary || "Could not start the new run.")
  end
end

.resolve_metric_ids(args) ⇒ Object



150
151
152
153
154
155
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 150

def self.resolve_metric_ids(args)
  return args["metric_ids"] if args.key?("metric_ids")
  return MetricGroup.find(args["metric_group_id"]).metric_ids if args["metric_group_id"].present?

  nil
end

.retry_failures(args) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 140

def self.retry_failures(args)
  run = Run.find(args["id"])
  if run.stale_review_summary.any?
    return error_result("Judge has changed since this run executed. Retry would mix versions in the same run; use runs_rerun instead.")
  end

  run.retry_failures!(only: args["only"])
  text_result(run_payload(run.reload))
end

.run_payload(run) ⇒ Object



157
158
159
160
161
162
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 157

def self.run_payload(run)
  json = run.as_json
  return json unless run.metric_ids.empty?

  json.merge("warning" => "No metrics are attached, so this run judges nothing. Attach metric_ids or a metric_group_id before generating.")
end

.update(args) ⇒ Object



97
98
99
100
101
102
103
104
105
106
# File 'app/services/completion_kit/mcp_tools/runs.rb', line 97

def self.update(args)
  run = Run.find(args["id"])
  if run.update(args.except("id", "metric_ids", "metric_group_id", "tag_names").slice("name", "dataset_id", "judge_model", "output_column", "expected_column"))
    run.replace_metrics!(resolve_metric_ids(args)) if args.key?("metric_ids") || args["metric_group_id"].present?
    run.update!(tag_names: args["tag_names"]) if args.key?("tag_names")
    text_result(run_payload(run.reload))
  else
    error_result(run.errors.full_messages.join(", "))
  end
end