kazukaraya12 commited on
Commit
aec3672
·
verified ·
1 Parent(s): c294c8d

Create openai_server.py

Browse files
Files changed (1) hide show
  1. openai_server.py +178 -0
openai_server.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ import time
4
+ import uuid
5
+ from typing import List, Optional, Dict, Any
6
+
7
+ from fastapi import FastAPI, Request, HTTPException
8
+ from fastapi.responses import JSONResponse
9
+ from sse_starlette.sse import EventSourceResponse
10
+ from pydantic import BaseModel, Field
11
+
12
+ # Import the core from the cloned Grok-Api repository
13
+ from core import Grok
14
+
15
+ app = FastAPI(title="Grok OpenAI Wrapper (Agent & MCP Compatible)")
16
+
17
+ # --- OpenAI Pydantic Models ---
18
+ class ChatMessage(BaseModel):
19
+ role: str
20
+ content: Optional[str] = None
21
+ name: Optional[str] = None
22
+ tool_calls: Optional[List[Dict[Any, Any]]] = None
23
+ tool_call_id: Optional[str] = None
24
+
25
+ class ChatCompletionRequest(BaseModel):
26
+ model: str = "grok-3-fast"
27
+ messages: List[ChatMessage]
28
+ stream: Optional[bool] = False
29
+ tools: Optional[List[Dict[Any, Any]]] = None
30
+ tool_choice: Optional[Any] = None
31
+ temperature: Optional[float] = 0.7
32
+
33
+ # --- Helper Functions ---
34
+ def format_messages_and_tools(messages: List[ChatMessage], tools: Optional[List[Dict]]) -> str:
35
+ """Translates the standard OpenAI message history into a single string for the web scraper"""
36
+ prompt = ""
37
+
38
+ # 1. Inject Tools via System Prompt Strategy if tools exist
39
+ if tools:
40
+ prompt += (
41
+ "SYSTEM INSTRUCTION: You are an intelligent AI acting as an API. You have access to tools. "
42
+ "If you need to call a tool, you MUST reply ONLY with a JSON block in the exact format below, and no other text.\n"
43
+ '```json\n{"tool_calls":[{"name": "function_name", "arguments": {"arg_name": "arg_value"}}]}\n```\n'
44
+ "Available tools:\n" + json.dumps(tools, indent=2) + "\n\n"
45
+ )
46
+
47
+ # 2. Append Message History
48
+ for msg in messages:
49
+ if msg.role == "system":
50
+ prompt += f"System: {msg.content}\n\n"
51
+ elif msg.role == "user":
52
+ prompt += f"User: {msg.content}\n\n"
53
+ elif msg.role == "assistant":
54
+ if msg.tool_calls:
55
+ prompt += f"Assistant called tools: {json.dumps(msg.tool_calls)}\n\n"
56
+ if msg.content:
57
+ prompt += f"Assistant: {msg.content}\n\n"
58
+ elif msg.role == "tool" or msg.role == "function":
59
+ # Pass tool results back to the model
60
+ prompt += f"TOOL RESULT (for {msg.tool_call_id or msg.name}): {msg.content}\n\n"
61
+
62
+ prompt += "Assistant: "
63
+ return prompt
64
+
65
+ def extract_tool_calls(text: str):
66
+ """Parses Grok's response to check if it emitted our forced JSON tool call"""
67
+ # Look for a markdown JSON block
68
+ match = re.search(r'```json\s*(.*?)\s*```', text, re.DOTALL)
69
+ json_str = match.group(1) if match else text.strip()
70
+
71
+ try:
72
+ parsed = json.loads(json_str)
73
+ if "tool_calls" in parsed and isinstance(parsed["tool_calls"], list):
74
+ formatted_calls = []
75
+ for tc in parsed["tool_calls"]:
76
+ formatted_calls.append({
77
+ "id": f"call_{uuid.uuid4().hex[:8]}",
78
+ "type": "function",
79
+ "function": {
80
+ "name": tc.get("name"),
81
+ "arguments": json.dumps(tc.get("arguments", {})) # OpenAI expects a stringified JSON here
82
+ }
83
+ })
84
+ return formatted_calls
85
+ except json.JSONDecodeError:
86
+ pass
87
+ return None
88
+
89
+ # --- API Endpoints ---
90
+ @app.post("/v1/chat/completions")
91
+ async def chat_completions(request: ChatCompletionRequest):
92
+ # 1. Prepare Prompt
93
+ mega_prompt = format_messages_and_tools(request.messages, request.tools)
94
+
95
+ try:
96
+ # 2. Call the underlying Grok Wrapper (Stateless, passing entire context in prompt)
97
+ grok_client = Grok(request.model)
98
+ raw_response = grok_client.start_convo(mega_prompt)
99
+
100
+ response_text = raw_response.get("response", "")
101
+ stream_array = raw_response.get("stream_response",[])
102
+
103
+ except Exception as e:
104
+ raise HTTPException(status_code=500, detail=str(e))
105
+
106
+ # 3. Check if response is a tool call
107
+ tool_calls = extract_tool_calls(response_text) if request.tools else None
108
+
109
+ # 4. Handle Streaming Response
110
+ if request.stream:
111
+ async def event_generator():
112
+ # If it's a tool call, we typically don't stream it, but send it as one chunk
113
+ if tool_calls:
114
+ chunk = {
115
+ "id": f"chatcmpl-{uuid.uuid4()}",
116
+ "object": "chat.completion.chunk",
117
+ "model": request.model,
118
+ "choices":[{"index": 0, "delta": {"tool_calls": tool_calls}, "finish_reason": "tool_calls"}]
119
+ }
120
+ yield {"data": json.dumps(chunk)}
121
+ else:
122
+ # Fake the stream using the token array returned by the API
123
+ for token in stream_array:
124
+ chunk = {
125
+ "id": f"chatcmpl-{uuid.uuid4()}",
126
+ "object": "chat.completion.chunk",
127
+ "model": request.model,
128
+ "choices":[{"index": 0, "delta": {"content": token}, "finish_reason": None}]
129
+ }
130
+ yield {"data": json.dumps(chunk)}
131
+ time.sleep(0.01) # slight delay to emulate natural streaming
132
+
133
+ # Final finish reason chunk
134
+ yield {
135
+ "data": json.dumps({
136
+ "id": f"chatcmpl-{uuid.uuid4()}",
137
+ "object": "chat.completion.chunk",
138
+ "model": request.model,
139
+ "choices":[{"index": 0, "delta": {}, "finish_reason": "stop"}]
140
+ })
141
+ }
142
+ yield {"data": "[DONE]"}
143
+
144
+ return EventSourceResponse(event_generator())
145
+
146
+ # 5. Handle Standard Sync Response
147
+ response_msg = {"role": "assistant", "content": None if tool_calls else response_text}
148
+ if tool_calls:
149
+ response_msg["tool_calls"] = tool_calls
150
+
151
+ return JSONResponse(content={
152
+ "id": f"chatcmpl-{uuid.uuid4()}",
153
+ "object": "chat.completion",
154
+ "created": int(time.time()),
155
+ "model": request.model,
156
+ "choices":[{
157
+ "index": 0,
158
+ "message": response_msg,
159
+ "finish_reason": "tool_calls" if tool_calls else "stop"
160
+ }],
161
+ "usage": {
162
+ "prompt_tokens": len(mega_prompt) // 4,
163
+ "completion_tokens": len(response_text) // 4,
164
+ "total_tokens": (len(mega_prompt) + len(response_text)) // 4
165
+ }
166
+ })
167
+
168
+ @app.get("/v1/models")
169
+ async def list_models():
170
+ return {
171
+ "object": "list",
172
+ "data":[
173
+ {"id": "grok-3-auto", "object": "model", "created": int(time.time()), "owned_by": "xai"},
174
+ {"id": "grok-3-fast", "object": "model", "created": int(time.time()), "owned_by": "xai"},
175
+ {"id": "grok-4", "object": "model", "created": int(time.time()), "owned_by": "xai"},
176
+ {"id": "grok-4-mini-thinking-tahoe", "object": "model", "created": int(time.time()), "owned_by": "xai"}
177
+ ]
178
+ }