88from unittest .mock import MagicMock , patch
99
1010from argus .captioner import (
11+ caption_frame ,
1112 caption_output_items ,
1213 match_ollama_model ,
1314 normalize_clip_title ,
@@ -141,6 +142,32 @@ def test_summarize_captions_parses_json_response(self, ollama_chat_mock) -> None
141142 self .assertLessEqual (len (result ["title" ]), 100 )
142143 self .assertEqual (result ["summary" ], "Wide exterior drone footage." )
143144 self .assertEqual (result ["suggested_tags" ], ["drone" , "aerial" ])
145+ payload = ollama_chat_mock .call_args [0 ][0 ]
146+ self .assertNotIn ("options" , payload )
147+
148+ @patch ("argus.captioner.ollama_chat" )
149+ def test_summarize_captions_includes_options_for_gemma4 (self , ollama_chat_mock ) -> None :
150+ ollama_chat_mock .return_value = {
151+ "message" : {
152+ "content" : (
153+ '{"title":"Short title","summary":"Wide exterior drone footage here.",'
154+ '"suggested_tags":["a","b","c"]}'
155+ )
156+ }
157+ }
158+
159+ result = summarize_captions (
160+ [{"timestamp_seconds" : 1.0 , "caption" : "Drone shot over a road." }],
161+ model = "gemma4" ,
162+ ollama_host = "http://localhost:11434" ,
163+ )
164+
165+ self .assertEqual (result ["status" ], "ok" )
166+ payload = ollama_chat_mock .call_args [0 ][0 ]
167+ self .assertIn ("options" , payload )
168+ self .assertEqual (payload ["options" ]["temperature" ], 1.0 )
169+ self .assertEqual (payload ["options" ]["top_p" ], 0.95 )
170+ self .assertEqual (payload ["options" ]["top_k" ], 64 )
144171
145172 @patch ("argus.captioner.ollama_chat" )
146173 def test_summarize_captions_rejects_missing_title (self , ollama_chat_mock ) -> None :
@@ -158,6 +185,65 @@ def test_summarize_captions_rejects_missing_title(self, ollama_chat_mock) -> Non
158185
159186 self .assertEqual (result ["status" ], "error" )
160187 self .assertIn ("required fields" , result ["reason" ])
188+ payload = ollama_chat_mock .call_args [0 ][0 ]
189+ self .assertNotIn ("options" , payload )
190+
191+ @patch ("argus.captioner.ollama_chat" )
192+ def test_caption_frame_includes_options_for_gemma4 (self , ollama_chat_mock ) -> None :
193+ ollama_chat_mock .return_value = {
194+ "message" : {
195+ "content" : json .dumps (
196+ {
197+ "short_caption" : "A test frame." ,
198+ "tags" : ["tag" ] * 40 ,
199+ "visible_text" : [],
200+ }
201+ )
202+ }
203+ }
204+
205+ with tempfile .TemporaryDirectory () as temp_dir :
206+ image_path = Path (temp_dir ) / "frame.jpg"
207+ image_path .write_bytes (b"\xff \xd8 \xff " )
208+ result = caption_frame (
209+ image_path ,
210+ model = "gemma4:latest" ,
211+ ollama_host = "http://localhost:11434" ,
212+ )
213+
214+ self .assertEqual (result ["status" ], "ok" )
215+ payload = ollama_chat_mock .call_args [0 ][0 ]
216+ self .assertIn ("options" , payload )
217+ self .assertEqual (payload ["options" ]["temperature" ], 1.0 )
218+ self .assertEqual (payload ["options" ]["top_p" ], 0.95 )
219+ self .assertEqual (payload ["options" ]["top_k" ], 64 )
220+
221+ @patch ("argus.captioner.ollama_chat" )
222+ def test_caption_frame_omits_options_for_non_gemma4 (self , ollama_chat_mock ) -> None :
223+ ollama_chat_mock .return_value = {
224+ "message" : {
225+ "content" : json .dumps (
226+ {
227+ "short_caption" : "A test frame." ,
228+ "tags" : ["tag" ] * 40 ,
229+ "visible_text" : [],
230+ }
231+ )
232+ }
233+ }
234+
235+ with tempfile .TemporaryDirectory () as temp_dir :
236+ image_path = Path (temp_dir ) / "frame.jpg"
237+ image_path .write_bytes (b"\xff \xd8 \xff " )
238+ result = caption_frame (
239+ image_path ,
240+ model = "gemma3" ,
241+ ollama_host = "http://localhost:11434" ,
242+ )
243+
244+ self .assertEqual (result ["status" ], "ok" )
245+ payload = ollama_chat_mock .call_args [0 ][0 ]
246+ self .assertNotIn ("options" , payload )
161247
162248 def test_normalize_clip_title_truncates_to_max_length (self ) -> None :
163249 long_title = "word " * 40
0 commit comments