Skip to content

Commit c82fcf0

Browse files
authored
Merge pull request #65 from perplexityai/kesku/strip-thinking-tokens
2 parents 7bb417b + f2a05d6 commit c82fcf0

File tree

3 files changed

+75
-3
lines changed

3 files changed

+75
-3
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ Deep, comprehensive research using the `sonar-deep-research` model. Ideal for th
1818
### **perplexity_reason**
1919
Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Perfect for complex analytical tasks.
2020

21+
> [!TIP]
22+
> Available as an optional parameter for **perplexity_reason** and **perplexity_research**: `strip_thinking`
23+
>
24+
> Set to `true` to remove `<think>...</think>` tags from the response, saving context tokens. Default: `false`
25+
2126
## Configuration
2227

2328
### Get Your API Key

index.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,4 +626,43 @@ describe("Perplexity MCP Server", () => {
626626
expect(formatted).not.toContain("12345");
627627
});
628628
});
629+
630+
describe("strip_thinking parameter", () => {
631+
it("should strip thinking tokens when true and keep them when false", async () => {
632+
const mockResponse = {
633+
choices: [
634+
{
635+
message: {
636+
content: "<think>This is my reasoning process</think>\n\nThe answer is 4.",
637+
},
638+
},
639+
],
640+
};
641+
642+
// Test with stripThinking = true
643+
global.fetch = vi.fn().mockResolvedValue({
644+
ok: true,
645+
json: async () => mockResponse,
646+
} as Response);
647+
648+
const messages = [{ role: "user", content: "What is 2+2?" }];
649+
const resultStripped = await performChatCompletion(messages, "sonar-reasoning-pro", true);
650+
651+
expect(resultStripped).not.toContain("<think>");
652+
expect(resultStripped).not.toContain("</think>");
653+
expect(resultStripped).not.toContain("This is my reasoning process");
654+
expect(resultStripped).toContain("The answer is 4.");
655+
656+
// Test with stripThinking = false
657+
global.fetch = vi.fn().mockResolvedValue({
658+
ok: true,
659+
json: async () => mockResponse,
660+
} as Response);
661+
662+
const resultKept = await performChatCompletion(messages, "sonar-reasoning-pro", false);
663+
664+
expect(resultKept).toContain("<think>This is my reasoning process</think>");
665+
expect(resultKept).toContain("The answer is 4.");
666+
});
667+
});
629668
});

index.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ const PERPLEXITY_RESEARCH_TOOL: Tool = {
7676
},
7777
description: "Array of conversation messages",
7878
},
79+
strip_thinking: {
80+
type: "boolean",
81+
description: "If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.",
82+
},
7983
},
8084
required: ["messages"],
8185
},
@@ -112,6 +116,10 @@ const PERPLEXITY_REASON_TOOL: Tool = {
112116
},
113117
description: "Array of conversation messages",
114118
},
119+
strip_thinking: {
120+
type: "boolean",
121+
description: "If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.",
122+
},
115123
},
116124
required: ["messages"],
117125
},
@@ -188,18 +196,31 @@ function validateMessages(messages: any, toolName: string): void {
188196
}
189197
}
190198

199+
/**
200+
* Strips thinking tokens (content within <think>...</think> tags) from the response.
201+
* This helps reduce context usage when the thinking process is not needed.
202+
*
203+
* @param {string} content - The content to process
204+
* @returns {string} The content with thinking tokens removed
205+
*/
206+
function stripThinkingTokens(content: string): string {
207+
return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
208+
}
209+
191210
/**
192211
* Performs a chat completion by sending a request to the Perplexity API.
193212
* Appends citations to the returned message content if they exist.
194213
*
195214
* @param {Array<{ role: string; content: string }>} messages - An array of message objects.
196215
* @param {string} model - The model to use for the completion.
216+
* @param {boolean} stripThinking - If true, removes <think>...</think> tags from the response.
197217
* @returns {Promise<string>} The chat completion result with appended citations.
198218
* @throws Will throw an error if the API request fails.
199219
*/
200220
export async function performChatCompletion(
201221
messages: Array<{ role: string; content: string }>,
202-
model: string = "sonar-pro"
222+
model: string = "sonar-pro",
223+
stripThinking: boolean = false
203224
): Promise<string> {
204225
// Read timeout fresh each time to respect env var changes
205226
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
@@ -271,6 +292,11 @@ export async function performChatCompletion(
271292
// Directly retrieve the main message content from the response
272293
let messageContent = firstChoice.message.content;
273294

295+
// Strip thinking tokens if requested
296+
if (stripThinking) {
297+
messageContent = stripThinkingTokens(messageContent);
298+
}
299+
274300
// If citations are provided, append them to the message content
275301
if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) {
276302
messageContent += "\n\nCitations:\n";
@@ -433,7 +459,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
433459
case "perplexity_research": {
434460
validateMessages(args.messages, "perplexity_research");
435461
const messages = args.messages as Array<{ role: string; content: string }>;
436-
const result = await performChatCompletion(messages, "sonar-deep-research");
462+
const stripThinking = typeof args.strip_thinking === "boolean" ? args.strip_thinking : false;
463+
const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking);
437464
return {
438465
content: [{ type: "text", text: result }],
439466
isError: false,
@@ -442,7 +469,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
442469
case "perplexity_reason": {
443470
validateMessages(args.messages, "perplexity_reason");
444471
const messages = args.messages as Array<{ role: string; content: string }>;
445-
const result = await performChatCompletion(messages, "sonar-reasoning-pro");
472+
const stripThinking = typeof args.strip_thinking === "boolean" ? args.strip_thinking : false;
473+
const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking);
446474
return {
447475
content: [{ type: "text", text: result }],
448476
isError: false,

0 commit comments

Comments
 (0)