diff --git a/README.md b/README.md index f7d4707..7908427 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ Deep, comprehensive research using the `sonar-deep-research` model. Ideal for th ### **perplexity_reason** Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Perfect for complex analytical tasks. +> [!TIP] +> Available as an optional parameter for **perplexity_reason** and **perplexity_research**: `strip_thinking` +> +> Set to `true` to remove `...` tags from the response, saving context tokens. Default: `false` + ## Configuration ### Get Your API Key diff --git a/index.test.ts b/index.test.ts index 21b9946..f0999c7 100644 --- a/index.test.ts +++ b/index.test.ts @@ -626,4 +626,43 @@ describe("Perplexity MCP Server", () => { expect(formatted).not.toContain("12345"); }); }); + + describe("strip_thinking parameter", () => { + it("should strip thinking tokens when true and keep them when false", async () => { + const mockResponse = { + choices: [ + { + message: { + content: "This is my reasoning process\n\nThe answer is 4.", + }, + }, + ], + }; + + // Test with stripThinking = true + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "What is 2+2?" }]; + const resultStripped = await performChatCompletion(messages, "sonar-reasoning-pro", true); + + expect(resultStripped).not.toContain(""); + expect(resultStripped).not.toContain(""); + expect(resultStripped).not.toContain("This is my reasoning process"); + expect(resultStripped).toContain("The answer is 4."); + + // Test with stripThinking = false + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const resultKept = await performChatCompletion(messages, "sonar-reasoning-pro", false); + + expect(resultKept).toContain("This is my reasoning process"); + expect(resultKept).toContain("The answer is 4."); + }); + }); }); diff --git a/index.ts b/index.ts index d80728b..5e7fa33 100644 --- a/index.ts +++ b/index.ts @@ -76,6 +76,10 @@ const PERPLEXITY_RESEARCH_TOOL: Tool = { }, description: "Array of conversation messages", }, + strip_thinking: { + type: "boolean", + description: "If true, removes ... tags and their content from the response to save context tokens. Default is false.", + }, }, required: ["messages"], }, @@ -112,6 +116,10 @@ const PERPLEXITY_REASON_TOOL: Tool = { }, description: "Array of conversation messages", }, + strip_thinking: { + type: "boolean", + description: "If true, removes ... tags and their content from the response to save context tokens. Default is false.", + }, }, required: ["messages"], }, @@ -188,18 +196,31 @@ function validateMessages(messages: any, toolName: string): void { } } +/** + * Strips thinking tokens (content within ... tags) from the response. + * This helps reduce context usage when the thinking process is not needed. + * + * @param {string} content - The content to process + * @returns {string} The content with thinking tokens removed + */ +function stripThinkingTokens(content: string): string { + return content.replace(/[\s\S]*?<\/think>/g, '').trim(); +} + /** * Performs a chat completion by sending a request to the Perplexity API. * Appends citations to the returned message content if they exist. * * @param {Array<{ role: string; content: string }>} messages - An array of message objects. * @param {string} model - The model to use for the completion. + * @param {boolean} stripThinking - If true, removes ... tags from the response. * @returns {Promise} The chat completion result with appended citations. * @throws Will throw an error if the API request fails. */ export async function performChatCompletion( messages: Array<{ role: string; content: string }>, - model: string = "sonar-pro" + model: string = "sonar-pro", + stripThinking: boolean = false ): Promise { // Read timeout fresh each time to respect env var changes const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10); @@ -271,6 +292,11 @@ export async function performChatCompletion( // Directly retrieve the main message content from the response let messageContent = firstChoice.message.content; + // Strip thinking tokens if requested + if (stripThinking) { + messageContent = stripThinkingTokens(messageContent); + } + // If citations are provided, append them to the message content if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) { messageContent += "\n\nCitations:\n"; @@ -433,7 +459,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "perplexity_research": { validateMessages(args.messages, "perplexity_research"); const messages = args.messages as Array<{ role: string; content: string }>; - const result = await performChatCompletion(messages, "sonar-deep-research"); + const stripThinking = typeof args.strip_thinking === "boolean" ? args.strip_thinking : false; + const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking); return { content: [{ type: "text", text: result }], isError: false, @@ -442,7 +469,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "perplexity_reason": { validateMessages(args.messages, "perplexity_reason"); const messages = args.messages as Array<{ role: string; content: string }>; - const result = await performChatCompletion(messages, "sonar-reasoning-pro"); + const stripThinking = typeof args.strip_thinking === "boolean" ? args.strip_thinking : false; + const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking); return { content: [{ type: "text", text: result }], isError: false,