megharudushi commited on
Commit
4437aaa
·
verified ·
1 Parent(s): a90c89e

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +420 -16
app.py CHANGED
@@ -522,8 +522,8 @@ async def lifespan(app: FastAPI):
522
 
523
  app = FastAPI(
524
  title="Free Coding API",
525
- description="OpenAI & Anthropic compatible API with Prefill, Thinking & Computer Use Agent (CUA) support",
526
- version="1.2.0",
527
  lifespan=lifespan
528
  )
529
 
@@ -912,6 +912,411 @@ async def anthropic_messages(
912
  )
913
  )
914
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
915
  # ============================================================================
916
  # Anthropic Separate Base Path: /anthropic/v1/
917
  # ============================================================================
@@ -1475,35 +1880,34 @@ async def cua_info():
1475
  async def root():
1476
  return {
1477
  "name": "Free Coding API",
1478
- "version": "1.2.0",
1479
  "model": MODEL_ID,
1480
  "features": {
1481
- "prefill_response": "Supported - Include assistant message at end for output control",
1482
- "thinking": "Supported - Enable with thinking: {type: 'enabled'}",
1483
- "streaming": "Supported - Both OpenAI and Anthropic formats",
1484
- "computer_use": "Supported - CUA with sheikh-computer-use-preview model"
1485
- },
1486
- "compatibility": {
1487
- "openai": "v1 Chat Completions API",
1488
- "anthropic": "Messages API (2023-06-01)",
1489
- "computer_use": "Anthropic Computer Use API compatible"
1490
  },
1491
  "openai": {
1492
  "base_url": "/v1",
1493
  "chat": "/v1/chat/completions",
1494
- "models": "/v1/models"
 
 
1495
  },
1496
  "anthropic": {
1497
  "base_url": "/anthropic/v1",
1498
  "messages": "/anthropic/v1/messages",
1499
- "models": "/anthropic/v1/models",
1500
- "info": "/anthropic"
1501
  },
1502
  "cua": {
1503
  "base_url": "/cua/v1",
1504
  "messages": "/cua/v1/messages",
1505
  "models": "/cua/v1/models",
1506
- "info": "/cua",
1507
  "model": "sheikh-computer-use-preview"
1508
  },
1509
  "docs": "/docs"
 
522
 
523
  app = FastAPI(
524
  title="Free Coding API",
525
+ description="OpenAI & Anthropic compatible API with Files, Skills, Batches, CUA, Prefill & Thinking support",
526
+ version="1.3.0",
527
  lifespan=lifespan
528
  )
529
 
 
912
  )
913
  )
914
 
915
+ # ============================================================================
916
+ # Files API (Beta)
917
+ # ============================================================================
918
+
919
+ # In-memory file storage (for demo - in production use persistent storage)
920
+ files_storage: Dict[str, Dict] = {}
921
+
922
+ class FileUploadResponse(BaseModel):
923
+ id: str
924
+ object: str = "file"
925
+ bytes: int
926
+ created_at: int
927
+ filename: str
928
+ purpose: str
929
+
930
+ @app.post("/v1/files")
931
+ async def upload_file(
932
+ request: Request,
933
+ authorization: Optional[str] = Header(None),
934
+ ):
935
+ """Upload a file for use across multiple API calls"""
936
+ if not verify_api_key(authorization):
937
+ raise HTTPException(status_code=401, detail="Invalid API key")
938
+
939
+ form = await request.form()
940
+ file = form.get("file")
941
+ purpose = form.get("purpose", "assistants")
942
+
943
+ if not file:
944
+ raise HTTPException(status_code=400, detail="No file provided")
945
+
946
+ file_id = f"file-{uuid.uuid4().hex[:24]}"
947
+ content = await file.read()
948
+
949
+ file_data = {
950
+ "id": file_id,
951
+ "object": "file",
952
+ "bytes": len(content),
953
+ "created_at": int(time.time()),
954
+ "filename": file.filename,
955
+ "purpose": purpose,
956
+ "content": content # Store content in memory
957
+ }
958
+ files_storage[file_id] = file_data
959
+
960
+ return FileUploadResponse(
961
+ id=file_id,
962
+ bytes=len(content),
963
+ created_at=file_data["created_at"],
964
+ filename=file.filename,
965
+ purpose=purpose
966
+ )
967
+
968
+ @app.get("/v1/files")
969
+ async def list_files(
970
+ authorization: Optional[str] = Header(None),
971
+ purpose: Optional[str] = None,
972
+ ):
973
+ """List all uploaded files"""
974
+ if not verify_api_key(authorization):
975
+ raise HTTPException(status_code=401, detail="Invalid API key")
976
+
977
+ files_list = []
978
+ for file_id, file_data in files_storage.items():
979
+ if purpose and file_data.get("purpose") != purpose:
980
+ continue
981
+ files_list.append({
982
+ "id": file_data["id"],
983
+ "object": "file",
984
+ "bytes": file_data["bytes"],
985
+ "created_at": file_data["created_at"],
986
+ "filename": file_data["filename"],
987
+ "purpose": file_data["purpose"]
988
+ })
989
+
990
+ return {"object": "list", "data": files_list}
991
+
992
+ @app.get("/v1/files/{file_id}")
993
+ async def get_file(
994
+ file_id: str,
995
+ authorization: Optional[str] = Header(None),
996
+ ):
997
+ """Get file metadata"""
998
+ if not verify_api_key(authorization):
999
+ raise HTTPException(status_code=401, detail="Invalid API key")
1000
+
1001
+ if file_id not in files_storage:
1002
+ raise HTTPException(status_code=404, detail="File not found")
1003
+
1004
+ file_data = files_storage[file_id]
1005
+ return {
1006
+ "id": file_data["id"],
1007
+ "object": "file",
1008
+ "bytes": file_data["bytes"],
1009
+ "created_at": file_data["created_at"],
1010
+ "filename": file_data["filename"],
1011
+ "purpose": file_data["purpose"]
1012
+ }
1013
+
1014
+ @app.delete("/v1/files/{file_id}")
1015
+ async def delete_file(
1016
+ file_id: str,
1017
+ authorization: Optional[str] = Header(None),
1018
+ ):
1019
+ """Delete a file"""
1020
+ if not verify_api_key(authorization):
1021
+ raise HTTPException(status_code=401, detail="Invalid API key")
1022
+
1023
+ if file_id not in files_storage:
1024
+ raise HTTPException(status_code=404, detail="File not found")
1025
+
1026
+ del files_storage[file_id]
1027
+ return {"id": file_id, "object": "file", "deleted": True}
1028
+
1029
+
1030
+ # ============================================================================
1031
+ # Skills API (Beta)
1032
+ # ============================================================================
1033
+
1034
+ skills_storage: Dict[str, Dict] = {}
1035
+
1036
+ class SkillCreate(BaseModel):
1037
+ name: str
1038
+ description: Optional[str] = None
1039
+ instructions: str
1040
+ tools: Optional[List[Dict]] = None
1041
+
1042
+ class SkillResponse(BaseModel):
1043
+ id: str
1044
+ object: str = "skill"
1045
+ name: str
1046
+ description: Optional[str] = None
1047
+ instructions: str
1048
+ tools: Optional[List[Dict]] = None
1049
+ created_at: int
1050
+
1051
+ @app.post("/v1/skills")
1052
+ async def create_skill(
1053
+ request: SkillCreate,
1054
+ authorization: Optional[str] = Header(None),
1055
+ ):
1056
+ """Create a custom agent skill"""
1057
+ if not verify_api_key(authorization):
1058
+ raise HTTPException(status_code=401, detail="Invalid API key")
1059
+
1060
+ skill_id = f"skill-{uuid.uuid4().hex[:24]}"
1061
+ skill_data = {
1062
+ "id": skill_id,
1063
+ "object": "skill",
1064
+ "name": request.name,
1065
+ "description": request.description,
1066
+ "instructions": request.instructions,
1067
+ "tools": request.tools or [],
1068
+ "created_at": int(time.time())
1069
+ }
1070
+ skills_storage[skill_id] = skill_data
1071
+
1072
+ return SkillResponse(**skill_data)
1073
+
1074
+ @app.get("/v1/skills")
1075
+ async def list_skills(
1076
+ authorization: Optional[str] = Header(None),
1077
+ ):
1078
+ """List all custom skills"""
1079
+ if not verify_api_key(authorization):
1080
+ raise HTTPException(status_code=401, detail="Invalid API key")
1081
+
1082
+ return {
1083
+ "object": "list",
1084
+ "data": [
1085
+ {k: v for k, v in skill.items()}
1086
+ for skill in skills_storage.values()
1087
+ ]
1088
+ }
1089
+
1090
+ @app.get("/v1/skills/{skill_id}")
1091
+ async def get_skill(
1092
+ skill_id: str,
1093
+ authorization: Optional[str] = Header(None),
1094
+ ):
1095
+ """Get skill details"""
1096
+ if not verify_api_key(authorization):
1097
+ raise HTTPException(status_code=401, detail="Invalid API key")
1098
+
1099
+ if skill_id not in skills_storage:
1100
+ raise HTTPException(status_code=404, detail="Skill not found")
1101
+
1102
+ return skills_storage[skill_id]
1103
+
1104
+ @app.delete("/v1/skills/{skill_id}")
1105
+ async def delete_skill(
1106
+ skill_id: str,
1107
+ authorization: Optional[str] = Header(None),
1108
+ ):
1109
+ """Delete a skill"""
1110
+ if not verify_api_key(authorization):
1111
+ raise HTTPException(status_code=401, detail="Invalid API key")
1112
+
1113
+ if skill_id not in skills_storage:
1114
+ raise HTTPException(status_code=404, detail="Skill not found")
1115
+
1116
+ del skills_storage[skill_id]
1117
+ return {"id": skill_id, "object": "skill", "deleted": True}
1118
+
1119
+
1120
+ # ============================================================================
1121
+ # Message Batches API (50% cost reduction for async processing)
1122
+ # ============================================================================
1123
+
1124
+ batches_storage: Dict[str, Dict] = {}
1125
+
1126
+ class BatchRequest(BaseModel):
1127
+ custom_id: str
1128
+ params: Dict # Contains the message request parameters
1129
+
1130
+ class CreateBatchRequest(BaseModel):
1131
+ requests: List[BatchRequest]
1132
+
1133
+ class BatchResponse(BaseModel):
1134
+ id: str
1135
+ type: str = "message_batch"
1136
+ processing_status: str # "in_progress", "ended"
1137
+ request_counts: Dict
1138
+ ended_at: Optional[int] = None
1139
+ created_at: int
1140
+ expires_at: int
1141
+ results_url: Optional[str] = None
1142
+
1143
+ @app.post("/v1/messages/batches")
1144
+ async def create_message_batch(
1145
+ request: CreateBatchRequest,
1146
+ authorization: Optional[str] = Header(None),
1147
+ x_api_key: Optional[str] = Header(None, alias="x-api-key"),
1148
+ ):
1149
+ """
1150
+ Create a Message Batch for async processing with 50% cost reduction.
1151
+ Process large volumes of Messages requests asynchronously.
1152
+ """
1153
+ auth_key = x_api_key or authorization
1154
+ if not verify_api_key(auth_key):
1155
+ raise HTTPException(status_code=401, detail="Invalid API key")
1156
+
1157
+ batch_id = f"batch_{uuid.uuid4().hex[:24]}"
1158
+ created_at = int(time.time())
1159
+
1160
+ # Process batch requests asynchronously (simulated)
1161
+ results = []
1162
+ succeeded = 0
1163
+ failed = 0
1164
+
1165
+ for req in request.requests:
1166
+ try:
1167
+ # Extract message parameters
1168
+ params = req.params
1169
+ messages = params.get("messages", [])
1170
+ max_tokens = params.get("max_tokens", 1024)
1171
+
1172
+ # Format and generate
1173
+ formatted_msgs = []
1174
+ for m in messages:
1175
+ content = m.get("content", "")
1176
+ if isinstance(content, list):
1177
+ content = " ".join([b.get("text", "") for b in content if b.get("type") == "text"])
1178
+ formatted_msgs.append({"role": m.get("role"), "content": content})
1179
+
1180
+ prompt = format_messages_for_model(formatted_msgs)
1181
+ response_text, _, input_tokens, output_tokens, _ = generate_response(
1182
+ prompt, max_tokens=max_tokens
1183
+ )
1184
+
1185
+ results.append({
1186
+ "custom_id": req.custom_id,
1187
+ "result": {
1188
+ "type": "succeeded",
1189
+ "message": {
1190
+ "id": f"msg_{uuid.uuid4().hex[:24]}",
1191
+ "type": "message",
1192
+ "role": "assistant",
1193
+ "content": [{"type": "text", "text": response_text}],
1194
+ "model": params.get("model", "claude-3-sonnet"),
1195
+ "stop_reason": "end_turn",
1196
+ "usage": {"input_tokens": input_tokens, "output_tokens": output_tokens}
1197
+ }
1198
+ }
1199
+ })
1200
+ succeeded += 1
1201
+ except Exception as e:
1202
+ results.append({
1203
+ "custom_id": req.custom_id,
1204
+ "result": {
1205
+ "type": "errored",
1206
+ "error": {"type": "server_error", "message": str(e)}
1207
+ }
1208
+ })
1209
+ failed += 1
1210
+
1211
+ batch_data = {
1212
+ "id": batch_id,
1213
+ "type": "message_batch",
1214
+ "processing_status": "ended",
1215
+ "request_counts": {
1216
+ "processing": 0,
1217
+ "succeeded": succeeded,
1218
+ "errored": failed,
1219
+ "canceled": 0,
1220
+ "expired": 0
1221
+ },
1222
+ "ended_at": int(time.time()),
1223
+ "created_at": created_at,
1224
+ "expires_at": created_at + 86400, # 24 hours
1225
+ "results": results
1226
+ }
1227
+ batches_storage[batch_id] = batch_data
1228
+
1229
+ return BatchResponse(
1230
+ id=batch_id,
1231
+ processing_status="ended",
1232
+ request_counts=batch_data["request_counts"],
1233
+ ended_at=batch_data["ended_at"],
1234
+ created_at=created_at,
1235
+ expires_at=batch_data["expires_at"],
1236
+ results_url=f"/v1/messages/batches/{batch_id}/results"
1237
+ )
1238
+
1239
+ @app.get("/v1/messages/batches")
1240
+ async def list_batches(
1241
+ authorization: Optional[str] = Header(None),
1242
+ x_api_key: Optional[str] = Header(None, alias="x-api-key"),
1243
+ ):
1244
+ """List all message batches"""
1245
+ auth_key = x_api_key or authorization
1246
+ if not verify_api_key(auth_key):
1247
+ raise HTTPException(status_code=401, detail="Invalid API key")
1248
+
1249
+ return {
1250
+ "object": "list",
1251
+ "data": [
1252
+ {k: v for k, v in batch.items() if k != "results"}
1253
+ for batch in batches_storage.values()
1254
+ ]
1255
+ }
1256
+
1257
+ @app.get("/v1/messages/batches/{batch_id}")
1258
+ async def get_batch(
1259
+ batch_id: str,
1260
+ authorization: Optional[str] = Header(None),
1261
+ x_api_key: Optional[str] = Header(None, alias="x-api-key"),
1262
+ ):
1263
+ """Get batch status and details"""
1264
+ auth_key = x_api_key or authorization
1265
+ if not verify_api_key(auth_key):
1266
+ raise HTTPException(status_code=401, detail="Invalid API key")
1267
+
1268
+ if batch_id not in batches_storage:
1269
+ raise HTTPException(status_code=404, detail="Batch not found")
1270
+
1271
+ batch = batches_storage[batch_id]
1272
+ return {k: v for k, v in batch.items() if k != "results"}
1273
+
1274
+ @app.get("/v1/messages/batches/{batch_id}/results")
1275
+ async def get_batch_results(
1276
+ batch_id: str,
1277
+ authorization: Optional[str] = Header(None),
1278
+ x_api_key: Optional[str] = Header(None, alias="x-api-key"),
1279
+ ):
1280
+ """Get batch results (JSONL format)"""
1281
+ auth_key = x_api_key or authorization
1282
+ if not verify_api_key(auth_key):
1283
+ raise HTTPException(status_code=401, detail="Invalid API key")
1284
+
1285
+ if batch_id not in batches_storage:
1286
+ raise HTTPException(status_code=404, detail="Batch not found")
1287
+
1288
+ batch = batches_storage[batch_id]
1289
+ if batch["processing_status"] != "ended":
1290
+ raise HTTPException(status_code=400, detail="Batch still processing")
1291
+
1292
+ # Return results as JSON (in real API this would be JSONL)
1293
+ return {"results": batch.get("results", [])}
1294
+
1295
+ @app.post("/v1/messages/batches/{batch_id}/cancel")
1296
+ async def cancel_batch(
1297
+ batch_id: str,
1298
+ authorization: Optional[str] = Header(None),
1299
+ x_api_key: Optional[str] = Header(None, alias="x-api-key"),
1300
+ ):
1301
+ """Cancel a batch"""
1302
+ auth_key = x_api_key or authorization
1303
+ if not verify_api_key(auth_key):
1304
+ raise HTTPException(status_code=401, detail="Invalid API key")
1305
+
1306
+ if batch_id not in batches_storage:
1307
+ raise HTTPException(status_code=404, detail="Batch not found")
1308
+
1309
+ batch = batches_storage[batch_id]
1310
+ if batch["processing_status"] == "ended":
1311
+ raise HTTPException(status_code=400, detail="Batch already ended")
1312
+
1313
+ batch["processing_status"] = "ended"
1314
+ batch["request_counts"]["canceled"] = batch["request_counts"].get("processing", 0)
1315
+ batch["request_counts"]["processing"] = 0
1316
+
1317
+ return {k: v for k, v in batch.items() if k != "results"}
1318
+
1319
+
1320
  # ============================================================================
1321
  # Anthropic Separate Base Path: /anthropic/v1/
1322
  # ============================================================================
 
1880
  async def root():
1881
  return {
1882
  "name": "Free Coding API",
1883
+ "version": "1.3.0",
1884
  "model": MODEL_ID,
1885
  "features": {
1886
+ "prefill_response": "Supported",
1887
+ "thinking": "Supported",
1888
+ "streaming": "Supported",
1889
+ "computer_use": "Supported",
1890
+ "files_api": "Beta",
1891
+ "skills_api": "Beta",
1892
+ "message_batches": "Supported (50% cost reduction)"
 
 
1893
  },
1894
  "openai": {
1895
  "base_url": "/v1",
1896
  "chat": "/v1/chat/completions",
1897
+ "models": "/v1/models",
1898
+ "files": "/v1/files",
1899
+ "skills": "/v1/skills"
1900
  },
1901
  "anthropic": {
1902
  "base_url": "/anthropic/v1",
1903
  "messages": "/anthropic/v1/messages",
1904
+ "batches": "/v1/messages/batches",
1905
+ "models": "/anthropic/v1/models"
1906
  },
1907
  "cua": {
1908
  "base_url": "/cua/v1",
1909
  "messages": "/cua/v1/messages",
1910
  "models": "/cua/v1/models",
 
1911
  "model": "sheikh-computer-use-preview"
1912
  },
1913
  "docs": "/docs"