File size: 5,362 Bytes
7c5229e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import List

app = FastAPI(
    title="Perplexity-like API",
    description="An API that uses web search to answer questions with citations.",
    version="1.0.0"
)

# --- API Configuration ---
TYPEGPT_API_URL = "https://api.typegpt.net/v1/chat/completions"
TYPEGPT_API_KEY = "sk-oPdaZC7n1JlDq0sJ5NSSyHe7sYaeAXeEuj0wX4Lk8hlOGPF8"
SEARCH_API_URL = "https://searchapi.snapzion.com/search"


# --- System Prompt ---
# This prompt guides the AI to behave like a factual research assistant.
SYSTEM_PROMPT = """
You are an expert AI research assistant. Your primary goal is to provide accurate, comprehensive, and helpful answers based ONLY on the provided search results.

Instructions:
1.  Carefully analyze the user's query and the provided search results.
2.  Synthesize an answer directly from the information found in the search results.
3.  For every statement or piece of information you provide, you MUST cite the corresponding search result number in the format `[<number>]`.
4.  If multiple sources support a statement, you can cite them like `[1, 2]`.
5.  If the search results do not contain enough information to answer the query, you must explicitly state that you could not find the information in the provided context.
6.  Do not use any prior knowledge or information outside of the provided search results.
7.  Structure your response in a clear and easy-to-read format. Start with a direct answer, followed by a more detailed explanation.
"""

# --- Pydantic Models for API Request/Response ---

class ChatMessage(BaseModel):
    role: str
    content: str

class ChatCompletionRequest(BaseModel):
    messages: List[ChatMessage] = Field(..., example=[{"role": "user", "content": "What are the benefits of learning Python?"}])
    model: str = "gpt-4.1-mini" # Model is fixed but included for compatibility

class Choice(BaseModel):
    message: ChatMessage

class ChatCompletionResponse(BaseModel):
    choices: List[Choice]
    search_results: List[dict]


# --- API Endpoint ---

@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def chat_completions(request: ChatCompletionRequest):
    """
    Takes a user's chat history, performs a web search based on the latest query,
    and uses the TypeGPT model to generate a factual, cited response.
    """
    if not request.messages or request.messages[-1].role != "user":
        raise HTTPException(status_code=400, detail="Invalid request. The last message must be from the 'user'.")

    user_query = request.messages[-1].content

    async with httpx.AsyncClient(timeout=30.0) as client:
        # 1. Perform a web search
        try:
            search_params = {"keywords": user_query}
            search_response = await client.get(SEARCH_API_URL, params=search_params)
            search_response.raise_for_status()
            search_results = search_response.json()
        except httpx.RequestError as e:
            raise HTTPException(status_code=502, detail=f"Error calling the search API: {e}")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Failed to process search results: {e}")

        # 2. Format search results into a context for the language model
        context = ""
        for i, result in enumerate(search_results[:7]):  # Use top 7 results for richer context
            context += f"Source [{i+1}]:\nTitle: {result.get('title', 'N/A')}\nSnippet: {result.get('snippet', '')}\nURL: {result.get('url', 'N/A')}\n\n"

        # 3. Construct the prompt for the language model
        final_prompt = f"""
        **Search Results:**
        {context}

        **User Query:** "{user_query}"

        Please provide a comprehensive answer based on the search results above, following all instructions.
        """

        # 4. Get the response from the TypeGPT language model
        try:
            headers = {
                "Authorization": f"Bearer {TYPEGPT_API_KEY}",
                "Content-Type": "application/json"
            }
            # The payload now includes the system prompt and the user prompt with context
            payload = {
                "model": "gpt-4.1-mini",
                "messages": [
                    {"role": "system", "content": SYSTEM_PROMPT},
                    {"role": "user", "content": final_prompt}
                ]
            }
            llm_response = await client.post(TYPEGPT_API_URL, headers=headers, json=payload)
            llm_response.raise_for_status()
            llm_data = llm_response.json()
            answer_content = llm_data['choices'][0]['message']['content']
        except httpx.RequestError as e:
            raise HTTPException(status_code=502, detail=f"Error calling language model API: {e}")
        except (KeyError, IndexError) as e:
            raise HTTPException(status_code=500, detail=f"Invalid response structure from language model API: {e}")

    # 5. Format the final response
    response_message = ChatMessage(role="assistant", content=answer_content)
    response_choice = Choice(message=response_message)
    
    return ChatCompletionResponse(choices=[response_choice], search_results=search_results)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)