Skip to content

Process thinking blocks for Anthropic with Bedrock provider #4453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Mar 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
44d91ec
add `ThinkingChatMessage` interface and handle thinking chunks in `Ba…
ferenci84 Mar 1, 2025
8c330cb
correct type errors
ferenci84 Mar 3, 2025
b4c1af0
Merge remote-tracking branch 'origin/anthropic_process_thinking_block…
ferenci84 Mar 3, 2025
7d047ca
Add reasoning and reasoningBudgetTokens to BaseCompletionOptions and …
ferenci84 Mar 3, 2025
ef66c22
add reasoning option to Anthropic class constructor
ferenci84 Mar 4, 2025
a2e4bdb
Update bedrock and use ConverseStream for Anthropic
ferenci84 Mar 4, 2025
a15d116
Adding thinking blocks and handle redacted_thinking
ferenci84 Mar 4, 2025
5037e4b
Merge branch 'anthropic_process_thinking_blocks2' into bedrock_reason…
ferenci84 Mar 5, 2025
e51a185
revert package changes
ferenci84 Mar 5, 2025
93602e3
Add ThinkingBlockPeek component and integrate it in Chat.tsx
ferenci84 Mar 5, 2025
2684d99
Merge branch 'anthropic_process_thinking_blocks2' into bedrock_reason…
ferenci84 Mar 9, 2025
334e35f
Handle thinking and redacted thinking in Bedrock response processing
ferenci84 Mar 9, 2025
11571e9
Merge branch 'main' into bedrock_reasoning_2
ferenci84 Mar 9, 2025
c73f72f
fix: tool use call
ferenci84 Mar 9, 2025
bb9ab5e
update Bedrock and fix types
ferenci84 Mar 9, 2025
b56295b
Merge branch 'main' into anthropic_process_thinking_blocks2
sestinj Mar 10, 2025
7a7b29f
update hideActionSpace condition to include "thinking" role
ferenci84 Mar 10, 2025
bf1faed
Merge remote-tracking branch 'origin/anthropic_process_thinking_block…
ferenci84 Mar 10, 2025
a6e9382
Merge branch 'anthropic_process_thinking_blocks2' into bedrock_reason…
ferenci84 Mar 10, 2025
288ca1b
adjust margin values for icons in ThinkingBlockPeek component
ferenci84 Mar 10, 2025
83aa729
Merge branch 'anthropic_process_thinking_blocks2' into bedrock_reason…
ferenci84 Mar 10, 2025
e9a3f97
add inProgress prop and update ThinkingBlockPeek component to handle …
ferenci84 Mar 11, 2025
7949f9a
Merge branch 'anthropic_process_thinking_blocks2' into bedrock_reason…
ferenci84 Mar 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions binary/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 34 additions & 23 deletions core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ export interface IndexingProgressUpdate {
desc: string;
shouldClearIndexes?: boolean;
status:
| "loading"
| "indexing"
| "done"
| "failed"
| "paused"
| "disabled"
| "cancelled";
| "loading"
| "indexing"
| "done"
| "failed"
| "paused"
| "disabled"
| "cancelled";
debugInfo?: string;
}

Expand Down Expand Up @@ -312,7 +312,7 @@ export interface CompletionOptions extends BaseCompletionOptions {
model: string;
}

export type ChatMessageRole = "user" | "assistant" | "system" | "tool";
export type ChatMessageRole = "user" | "assistant" | "thinking" | "system" | "tool";

export type TextMessagePart = {
type: "text";
Expand Down Expand Up @@ -357,6 +357,14 @@ export interface UserChatMessage {
content: MessageContent;
}

export interface ThinkingChatMessage {
role: "thinking";
content: MessageContent;
signature?: string;
redactedThinking?: string;
toolCalls?: ToolCallDelta[];
}

export interface AssistantChatMessage {
role: "assistant";
content: MessageContent;
Expand All @@ -371,6 +379,7 @@ export interface SystemChatMessage {
export type ChatMessage =
| UserChatMessage
| AssistantChatMessage
| ThinkingChatMessage
| SystemChatMessage
| ToolResultChatMessage;

Expand Down Expand Up @@ -679,10 +688,10 @@ export interface IDE {
getCurrentFile(): Promise<
| undefined
| {
isUntitled: boolean;
path: string;
contents: string;
}
isUntitled: boolean;
path: string;
contents: string;
}
>;

getLastFileSaveTimestamp?(): number;
Expand Down Expand Up @@ -866,11 +875,11 @@ export interface CustomCommand {
export interface Prediction {
type: "content";
content:
| string
| {
type: "text";
text: string;
}[];
| string
| {
type: "text";
text: string;
}[];
}

export interface ToolExtras {
Expand Down Expand Up @@ -921,6 +930,8 @@ export interface BaseCompletionOptions {
prediction?: Prediction;
tools?: Tool[];
toolChoice?: ToolChoice;
reasoning?: boolean;
reasoningBudgetTokens?: number;
}

export interface ModelCapability {
Expand Down Expand Up @@ -1208,9 +1219,9 @@ export interface Config {
embeddingsProvider?: EmbeddingsProviderDescription | ILLM;
/** The model that Continue will use for tab autocompletions. */
tabAutocompleteModel?:
| CustomLLM
| ModelDescription
| (CustomLLM | ModelDescription)[];
| CustomLLM
| ModelDescription
| (CustomLLM | ModelDescription)[];
/** Options for tab autocomplete */
tabAutocompleteOptions?: Partial<TabAutocompleteOptions>;
/** UI styles customization */
Expand Down Expand Up @@ -1302,9 +1313,9 @@ export type PackageDetailsSuccess = PackageDetails & {
export type PackageDocsResult = {
packageInfo: ParsedPackageInfo;
} & (
| { error: string; details?: never }
| { details: PackageDetailsSuccess; error?: never }
);
| { error: string; details?: never }
| { details: PackageDetailsSuccess; error?: never }
);

export interface TerminalOptions {
reuseTerminal?: boolean;
Expand Down
12 changes: 7 additions & 5 deletions core/llm/countTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class LlamaEncoding implements Encoding {
}

class NonWorkerAsyncEncoder implements AsyncEncoder {
constructor(private readonly encoding: Encoding) {}
constructor(private readonly encoding: Encoding) { }

async close(): Promise<void> {}
async close(): Promise<void> { }

async encode(text: string): Promise<number[]> {
return this.encoding.encode(text);
Expand Down Expand Up @@ -366,6 +366,7 @@ function chatMessageIsEmpty(message: ChatMessage): boolean {
message.content.trim() === "" &&
!message.toolCalls
);
case "thinking":
case "tool":
return false;
}
Expand All @@ -383,8 +384,8 @@ function compileChatMessages(
): ChatMessage[] {
let msgsCopy = msgs
? msgs
.map((msg) => ({ ...msg }))
.filter((msg) => !chatMessageIsEmpty(msg) && msg.role !== "system")
.map((msg) => ({ ...msg }))
.filter((msg) => !chatMessageIsEmpty(msg) && msg.role !== "system")
: [];

msgsCopy = addSpaceToAnyEmptyMessages(msgsCopy);
Expand Down Expand Up @@ -469,5 +470,6 @@ export {
pruneLinesFromTop,
pruneRawPromptFromTop,
pruneStringFromBottom,
pruneStringFromTop,
pruneStringFromTop
};

39 changes: 30 additions & 9 deletions core/llm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@ export abstract class BaseLLM implements ILLM {
options.completionOptions?.maxTokens ??
(llmInfo?.maxCompletionTokens
? Math.min(
llmInfo.maxCompletionTokens,
// Even if the model has a large maxTokens, we don't want to use that every time,
// because it takes away from the context length
this.contextLength / 4,
)
llmInfo.maxCompletionTokens,
// Even if the model has a large maxTokens, we don't want to use that every time,
// because it takes away from the context length
this.contextLength / 4,
)
: DEFAULT_MAX_TOKENS),
};
this.requestOptions = options.requestOptions;
Expand Down Expand Up @@ -780,6 +780,7 @@ export abstract class BaseLLM implements ILLM {
}
}

let thinking = "";
let completion = "";
let citations: null | string[] = null

Expand Down Expand Up @@ -834,8 +835,16 @@ export abstract class BaseLLM implements ILLM {
signal,
completionOptions,
)) {
completion += chunk.content;
yield chunk;

if (chunk.role === "assistant") {
completion += chunk.content;
yield chunk;
}

if (chunk.role === "thinking") {
thinking += chunk.content;
yield chunk;
}
}
}
}
Expand All @@ -847,6 +856,18 @@ export abstract class BaseLLM implements ILLM {
this._logTokensGenerated(completionOptions.model, prompt, completion);

if (logEnabled && this.writeLog) {
if (thinking) {
await this.writeLog(`Thinking:\n${thinking}\n\n`);
}
/*
TODO: According to: https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
During tool use, you must pass thinking and redacted_thinking blocks back to the API,
and you must include the complete unmodified block back to the API. This is critical
for maintaining the model's reasoning flow and conversation integrity.

On the other hand, adding thinking and redacted_thinking blocks are ignored on subsequent
requests when not using tools, so it's the simplest option to always add to history.
*/
await this.writeLog(`Completion:\n${completion}\n\n`);

if (citations) {
Expand Down Expand Up @@ -920,15 +941,15 @@ export abstract class BaseLLM implements ILLM {
);
}

protected async *_streamComplete(
protected async * _streamComplete(
prompt: string,
signal: AbortSignal,
options: CompletionOptions,
): AsyncGenerator<string> {
throw new Error("Not implemented");
}

protected async *_streamChat(
protected async * _streamChat(
messages: ChatMessage[],
signal: AbortSignal,
options: CompletionOptions,
Expand Down
50 changes: 41 additions & 9 deletions core/llm/llms/Anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ class Anthropic extends BaseLLM {
description: tool.function.description,
input_schema: tool.function.parameters,
})),
thinking: options.reasoning ? {
type: "enabled",
budget_tokens: options.reasoningBudgetTokens,
} : undefined,
tool_choice: options.toolChoice
? {
type: "tool",
name: options.toolChoice.function.name,
}
type: "tool",
name: options.toolChoice.function.name,
}
: undefined,
};

Expand Down Expand Up @@ -63,6 +67,23 @@ class Anthropic extends BaseLLM {
input: JSON.parse(toolCall.function?.arguments || "{}"),
})),
};
} else if (message.role === "thinking" && !message.redactedThinking) {
return {
role: "assistant",
content: [{
type: "thinking",
thinking: message.content,
signature: message.signature
}]
};
} else if (message.role === "thinking" && message.redactedThinking) {
return {
role: "assistant",
content: [{
type: "redacted_thinking",
data: message.redactedThinking
}]
};
}

if (typeof message.content === "string") {
Expand Down Expand Up @@ -174,12 +195,12 @@ class Anthropic extends BaseLLM {
messages: msgs,
system: shouldCacheSystemMessage
? [
{
type: "text",
text: this.systemMessage,
cache_control: { type: "ephemeral" },
},
]
{
type: "text",
text: this.systemMessage,
cache_control: { type: "ephemeral" },
},
]
: systemMessage,
}),
signal,
Expand Down Expand Up @@ -216,13 +237,24 @@ class Anthropic extends BaseLLM {
lastToolUseId = value.content_block.id;
lastToolUseName = value.content_block.name;
}
// handle redacted thinking
if (value.content_block.type === "redacted_thinking") {
console.log("redacted thinking", value.content_block.data);
yield { role: "thinking", content: "", redactedThinking: value.content_block.data };
}
break;
case "content_block_delta":
// https://docs.anthropic.com/en/api/messages-streaming#delta-types
switch (value.delta.type) {
case "text_delta":
yield { role: "assistant", content: value.delta.text };
break;
case "thinking_delta":
yield { role: "thinking", content: value.delta.thinking };
break;
case "signature_delta":
yield { role: "thinking", content: "", signature: value.delta.signature };
break;
case "input_json_delta":
if (!lastToolUseId || !lastToolUseName) {
throw new Error("No tool use found");
Expand Down
Loading
Loading