Skip to content

Commit 2ea499f

Browse files
committed
feat: support OpenAI reasoning_effort parameter (#57)
1 parent da35ff3 commit 2ea499f

4 files changed

Lines changed: 20 additions & 11 deletions

File tree

packages/cli/src/commands/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ configCommand
2323

2424
const KNOWN_KEYS = new Set([
2525
"llm.provider", "llm.baseUrl", "llm.model", "llm.temperature",
26-
"llm.maxTokens", "llm.thinkingBudget", "llm.apiFormat", "llm.stream",
26+
"llm.maxTokens", "llm.thinkingBudget", "llm.reasoningEffort", "llm.apiFormat", "llm.stream",
2727
"daemon.schedule.radarCron", "daemon.schedule.writeCron",
2828
"daemon.maxConcurrentBooks", "daemon.chaptersPerCycle",
2929
"daemon.retryDelayMs", "daemon.cooldownAfterChapterMs",

packages/core/src/llm/provider.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export interface LLMClient {
7979
readonly temperature: number;
8080
readonly maxTokens: number;
8181
readonly thinkingBudget: number;
82+
readonly reasoningEffort?: "low" | "medium" | "high";
8283
};
8384
}
8485

@@ -114,6 +115,7 @@ export function createLLMClient(config: LLMConfig): LLMClient {
114115
temperature: config.temperature ?? 0.7,
115116
maxTokens: config.maxTokens ?? 8192,
116117
thinkingBudget: config.thinkingBudget ?? 0,
118+
reasoningEffort: config.reasoningEffort,
117119
};
118120

119121
const apiFormat = config.apiFormat ?? "chat";
@@ -218,6 +220,7 @@ export async function chatCompletion(
218220
const resolved = {
219221
temperature: options?.temperature ?? client.defaults.temperature,
220222
maxTokens: options?.maxTokens ?? client.defaults.maxTokens,
223+
reasoningEffort: client.defaults.reasoningEffort,
221224
};
222225
const onStreamProgress = options?.onStreamProgress;
223226
const errorCtx = { baseUrl: client._openai?.baseURL ?? "(anthropic)", model };
@@ -321,21 +324,22 @@ async function chatCompletionOpenAIChat(
321324
client: OpenAI,
322325
model: string,
323326
messages: ReadonlyArray<LLMMessage>,
324-
options: { readonly temperature: number; readonly maxTokens: number },
327+
options: { readonly temperature: number; readonly maxTokens: number; readonly reasoningEffort?: "low" | "medium" | "high" },
325328
webSearch?: boolean,
326329
onStreamProgress?: OnStreamProgress,
327330
): Promise<LLMResponse> {
328-
const stream = await client.chat.completions.create({
331+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
332+
const createParams: any = {
329333
model,
330-
messages: messages.map((m) => ({
331-
role: m.role,
332-
content: m.content,
333-
})),
334+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
334335
temperature: options.temperature,
335336
max_tokens: options.maxTokens,
336337
stream: true,
337338
...(webSearch ? { web_search_options: { search_context_size: "medium" as const } } : {}),
338-
});
339+
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
340+
};
341+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
342+
const stream = await client.chat.completions.create(createParams) as any;
339343

340344
const chunks: string[] = [];
341345
let inputTokens = 0;
@@ -382,16 +386,19 @@ async function chatCompletionOpenAIChatSync(
382386
client: OpenAI,
383387
model: string,
384388
messages: ReadonlyArray<LLMMessage>,
385-
options: { readonly temperature: number; readonly maxTokens: number },
389+
options: { readonly temperature: number; readonly maxTokens: number; readonly reasoningEffort?: "low" | "medium" | "high" },
386390
_webSearch?: boolean,
387391
): Promise<LLMResponse> {
388-
const response = await client.chat.completions.create({
392+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
393+
const syncParams: any = {
389394
model,
390395
messages: messages.map((m) => ({ role: m.role, content: m.content })),
391396
temperature: options.temperature,
392397
max_tokens: options.maxTokens,
393398
stream: false,
394-
});
399+
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
400+
};
401+
const response = await client.chat.completions.create(syncParams);
395402

396403
const content = response.choices[0]?.message?.content ?? "";
397404
if (!content) throw new Error("LLM returned empty response");

packages/core/src/models/project.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const LLMConfigSchema = z.object({
88
temperature: z.number().min(0).max(2).default(0.7),
99
maxTokens: z.number().int().min(1).default(8192),
1010
thinkingBudget: z.number().int().min(0).default(0),
11+
reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
1112
apiFormat: z.enum(["chat", "responses"]).default("chat"),
1213
stream: z.boolean().default(true),
1314
});

packages/core/src/utils/config-loader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export async function loadProjectConfig(root: string): Promise<ProjectConfig> {
4444
if (env.INKOS_LLM_TEMPERATURE) llm.temperature = parseFloat(env.INKOS_LLM_TEMPERATURE);
4545
if (env.INKOS_LLM_MAX_TOKENS) llm.maxTokens = parseInt(env.INKOS_LLM_MAX_TOKENS, 10);
4646
if (env.INKOS_LLM_THINKING_BUDGET) llm.thinkingBudget = parseInt(env.INKOS_LLM_THINKING_BUDGET, 10);
47+
if (env.INKOS_LLM_REASONING_EFFORT) llm.reasoningEffort = env.INKOS_LLM_REASONING_EFFORT;
4748
if (env.INKOS_LLM_API_FORMAT) llm.apiFormat = env.INKOS_LLM_API_FORMAT;
4849
config.llm = llm;
4950

0 commit comments

Comments
 (0)