Skip to content

Commit

Permalink
Merge pull request #43 from kyaukyuai/feat/perplexity
Browse files Browse the repository at this point in the history
✨ (search): integrate Perplexity API for enhanced search functionality
  • Loading branch information
kyaukyuai authored Feb 28, 2025
2 parents a4a3e98 + 0e58322 commit d80bb6c
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 d80bb6c

Please sign in to comment.