├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── README.md ├── assets └── sonar.avif └── utils ├── extract_json_reasoning_models.py └── parse_json.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report an issue with the Perplexity Sonar API 4 | title: "[Bug] " 5 | labels: ["bug"] 6 | assignees: [] 7 | --- 8 | 9 | ## 🐛 Describe the Bug 10 | A clear and concise description of what the bug is. 11 | 12 | ## ✅ Expected Behavior 13 | What you expected to happen. 14 | 15 | ## ❌ Actual Behavior 16 | What actually happened. 17 | 18 | ## 🔄 Steps to Reproduce 19 | 1. Call the API with the following request: 20 | 2. Observe the unexpected behavior. 21 | 22 | ## 📌 API Request & Response (if applicable) 23 | 24 | ## 🌍 Environment 25 | - **API Version:** [e.g., sonar-3.1] 26 | - **SDK (if applicable):** [e.g., Python SDK v0.5] 27 | - **Operating System:** [e.g., MacOS, Linux, Windows] 28 | - **Authentication Type:** [e.g., API Key, OAuth] 29 | 30 | ## 📎 Logs or Screenshots (if applicable) 31 | Add any logs or screenshots that can help debug the issue. 32 | 33 | ## 📝 Additional Context 34 | Add any other context about the problem here. 35 | 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new feature or improvement for the Perplexity Sonar API 4 | title: "[Feature Request] " 5 | labels: ["enhancement"] 6 | assignees: [] 7 | --- 8 | 9 | ## 🚀 Feature Request 10 | A clear and concise description of the feature or improvement you’d like to see. 11 | 12 | ## 🔍 Problem Statement 13 | What problem or limitation does this feature aim to solve? Please describe the issue you’re facing and how this feature would help. 14 | 15 | ## 💡 Proposed Solution 16 | Describe how this feature should work. If possible, provide an example or use case demonstrating how it would be used. 17 | 18 | ## 📌 API Impact 19 | - **Which API component is affected?** [e.g., chat completions, retrieval, search filters] 20 | - **Is this related to a specific model?** [e.g., Sonar Deep Research] 21 | - **Would this require new API parameters or changes to existing ones?** 22 | 23 | ## 🔄 Alternatives Considered 24 | Have you explored any workarounds? If so, what were they, and why are they insufficient? 25 | 26 | ## 📎 Additional Context 27 | Add any other relevant information, links, or screenshots that can help us better understand your request. 28 | 29 | --- 30 | 📧 For direct inquiries, please reach out to **api@perplexity.ai** 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perplexity Sonar API Discussion 2 | ## UPDATE: Sunsetting Repository (May 19th, 2025) 3 | 4 | Thanks for being a user of Sonar! We're currently in the process of migrating our discussions page to a new home: [community.perplexity.ai](community.perplexity.ai). This repository will be shutting down soon, please post your thoughts and questions there! 5 | 6 | 7 | 8 | ![Sonar API](assets/sonar.avif) 9 | 10 | ## Purpose of This Repository 11 | This repository serves as a **central hub** for: 12 | - Discussing **API capabilities**, best practices, and use cases. 13 | - Reporting **bugs** and tracking improvements. 14 | - Providing feedback on **API documentation and feature requests**. 15 | - Engaging with the Perplexity developer community. 16 | 17 | If you're integrating the **Sonar API** into your applications, this is the place to **share your experiences and collaborate** with others. 18 | 19 | ## How to Participate 20 | ### 1. Ask Questions & Start Discussions 21 | - Use the **[Discussions](https://github.com/ppl-ai/api-discussion/discussions)** tab to start a conversation about API features, best practices, and use cases. 22 | - Share your insights, challenges, and implementations. 23 | 24 | ### 2. Report Bugs 25 | - If you encounter an issue, please **[open a bug report](https://github.com/ppl-ai/api-discussion/issues/new/choose)** using the structured issue template. 26 | - Provide **clear reproduction steps** and **API request/response examples** to help with investigation. 27 | 28 | ### 3. Suggest Features 29 | - Have an idea to improve the API? Start a **feature request discussion** [here](https://github.com/ppl-ai/api-discussion/discussions). 30 | - Your feedback helps shape the API roadmap. 31 | 32 | ## Giving Us Feedback 33 | Your feedback is valuable in helping us improve the Sonar API. If we have assisted you in resolving an issue, we would appreciate your thoughts on our interactions. Please leave your feedback [here](https://perplexity.typeform.com/to/anY4jEzX). 34 | 35 | ## Need Direct Support? 36 | For **official API support**, contact our team at: 37 | **api@perplexity.ai** 38 | 39 | ## Resources 40 | - **API Documentation**: [Perplexity API Docs](https://github.com/ppl-ai/api-docs) 41 | - **Changelog**: [Latest Updates](https://docs.perplexity.ai/changelog/changelog) 42 | 43 | ## Contributing 44 | We encourage contributions from the community. If you’d like to share improvements, provide feedback, or help fellow developers, feel free to engage in **issues and discussions**. 45 | 46 | --- 47 | 48 | Thank you for being a part of the **Sonar API community**. We look forward to your contributions and discussions. 49 | 50 | -------------------------------------------------------------------------------- /assets/sonar.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ppl-ai/api-discussion/22ba5cdfadebd2c531ecad84b825c947b9ef964e/assets/sonar.avif -------------------------------------------------------------------------------- /utils/extract_json_reasoning_models.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Any, Dict 3 | 4 | def extract_valid_json(response: Dict[str, Any]) -> Dict[str, Any]: 5 | """ 6 | Extracts and returns only the valid JSON part from a response object. 7 | 8 | This function assumes that the response has a structure where the valid JSON 9 | is included in the 'content' field of the first choice's message, after the 10 | closing "" marker. Any markdown code fences (e.g. ```json) are stripped. 11 | 12 | Parameters: 13 | response (dict): The full API response object. 14 | 15 | Returns: 16 | dict: The parsed JSON object extracted from the content. 17 | 18 | Raises: 19 | ValueError: If no valid JSON can be parsed from the content. 20 | """ 21 | # Navigate to the 'content' field; adjust if your structure differs. 22 | content = ( 23 | response 24 | .get("choices", [{}])[0] 25 | .get("message", {}) 26 | .get("content", "") 27 | ) 28 | 29 | # Find the index of the closing tag. 30 | marker = "" 31 | idx = content.rfind(marker) 32 | 33 | if idx == -1: 34 | # If marker not found, try parsing the entire content. 35 | try: 36 | return json.loads(content) 37 | except json.JSONDecodeError as e: 38 | raise ValueError("No marker found and content is not valid JSON") from e 39 | 40 | # Extract the substring after the marker. 41 | json_str = content[idx + len(marker):].strip() 42 | 43 | # Remove markdown code fence markers if present. 44 | if json_str.startswith("```json"): 45 | json_str = json_str[len("```json"):].strip() 46 | if json_str.startswith("```"): 47 | json_str = json_str[3:].strip() 48 | if json_str.endswith("```"): 49 | json_str = json_str[:-3].strip() 50 | 51 | try: 52 | parsed_json = json.loads(json_str) 53 | return parsed_json 54 | except json.JSONDecodeError as e: 55 | raise ValueError("Failed to parse valid JSON from response content") from e 56 | -------------------------------------------------------------------------------- /utils/parse_json.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from typing import Any, Dict 4 | 5 | def extract_and_inject_json(completion: Dict[str, Any]) -> Dict[str, Any]: 6 | """ 7 | Extracts the valid JSON portion from the 'content' field in the first choice's message 8 | of the completion object and injects the parsed JSON back into that message. 9 | 10 | This function handles cases where the content may include extraneous text such as 11 | markdown code fences (e.g., "```json"), additional text before/after the JSON, or 12 | other noise. If the JSON is present and valid, it will be extracted; if it is malformed, 13 | an error will be raised. 14 | 15 | Parameters: 16 | completion (dict): The completion object containing a "choices" list with a message. 17 | 18 | Returns: 19 | dict: The updated completion object with the "content" field replaced by the parsed JSON. 20 | 21 | Raises: 22 | ValueError: If no valid JSON can be extracted or parsed. 23 | """ 24 | try: 25 | # Retrieve the raw content from the first choice's message. 26 | raw_content: str = completion["choices"][0]["message"]["content"] 27 | raw_content = raw_content.strip() 28 | 29 | # Remove markdown code fences if present. 30 | if raw_content.startswith("```"): 31 | lines = raw_content.splitlines() 32 | if lines[0].startswith("```"): 33 | lines = lines[1:] 34 | if lines and lines[-1].startswith("```"): 35 | lines = lines[:-1] 36 | raw_content = "\n".join(lines).strip() 37 | 38 | # Try extracting JSON using the first and last brace positions. 39 | start = raw_content.find("{") 40 | end = raw_content.rfind("}") 41 | json_candidate = None 42 | if start != -1 and end != -1 and start < end: 43 | json_candidate = raw_content[start:end+1].strip() 44 | 45 | # If substring extraction failed or candidate is unparseable, try regex fallback. 46 | if json_candidate: 47 | try: 48 | parsed_json = json.loads(json_candidate) 49 | except json.JSONDecodeError: 50 | match = re.search(r"({.*})", raw_content, re.DOTALL) 51 | if match: 52 | json_candidate = match.group(1).strip() 53 | parsed_json = json.loads(json_candidate) 54 | else: 55 | raise ValueError("No valid JSON object found via regex.") 56 | else: 57 | match = re.search(r"({.*})", raw_content, re.DOTALL) 58 | if match: 59 | json_candidate = match.group(1).strip() 60 | parsed_json = json.loads(json_candidate) 61 | else: 62 | raise ValueError("No JSON object found in content.") 63 | 64 | # Inject the parsed JSON back into the completion object. 65 | completion["choices"][0]["message"]["content"] = parsed_json 66 | return completion 67 | except Exception as e: 68 | raise ValueError(f"Error extracting valid JSON from content: {e}") 69 | --------------------------------------------------------------------------------