Skip to content

Commit 049cab5

Browse files
authored
add gen_ai.request.model to conclusion span attributes (#28739)
1 parent 6bf3d1f commit 049cab5

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

actions/setup/js/send_otlp_span.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ async function sendJobConclusionSpan(spanName, options = {}) {
749749

750750
if (jobName) attributes.push(buildAttr("gh-aw.job.name", jobName));
751751
if (engineId) attributes.push(buildAttr("gh-aw.engine.id", engineId));
752+
if (model) attributes.push(buildAttr("gen_ai.request.model", model));
752753
if (eventName) attributes.push(buildAttr("gh-aw.event_name", eventName));
753754
// Deployment state: prefer the env var (set from github.event.deployment_status.state
754755
// in the compiled workflow), fall back to aw_info.deployment_state or aw_context propagation.

actions/setup/js/send_otlp_span.test.cjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,44 @@ describe("sendJobConclusionSpan", () => {
18741874
expect(keys).not.toContain("gen_ai.workflow.name");
18751875
});
18761876

1877+
it("includes gen_ai.request.model on the conclusion span when model is set in aw_info.json", async () => {
1878+
const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" });
1879+
vi.stubGlobal("fetch", mockFetch);
1880+
1881+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
1882+
1883+
const readFileSpy = vi.spyOn(fs, "readFileSync").mockImplementation(filePath => {
1884+
if (filePath === "/tmp/gh-aw/aw_info.json") {
1885+
return JSON.stringify({ model: "claude-3-5-sonnet-20241022", engine_id: "claude" });
1886+
}
1887+
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
1888+
});
1889+
1890+
await sendJobConclusionSpan("gh-aw.job.conclusion");
1891+
readFileSpy.mockRestore();
1892+
1893+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
1894+
const span = body.resourceSpans[0].scopeSpans[0].spans[0];
1895+
expect(span.name).toBe("gh-aw.job.conclusion");
1896+
const attrs = Object.fromEntries(span.attributes.map(a => [a.key, a.value.stringValue ?? a.value.intValue]));
1897+
expect(attrs["gen_ai.request.model"]).toBe("claude-3-5-sonnet-20241022");
1898+
});
1899+
1900+
it("omits gen_ai.request.model from the conclusion span when model is absent in aw_info.json", async () => {
1901+
const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" });
1902+
vi.stubGlobal("fetch", mockFetch);
1903+
1904+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com";
1905+
1906+
await sendJobConclusionSpan("gh-aw.job.conclusion");
1907+
1908+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
1909+
const span = body.resourceSpans[0].scopeSpans[0].spans[0];
1910+
expect(span.name).toBe("gh-aw.job.conclusion");
1911+
const keys = span.attributes.map(a => a.key);
1912+
expect(keys).not.toContain("gen_ai.request.model");
1913+
});
1914+
18771915
it("includes gh-aw.run.attempt attribute from GITHUB_RUN_ATTEMPT env var", async () => {
18781916
const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" });
18791917
vi.stubGlobal("fetch", mockFetch);

0 commit comments

Comments
 (0)