Skip to content

Commit

Permalink
✨ (search): integrate Perplexity API for enhanced search functionality
Browse files Browse the repository at this point in the history
Add Perplexity API key to .env.example for configuration. Update
configuration to use Perplexity as the default search API. Implement
Perplexity search in both async and sync deep research agents. Ensure
robust handling of section content and feedback attributes.
  • Loading branch information
kyaukyuai committed Feb 28, 2025
1 parent a4a3e98 commit 0e58322
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 26 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ENVIRONMENT=development # development or production
# AI Service Configuration
ANTHROPIC_API_KEY=your-anthropic-api-key # Required for Claude AI integration
TAVILY_API_KEY=your-tavily-api-key # Required for search functionality
PERPLEXITY_API_KEY=your-perplexity-api-key # Required for search functionality

# LangGraph Configuration
LANGGRAPH_URL=http://localhost:2024 # LangGraph service endpoint
Expand Down
4 changes: 2 additions & 2 deletions slack_ai_agent/agents/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class Configuration:
writer_provider: WriterProvider = (
WriterProvider.ANTHROPIC
) # Defaults to Anthropic as provider
writer_model: str = "claude-3-5-sonnet-latest" # Defaults to Anthropic as provider
search_api: SearchAPI = SearchAPI.TAVILY # Default to TAVILY
writer_model: str = "claude-3-7-sonnet-latest" # Defaults to Anthropic as provider
search_api: SearchAPI = SearchAPI.PERPLEXITY # Default to TAVILY

@classmethod
def from_runnable_config(
Expand Down
11 changes: 6 additions & 5 deletions slack_ai_agent/agents/deep_research_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from slack_ai_agent.agents.prompts.deep_research_section_writer_instructions import (
section_writer_instructions,
)
from slack_ai_agent.agents.tools.perplexity_search import perplexity_search
from slack_ai_agent.agents.tools.tavily_search import deduplicate_and_format_sources
from slack_ai_agent.agents.tools.tavily_search import tavily_search_async

Expand Down Expand Up @@ -197,11 +198,11 @@ async def generate_report_plan(state: ReportState, config: RunnableConfig):
source_str = deduplicate_and_format_sources(
search_results, max_tokens_per_source=1000, include_raw_content=False
)
# elif search_api == "perplexity":
# search_results = perplexity_search(query_list)
# source_str = deduplicate_and_format_sources(
# search_results, max_tokens_per_source=1000, include_raw_content=False
# )
elif search_api == "perplexity":
search_results = perplexity_search(query_list)
source_str = deduplicate_and_format_sources(
search_results, max_tokens_per_source=1000, include_raw_content=False
)
else:
raise ValueError(f"Unsupported search API: {configurable.search_api}")

Expand Down
48 changes: 29 additions & 19 deletions slack_ai_agent/agents/sync_deep_research_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from slack_ai_agent.agents.prompts.deep_research_section_writer_instructions import (
section_writer_instructions,
)
from slack_ai_agent.agents.tools.perplexity_search import perplexity_search
from slack_ai_agent.agents.tools.tavily_search import deduplicate_and_format_sources
from slack_ai_agent.agents.tools.tavily_search import (
tavily_search, # 非同期版ではなく同期版を使用
Expand Down Expand Up @@ -195,15 +196,15 @@ def generate_report_plan(state: ReportState, config: RunnableConfig):

# Search the web - 同期バージョンを使用
if search_api == "tavily":
search_results = tavily_search(query_list) # 非同期から同期に変更
search_results = tavily_search(query=query_list)
source_str = deduplicate_and_format_sources(
search_results, max_tokens_per_source=1000, include_raw_content=False
)
elif search_api == "perplexity":
search_results = perplexity_search(search_queries=query_list)
source_str = deduplicate_and_format_sources(
search_results, max_tokens_per_source=1000, include_raw_content=False
)
# elif search_api == "perplexity":
# search_results = perplexity_search(query_list)
# source_str = deduplicate_and_format_sources(
# search_results, max_tokens_per_source=1000, include_raw_content=False
# )
else:
raise ValueError(f"Unsupported search API: {configurable.search_api}")

Expand Down Expand Up @@ -375,15 +376,15 @@ def search_web(state: SectionState, config: RunnableConfig):

# Search the web - 同期バージョン
if search_api == "tavily":
search_results = tavily_search(query_list) # 非同期から同期に変更
search_results = tavily_search(query=query_list) # 非同期から同期に変更
source_str = deduplicate_and_format_sources(
search_results, max_tokens_per_source=5000, include_raw_content=True
)
# elif search_api == "perplexity":
# search_results = perplexity_search(query_list)
# source_str = deduplicate_and_format_sources(
# search_results, max_tokens_per_source=5000, include_raw_content=False
# )
elif search_api == "perplexity":
search_results = perplexity_search(search_queries=query_list)
source_str = deduplicate_and_format_sources(
search_results, max_tokens_per_source=5000, include_raw_content=False
)
else:
raise ValueError(f"Unsupported search API: {configurable.search_api}")

Expand Down Expand Up @@ -431,7 +432,11 @@ def write_section(
)

# Write content to the section object
section.content = section_content.content
# Ensure we're getting a string from section_content
if hasattr(section_content, "content"):
section.content = str(section_content.content)
else:
section.content = str(section_content)

# Grade prompt
section_grader_instructions_formatted = section_grader_instructions.format(
Expand All @@ -449,16 +454,17 @@ def write_section(
]
)

if (
feedback.grade == "pass"
or state["search_iterations"] >= configurable.max_search_depth
):
# Safely access feedback attributes
grade = getattr(feedback, "grade", None)
follow_up_queries = getattr(feedback, "follow_up_queries", [])

if grade == "pass" or state["search_iterations"] >= configurable.max_search_depth:
# Publish the section to completed sections
return Command(update={"completed_sections": [section]}, goto=END)
else:
# Update the existing section with new content and update search queries
return Command(
update={"search_queries": feedback.follow_up_queries, "section": section},
update={"search_queries": follow_up_queries, "section": section},
goto="search_web",
)

Expand Down Expand Up @@ -498,7 +504,11 @@ def write_final_sections(state: SectionState, config: RunnableConfig):
)

# Write content to section
section.content = section_content.content
# Ensure we're getting a string from section_content
if hasattr(section_content, "content"):
section.content = str(section_content.content)
else:
section.content = str(section_content)

# Write the updated section to completed sections
return {"completed_sections": [section]}
Expand Down
100 changes: 100 additions & 0 deletions slack_ai_agent/agents/tools/perplexity_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os

import requests # type: ignore
from langsmith import traceable


@traceable
def perplexity_search(search_queries):
"""Search the web using the Perplexity API.
Args:
search_queries (List[str]): List of search query strings to process
Returns:
List[dict]: List of search responses from Perplexity API, one per query. Each response has format:
{
'query': str, # The original search query
'follow_up_questions': None,
'answer': None,
'images': list,
'results': [ # List of search results
{
'title': str, # Title of the search result
'url': str, # URL of the result
'content': str, # Summary/snippet of content
'score': float, # Relevance score
'raw_content': str|None # Full content or None for secondary citations
},
...
]
}
"""

headers = {
"accept": "application/json",
"content-type": "application/json",
"Authorization": f"Bearer {os.getenv('PERPLEXITY_API_KEY')}",
}

search_docs = []
for query in search_queries:
payload = {
"model": "sonar-pro",
"messages": [
{
"role": "system",
"content": "Search the web and provide factual information with sources.",
},
{"role": "user", "content": query},
],
}

response = requests.post(
"https://api.perplexity.ai/chat/completions", headers=headers, json=payload
)
response.raise_for_status() # Raise exception for bad status codes

# Parse the response
data = response.json()
content = data["choices"][0]["message"]["content"]
citations = data.get("citations", ["https://perplexity.ai"])

# Create results list for this query
results = []

# First citation gets the full content
results.append(
{
"title": "Perplexity Search, Source 1",
"url": citations[0],
"content": content,
"raw_content": content,
"score": 1.0, # Adding score to match Tavily format
}
)

# Add additional citations without duplicating content
for i, citation in enumerate(citations[1:], start=2):
results.append(
{
"title": f"Perplexity Search, Source {i}",
"url": citation,
"content": "See primary source for full content",
"raw_content": None,
"score": 0.5, # Lower score for secondary sources
}
)

# Format response to match Tavily structure
search_docs.append(
{
"query": query,
"follow_up_questions": None,
"answer": None,
"images": [],
"results": results,
}
)

return search_docs

0 comments on commit 0e58322

Please sign in to comment.