test_compat_anthropic.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. #!/usr/bin/env python3
  2. import pytest
  3. import base64
  4. import requests
  5. from utils import *
  6. server: ServerProcess
  7. def get_test_image_base64() -> str:
  8. """Get a test image in base64 format"""
  9. # Use the same test image as test_vision_api.py
  10. IMG_URL = "https://huggingface.co/ggml-org/tinygemma3-GGUF/resolve/main/test/11_truck.png"
  11. response = requests.get(IMG_URL)
  12. response.raise_for_status()
  13. return base64.b64encode(response.content).decode("utf-8")
  14. @pytest.fixture(autouse=True)
  15. def create_server():
  16. global server
  17. server = ServerPreset.tinyllama2()
  18. server.model_alias = "tinyllama-2-anthropic"
  19. server.server_port = 8082
  20. server.n_slots = 1
  21. server.n_ctx = 8192
  22. server.n_batch = 2048
  23. @pytest.fixture
  24. def vision_server():
  25. """Separate fixture for vision tests that require multimodal support"""
  26. global server
  27. server = ServerPreset.tinygemma3()
  28. server.offline = False # Allow downloading the model
  29. server.model_alias = "tinygemma3-anthropic"
  30. server.server_port = 8083 # Different port to avoid conflicts
  31. server.n_slots = 1
  32. return server
  33. # Basic message tests
  34. def test_anthropic_messages_basic():
  35. """Test basic Anthropic messages endpoint"""
  36. server.start()
  37. res = server.make_request("POST", "/v1/messages", data={
  38. "model": "test",
  39. "max_tokens": 50,
  40. "messages": [
  41. {"role": "user", "content": "Say hello"}
  42. ]
  43. })
  44. assert res.status_code == 200, f"Expected 200, got {res.status_code}"
  45. assert res.body["type"] == "message", f"Expected type 'message', got {res.body.get('type')}"
  46. assert res.body["role"] == "assistant", f"Expected role 'assistant', got {res.body.get('role')}"
  47. assert "content" in res.body, "Missing 'content' field"
  48. assert isinstance(res.body["content"], list), "Content should be an array"
  49. assert len(res.body["content"]) > 0, "Content array should not be empty"
  50. assert res.body["content"][0]["type"] == "text", "First content block should be text"
  51. assert "text" in res.body["content"][0], "Text content block missing 'text' field"
  52. assert res.body["stop_reason"] in ["end_turn", "max_tokens"], f"Invalid stop_reason: {res.body.get('stop_reason')}"
  53. assert "usage" in res.body, "Missing 'usage' field"
  54. assert "input_tokens" in res.body["usage"], "Missing usage.input_tokens"
  55. assert "output_tokens" in res.body["usage"], "Missing usage.output_tokens"
  56. assert isinstance(res.body["usage"]["input_tokens"], int), "input_tokens should be integer"
  57. assert isinstance(res.body["usage"]["output_tokens"], int), "output_tokens should be integer"
  58. assert res.body["usage"]["output_tokens"] > 0, "Should have generated some tokens"
  59. # Anthropic API should NOT include timings
  60. assert "timings" not in res.body, "Anthropic API should not include timings field"
  61. def test_anthropic_messages_with_system():
  62. """Test messages with system prompt"""
  63. server.start()
  64. res = server.make_request("POST", "/v1/messages", data={
  65. "model": "test",
  66. "max_tokens": 50,
  67. "system": "You are a helpful assistant.",
  68. "messages": [
  69. {"role": "user", "content": "Hello"}
  70. ]
  71. })
  72. assert res.status_code == 200
  73. assert res.body["type"] == "message"
  74. assert len(res.body["content"]) > 0
  75. def test_anthropic_messages_multipart_content():
  76. """Test messages with multipart content blocks"""
  77. server.start()
  78. res = server.make_request("POST", "/v1/messages", data={
  79. "model": "test",
  80. "max_tokens": 50,
  81. "messages": [
  82. {
  83. "role": "user",
  84. "content": [
  85. {"type": "text", "text": "What is"},
  86. {"type": "text", "text": " the answer?"}
  87. ]
  88. }
  89. ]
  90. })
  91. assert res.status_code == 200
  92. assert res.body["type"] == "message"
  93. def test_anthropic_messages_conversation():
  94. """Test multi-turn conversation"""
  95. server.start()
  96. res = server.make_request("POST", "/v1/messages", data={
  97. "model": "test",
  98. "max_tokens": 50,
  99. "messages": [
  100. {"role": "user", "content": "Hello"},
  101. {"role": "assistant", "content": "Hi there!"},
  102. {"role": "user", "content": "How are you?"}
  103. ]
  104. })
  105. assert res.status_code == 200
  106. assert res.body["type"] == "message"
  107. # Streaming tests
  108. def test_anthropic_messages_streaming():
  109. """Test streaming messages"""
  110. server.start()
  111. res = server.make_stream_request("POST", "/v1/messages", data={
  112. "model": "test",
  113. "max_tokens": 30,
  114. "messages": [
  115. {"role": "user", "content": "Say hello"}
  116. ],
  117. "stream": True
  118. })
  119. events = []
  120. for data in res:
  121. # Each event should have type and other fields
  122. assert "type" in data, f"Missing 'type' in event: {data}"
  123. events.append(data)
  124. # Verify event sequence
  125. event_types = [e["type"] for e in events]
  126. assert "message_start" in event_types, "Missing message_start event"
  127. assert "content_block_start" in event_types, "Missing content_block_start event"
  128. assert "content_block_delta" in event_types, "Missing content_block_delta event"
  129. assert "content_block_stop" in event_types, "Missing content_block_stop event"
  130. assert "message_delta" in event_types, "Missing message_delta event"
  131. assert "message_stop" in event_types, "Missing message_stop event"
  132. # Check message_start structure
  133. message_start = next(e for e in events if e["type"] == "message_start")
  134. assert "message" in message_start, "message_start missing 'message' field"
  135. assert message_start["message"]["type"] == "message"
  136. assert message_start["message"]["role"] == "assistant"
  137. assert message_start["message"]["content"] == []
  138. assert "usage" in message_start["message"]
  139. assert message_start["message"]["usage"]["input_tokens"] > 0
  140. # Check content_block_start
  141. block_start = next(e for e in events if e["type"] == "content_block_start")
  142. assert "index" in block_start, "content_block_start missing 'index'"
  143. assert block_start["index"] == 0, "First content block should be at index 0"
  144. assert "content_block" in block_start
  145. assert block_start["content_block"]["type"] == "text"
  146. # Check content_block_delta
  147. deltas = [e for e in events if e["type"] == "content_block_delta"]
  148. assert len(deltas) > 0, "Should have at least one content_block_delta"
  149. for delta in deltas:
  150. assert "index" in delta
  151. assert "delta" in delta
  152. assert delta["delta"]["type"] == "text_delta"
  153. assert "text" in delta["delta"]
  154. # Check content_block_stop
  155. block_stop = next(e for e in events if e["type"] == "content_block_stop")
  156. assert "index" in block_stop
  157. assert block_stop["index"] == 0
  158. # Check message_delta
  159. message_delta = next(e for e in events if e["type"] == "message_delta")
  160. assert "delta" in message_delta
  161. assert "stop_reason" in message_delta["delta"]
  162. assert message_delta["delta"]["stop_reason"] in ["end_turn", "max_tokens"]
  163. assert "usage" in message_delta
  164. assert message_delta["usage"]["output_tokens"] > 0
  165. # Check message_stop
  166. message_stop = next(e for e in events if e["type"] == "message_stop")
  167. # message_stop should NOT have timings for Anthropic API
  168. assert "timings" not in message_stop, "Anthropic streaming should not include timings"
  169. # Token counting tests
  170. def test_anthropic_count_tokens():
  171. """Test token counting endpoint"""
  172. server.start()
  173. res = server.make_request("POST", "/v1/messages/count_tokens", data={
  174. "model": "test",
  175. "messages": [
  176. {"role": "user", "content": "Hello world"}
  177. ]
  178. })
  179. assert res.status_code == 200
  180. assert "input_tokens" in res.body
  181. assert isinstance(res.body["input_tokens"], int)
  182. assert res.body["input_tokens"] > 0
  183. # Should only have input_tokens, no other fields
  184. assert "output_tokens" not in res.body
  185. def test_anthropic_count_tokens_with_system():
  186. """Test token counting with system prompt"""
  187. server.start()
  188. res = server.make_request("POST", "/v1/messages/count_tokens", data={
  189. "model": "test",
  190. "system": "You are a helpful assistant.",
  191. "messages": [
  192. {"role": "user", "content": "Hello"}
  193. ]
  194. })
  195. assert res.status_code == 200
  196. assert res.body["input_tokens"] > 0
  197. def test_anthropic_count_tokens_no_max_tokens():
  198. """Test that count_tokens doesn't require max_tokens"""
  199. server.start()
  200. # max_tokens is NOT required for count_tokens
  201. res = server.make_request("POST", "/v1/messages/count_tokens", data={
  202. "model": "test",
  203. "messages": [
  204. {"role": "user", "content": "Hello"}
  205. ]
  206. })
  207. assert res.status_code == 200
  208. assert "input_tokens" in res.body
  209. # Tool use tests
  210. def test_anthropic_tool_use_basic():
  211. """Test basic tool use"""
  212. server.jinja = True
  213. server.start()
  214. res = server.make_request("POST", "/v1/messages", data={
  215. "model": "test",
  216. "max_tokens": 200,
  217. "tools": [{
  218. "name": "get_weather",
  219. "description": "Get the current weather in a location",
  220. "input_schema": {
  221. "type": "object",
  222. "properties": {
  223. "location": {
  224. "type": "string",
  225. "description": "City name"
  226. }
  227. },
  228. "required": ["location"]
  229. }
  230. }],
  231. "messages": [
  232. {"role": "user", "content": "What's the weather in Paris?"}
  233. ]
  234. })
  235. assert res.status_code == 200
  236. assert res.body["type"] == "message"
  237. assert len(res.body["content"]) > 0
  238. # Check if model used the tool (it might not always, depending on the model)
  239. content_types = [block.get("type") for block in res.body["content"]]
  240. if "tool_use" in content_types:
  241. # Model used the tool
  242. assert res.body["stop_reason"] == "tool_use"
  243. # Find the tool_use block
  244. tool_block = next(b for b in res.body["content"] if b.get("type") == "tool_use")
  245. assert "id" in tool_block
  246. assert "name" in tool_block
  247. assert tool_block["name"] == "get_weather"
  248. assert "input" in tool_block
  249. assert isinstance(tool_block["input"], dict)
  250. def test_anthropic_tool_result():
  251. """Test sending tool results back
  252. This test verifies that tool_result blocks are properly converted to
  253. role="tool" messages internally. Without proper conversion, this would
  254. fail with a 500 error: "unsupported content[].type" because tool_result
  255. blocks would remain in the user message content array.
  256. """
  257. server.jinja = True
  258. server.start()
  259. res = server.make_request("POST", "/v1/messages", data={
  260. "model": "test",
  261. "max_tokens": 100,
  262. "messages": [
  263. {"role": "user", "content": "What's the weather?"},
  264. {
  265. "role": "assistant",
  266. "content": [
  267. {
  268. "type": "tool_use",
  269. "id": "test123",
  270. "name": "get_weather",
  271. "input": {"location": "Paris"}
  272. }
  273. ]
  274. },
  275. {
  276. "role": "user",
  277. "content": [
  278. {
  279. "type": "tool_result",
  280. "tool_use_id": "test123",
  281. "content": "The weather is sunny, 25°C"
  282. }
  283. ]
  284. }
  285. ]
  286. })
  287. # This would be 500 with the old bug where tool_result blocks weren't converted
  288. assert res.status_code == 200
  289. assert res.body["type"] == "message"
  290. # Model should respond to the tool result
  291. assert len(res.body["content"]) > 0
  292. assert res.body["content"][0]["type"] == "text"
  293. def test_anthropic_tool_result_with_text():
  294. """Test tool result mixed with text content
  295. This tests the edge case where a user message contains both text and
  296. tool_result blocks. The server must properly split these into separate
  297. messages: a user message with text, followed by tool messages.
  298. Without proper handling, this would fail with 500: "unsupported content[].type"
  299. """
  300. server.jinja = True
  301. server.start()
  302. res = server.make_request("POST", "/v1/messages", data={
  303. "model": "test",
  304. "max_tokens": 100,
  305. "messages": [
  306. {"role": "user", "content": "What's the weather?"},
  307. {
  308. "role": "assistant",
  309. "content": [
  310. {
  311. "type": "tool_use",
  312. "id": "tool_1",
  313. "name": "get_weather",
  314. "input": {"location": "Paris"}
  315. }
  316. ]
  317. },
  318. {
  319. "role": "user",
  320. "content": [
  321. {"type": "text", "text": "Here are the results:"},
  322. {
  323. "type": "tool_result",
  324. "tool_use_id": "tool_1",
  325. "content": "Sunny, 25°C"
  326. }
  327. ]
  328. }
  329. ]
  330. })
  331. assert res.status_code == 200
  332. assert res.body["type"] == "message"
  333. assert len(res.body["content"]) > 0
  334. def test_anthropic_tool_result_error():
  335. """Test tool result with error flag"""
  336. server.jinja = True
  337. server.start()
  338. res = server.make_request("POST", "/v1/messages", data={
  339. "model": "test",
  340. "max_tokens": 100,
  341. "messages": [
  342. {"role": "user", "content": "Get the weather"},
  343. {
  344. "role": "assistant",
  345. "content": [
  346. {
  347. "type": "tool_use",
  348. "id": "test123",
  349. "name": "get_weather",
  350. "input": {"location": "InvalidCity"}
  351. }
  352. ]
  353. },
  354. {
  355. "role": "user",
  356. "content": [
  357. {
  358. "type": "tool_result",
  359. "tool_use_id": "test123",
  360. "is_error": True,
  361. "content": "City not found"
  362. }
  363. ]
  364. }
  365. ]
  366. })
  367. assert res.status_code == 200
  368. assert res.body["type"] == "message"
  369. def test_anthropic_tool_streaming():
  370. """Test streaming with tool use"""
  371. server.jinja = True
  372. server.start()
  373. res = server.make_stream_request("POST", "/v1/messages", data={
  374. "model": "test",
  375. "max_tokens": 200,
  376. "stream": True,
  377. "tools": [{
  378. "name": "calculator",
  379. "description": "Calculate math",
  380. "input_schema": {
  381. "type": "object",
  382. "properties": {
  383. "expression": {"type": "string"}
  384. },
  385. "required": ["expression"]
  386. }
  387. }],
  388. "messages": [
  389. {"role": "user", "content": "Calculate 2+2"}
  390. ]
  391. })
  392. events = []
  393. for data in res:
  394. events.append(data)
  395. event_types = [e["type"] for e in events]
  396. # Should have basic events
  397. assert "message_start" in event_types
  398. assert "message_stop" in event_types
  399. # If tool was used, check for proper tool streaming
  400. if any(e.get("type") == "content_block_start" and
  401. e.get("content_block", {}).get("type") == "tool_use"
  402. for e in events):
  403. # Find tool use block start
  404. tool_starts = [e for e in events if
  405. e.get("type") == "content_block_start" and
  406. e.get("content_block", {}).get("type") == "tool_use"]
  407. assert len(tool_starts) > 0, "Should have tool_use content_block_start"
  408. # Check index is correct (should be 0 if no text, 1 if there's text)
  409. tool_start = tool_starts[0]
  410. assert "index" in tool_start
  411. assert tool_start["content_block"]["type"] == "tool_use"
  412. assert "name" in tool_start["content_block"]
  413. # Vision/multimodal tests
  414. def test_anthropic_vision_format_accepted():
  415. """Test that Anthropic vision format is accepted (format validation only)"""
  416. server.start()
  417. # Small 1x1 red PNG image in base64
  418. red_pixel_png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="
  419. res = server.make_request("POST", "/v1/messages", data={
  420. "model": "test",
  421. "max_tokens": 10,
  422. "messages": [
  423. {
  424. "role": "user",
  425. "content": [
  426. {
  427. "type": "image",
  428. "source": {
  429. "type": "base64",
  430. "media_type": "image/png",
  431. "data": red_pixel_png
  432. }
  433. },
  434. {
  435. "type": "text",
  436. "text": "What is this?"
  437. }
  438. ]
  439. }
  440. ]
  441. })
  442. # Server accepts the format but tinyllama doesn't support images
  443. # So it should return 500 with clear error message about missing mmproj
  444. assert res.status_code == 500
  445. assert "image input is not supported" in res.body.get("error", {}).get("message", "").lower()
  446. def test_anthropic_vision_base64_with_multimodal_model(vision_server):
  447. """Test vision with base64 image using Anthropic format with multimodal model"""
  448. global server
  449. server = vision_server
  450. server.start()
  451. # Get test image in base64 format
  452. image_base64 = get_test_image_base64()
  453. res = server.make_request("POST", "/v1/messages", data={
  454. "model": "test",
  455. "max_tokens": 10,
  456. "messages": [
  457. {
  458. "role": "user",
  459. "content": [
  460. {
  461. "type": "image",
  462. "source": {
  463. "type": "base64",
  464. "media_type": "image/png",
  465. "data": image_base64
  466. }
  467. },
  468. {
  469. "type": "text",
  470. "text": "What is this:\n"
  471. }
  472. ]
  473. }
  474. ]
  475. })
  476. assert res.status_code == 200, f"Expected 200, got {res.status_code}: {res.body}"
  477. assert res.body["type"] == "message"
  478. assert len(res.body["content"]) > 0
  479. assert res.body["content"][0]["type"] == "text"
  480. # The model should generate some response about the image
  481. assert len(res.body["content"][0]["text"]) > 0
  482. # Parameter tests
  483. def test_anthropic_stop_sequences():
  484. """Test stop_sequences parameter"""
  485. server.start()
  486. res = server.make_request("POST", "/v1/messages", data={
  487. "model": "test",
  488. "max_tokens": 100,
  489. "stop_sequences": ["\n", "END"],
  490. "messages": [
  491. {"role": "user", "content": "Count to 10"}
  492. ]
  493. })
  494. assert res.status_code == 200
  495. assert res.body["type"] == "message"
  496. def test_anthropic_temperature():
  497. """Test temperature parameter"""
  498. server.start()
  499. res = server.make_request("POST", "/v1/messages", data={
  500. "model": "test",
  501. "max_tokens": 50,
  502. "temperature": 0.5,
  503. "messages": [
  504. {"role": "user", "content": "Hello"}
  505. ]
  506. })
  507. assert res.status_code == 200
  508. assert res.body["type"] == "message"
  509. def test_anthropic_top_p():
  510. """Test top_p parameter"""
  511. server.start()
  512. res = server.make_request("POST", "/v1/messages", data={
  513. "model": "test",
  514. "max_tokens": 50,
  515. "top_p": 0.9,
  516. "messages": [
  517. {"role": "user", "content": "Hello"}
  518. ]
  519. })
  520. assert res.status_code == 200
  521. assert res.body["type"] == "message"
  522. def test_anthropic_top_k():
  523. """Test top_k parameter (llama.cpp specific)"""
  524. server.start()
  525. res = server.make_request("POST", "/v1/messages", data={
  526. "model": "test",
  527. "max_tokens": 50,
  528. "top_k": 40,
  529. "messages": [
  530. {"role": "user", "content": "Hello"}
  531. ]
  532. })
  533. assert res.status_code == 200
  534. assert res.body["type"] == "message"
  535. # Error handling tests
  536. def test_anthropic_missing_messages():
  537. """Test error when messages are missing"""
  538. server.start()
  539. res = server.make_request("POST", "/v1/messages", data={
  540. "model": "test",
  541. "max_tokens": 50
  542. # missing "messages" field
  543. })
  544. # Should return an error (400 or 500)
  545. assert res.status_code >= 400
  546. def test_anthropic_empty_messages():
  547. """Test permissive handling of empty messages array"""
  548. server.start()
  549. res = server.make_request("POST", "/v1/messages", data={
  550. "model": "test",
  551. "max_tokens": 50,
  552. "messages": []
  553. })
  554. # Server is permissive and accepts empty messages (provides defaults)
  555. # This matches the permissive validation design choice
  556. assert res.status_code == 200
  557. assert res.body["type"] == "message"
  558. # Content block index tests
  559. def test_anthropic_streaming_content_block_indices():
  560. """Test that content block indices are correct in streaming"""
  561. server.jinja = True
  562. server.start()
  563. # Request that might produce both text and tool use
  564. res = server.make_stream_request("POST", "/v1/messages", data={
  565. "model": "test",
  566. "max_tokens": 400,
  567. "stream": True,
  568. "tools": [{
  569. "name": "test_tool",
  570. "description": "A test tool",
  571. "input_schema": {
  572. "type": "object",
  573. "properties": {
  574. "param": {"type": "string"}
  575. },
  576. "required": ["param"]
  577. }
  578. }],
  579. "messages": [
  580. {"role": "user", "content": "Use the test tool"}
  581. ]
  582. })
  583. events = []
  584. for data in res:
  585. events.append(data)
  586. # Check content_block_start events have sequential indices
  587. block_starts = [e for e in events if e.get("type") == "content_block_start"]
  588. if len(block_starts) > 1:
  589. # If there are multiple blocks, indices should be sequential
  590. indices = [e["index"] for e in block_starts]
  591. expected_indices = list(range(len(block_starts)))
  592. assert indices == expected_indices, f"Expected indices {expected_indices}, got {indices}"
  593. # Check content_block_stop events match the starts
  594. block_stops = [e for e in events if e.get("type") == "content_block_stop"]
  595. start_indices = set(e["index"] for e in block_starts)
  596. stop_indices = set(e["index"] for e in block_stops)
  597. assert start_indices == stop_indices, "content_block_stop indices should match content_block_start indices"
  598. # Extended features tests
  599. def test_anthropic_thinking():
  600. """Test extended thinking parameter"""
  601. server.jinja = True
  602. server.start()
  603. res = server.make_request("POST", "/v1/messages", data={
  604. "model": "test",
  605. "max_tokens": 100,
  606. "thinking": {
  607. "type": "enabled",
  608. "budget_tokens": 50
  609. },
  610. "messages": [
  611. {"role": "user", "content": "What is 2+2?"}
  612. ]
  613. })
  614. assert res.status_code == 200
  615. assert res.body["type"] == "message"
  616. def test_anthropic_metadata():
  617. """Test metadata parameter"""
  618. server.start()
  619. res = server.make_request("POST", "/v1/messages", data={
  620. "model": "test",
  621. "max_tokens": 50,
  622. "metadata": {
  623. "user_id": "test_user_123"
  624. },
  625. "messages": [
  626. {"role": "user", "content": "Hello"}
  627. ]
  628. })
  629. assert res.status_code == 200
  630. assert res.body["type"] == "message"
  631. # Compatibility tests
  632. def test_anthropic_vs_openai_different_response_format():
  633. """Verify Anthropic format is different from OpenAI format"""
  634. server.start()
  635. # Make OpenAI request
  636. openai_res = server.make_request("POST", "/v1/chat/completions", data={
  637. "model": "test",
  638. "max_tokens": 50,
  639. "messages": [
  640. {"role": "user", "content": "Hello"}
  641. ]
  642. })
  643. # Make Anthropic request
  644. anthropic_res = server.make_request("POST", "/v1/messages", data={
  645. "model": "test",
  646. "max_tokens": 50,
  647. "messages": [
  648. {"role": "user", "content": "Hello"}
  649. ]
  650. })
  651. assert openai_res.status_code == 200
  652. assert anthropic_res.status_code == 200
  653. # OpenAI has "object", Anthropic has "type"
  654. assert "object" in openai_res.body
  655. assert "type" in anthropic_res.body
  656. assert openai_res.body["object"] == "chat.completion"
  657. assert anthropic_res.body["type"] == "message"
  658. # OpenAI has "choices", Anthropic has "content"
  659. assert "choices" in openai_res.body
  660. assert "content" in anthropic_res.body
  661. # Different usage field names
  662. assert "prompt_tokens" in openai_res.body["usage"]
  663. assert "input_tokens" in anthropic_res.body["usage"]
  664. assert "completion_tokens" in openai_res.body["usage"]
  665. assert "output_tokens" in anthropic_res.body["usage"]