//! Tests for the request body. Do not edit. //! //! Bodies are compared as JSON values, so key order is free. What is not free: which keys //! appear, and that the same input always gives the same bytes. mod support; use loopd::llama::request::build_body; use loopd::llama::{ChatMessage, ChatRequest, ToolSchema}; use serde_json::{Value, json}; use std::path::Path; fn body(req: &ChatRequest) -> Value { let cfg = support::test_config(Path::new("/tmp/unused.sock")); serde_json::from_str(&build_body(&cfg, req).unwrap()).unwrap() } fn user(text: &str) -> ChatMessage { ChatMessage::User { content: text.to_string(), } } fn read_file_tool() -> ToolSchema { ToolSchema { name: "read_file".to_string(), description: "Read a text file.".to_string(), parameters: json!({"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}), } } #[test] fn a_plain_request_carries_every_setting() { let req = ChatRequest { slot: 1, messages: vec![user("hi")], tools: vec![], thinking: true, }; let want = json!({ "model": "test-model", "messages": [{"role": "user", "content": "hi"}], "id_slot": 1, "cache_prompt": true, "stream": true, "return_progress": true, "timings_per_token": true, "reasoning_control": true, "max_tokens": 8192, "temperature": 0.6, "top_p": 0.95, "top_k": 20, "chat_template_kwargs": {"enable_thinking": true}, }); assert_eq!(body(&req), want, "no `tools` key when there are no tools"); } #[test] fn thinking_off_and_tools() { let req = ChatRequest { slot: 0, messages: vec![user("hi")], tools: vec![read_file_tool()], thinking: false, }; let got = body(&req); assert_eq!( got["chat_template_kwargs"], json!({"enable_thinking": false}) ); let want_tools = json!([{ "type": "function", "function": { "name": "read_file", "description": "Read a text file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}, }, }]); assert_eq!(got["tools"], want_tools); } /// One message of every kind, and both shapes of assistant message. An example of each is /// here on purpose: they are rendered differently. #[test] fn every_kind_of_message() { let call = proto::ToolCall { id: "call_1".to_string(), name: "read_file".to_string(), arguments: r#"{"path":"/etc/hostname"}"#.to_string(), }; let messages = vec![ ChatMessage::System { content: "be careful".to_string(), }, user("read it"), ChatMessage::Assistant { content: None, reasoning_content: Some("I should read the file.".to_string()), tool_calls: vec![call], }, ChatMessage::Tool { tool_call_id: "call_1".to_string(), content: "straylight\n".to_string(), }, ChatMessage::Assistant { content: Some("It says straylight.".to_string()), reasoning_content: None, tool_calls: vec![], }, ]; let req = ChatRequest { slot: 0, messages, tools: vec![read_file_tool()], thinking: true, }; let want = json!([ {"role": "system", "content": "be careful"}, {"role": "user", "content": "read it"}, { "role": "assistant", "content": "", "reasoning_content": "I should read the file.", "tool_calls": [{ "id": "call_1", "type": "function", "function": {"name": "read_file", "arguments": "{\"path\":\"/etc/hostname\"}"}, }], }, {"role": "tool", "tool_call_id": "call_1", "content": "straylight\n"}, {"role": "assistant", "content": "It says straylight."}, ]); assert_eq!(body(&req)["messages"], want); } #[test] fn values_come_from_the_config() { let mut cfg = support::test_config(Path::new("/tmp/unused.sock")); cfg.infer.model = "another-model".to_string(); cfg.limits.max_tokens = 99; cfg.sampling.temperature = 0.25; cfg.sampling.top_p = 0.5; cfg.sampling.top_k = 7; let req = ChatRequest { slot: 3, messages: vec![user("hi")], tools: vec![], thinking: false, }; let got: Value = serde_json::from_str(&build_body(&cfg, &req).unwrap()).unwrap(); assert_eq!(got["model"], "another-model"); assert_eq!(got["max_tokens"], 99); assert_eq!(got["temperature"], 0.25); assert_eq!(got["top_p"], 0.5); assert_eq!(got["top_k"], 7); assert_eq!(got["id_slot"], 3); } #[test] fn the_same_input_gives_the_same_bytes() { let cfg = support::test_config(Path::new("/tmp/unused.sock")); let req = ChatRequest { slot: 0, messages: vec![user( "text with \"quotes\", a \\ backslash, a\nnewline and caf\u{e9} \u{1f4e6}", )], tools: vec![read_file_tool()], thinking: true, }; let first = build_body(&cfg, &req).unwrap(); for _ in 0..20 { assert_eq!(build_body(&cfg, &req).unwrap(), first); } let back: Value = serde_json::from_str(&first).unwrap(); assert_eq!( back["messages"][0]["content"], "text with \"quotes\", a \\ backslash, a\nnewline and caf\u{e9} \u{1f4e6}" ); }