npcsh 0.3.31__py3-none-any.whl → 0.3.32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. npcsh/audio.py +540 -181
  2. npcsh/audio_gen.py +1 -0
  3. npcsh/cli.py +8 -10
  4. npcsh/conversation.py +14 -251
  5. npcsh/dataframes.py +13 -5
  6. npcsh/helpers.py +5 -0
  7. npcsh/image.py +2 -2
  8. npcsh/image_gen.py +38 -38
  9. npcsh/knowledge_graph.py +4 -4
  10. npcsh/llm_funcs.py +517 -349
  11. npcsh/npc_compiler.py +32 -23
  12. npcsh/npc_sysenv.py +5 -0
  13. npcsh/plonk.py +2 -2
  14. npcsh/response.py +131 -482
  15. npcsh/search.py +5 -1
  16. npcsh/serve.py +210 -203
  17. npcsh/shell.py +11 -25
  18. npcsh/shell_helpers.py +489 -99
  19. npcsh/stream.py +87 -554
  20. npcsh/video.py +5 -2
  21. npcsh/video_gen.py +69 -0
  22. npcsh-0.3.32.dist-info/METADATA +779 -0
  23. {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/RECORD +49 -47
  24. npcsh-0.3.31.dist-info/METADATA +0 -1853
  25. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/bash_executer.tool +0 -0
  26. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/calculator.tool +0 -0
  27. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/celona.npc +0 -0
  28. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/code_executor.tool +0 -0
  29. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/corca.npc +0 -0
  30. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/eriane.npc +0 -0
  31. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/foreman.npc +0 -0
  32. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/generic_search.tool +0 -0
  33. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/image_generation.tool +0 -0
  34. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/lineru.npc +0 -0
  35. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/local_search.tool +0 -0
  36. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/maurawa.npc +0 -0
  37. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
  38. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/npcsh_executor.tool +0 -0
  39. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/raone.npc +0 -0
  40. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/screen_cap.tool +0 -0
  41. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/sibiji.npc +0 -0
  42. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/slean.npc +0 -0
  43. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/sql_executor.tool +0 -0
  44. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/test_pipeline.py +0 -0
  45. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/turnic.npc +0 -0
  46. {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/welxor.npc +0 -0
  47. {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/WHEEL +0 -0
  48. {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/entry_points.txt +0 -0
  49. {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/licenses/LICENSE +0 -0
  50. {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/top_level.txt +0 -0
npcsh/response.py CHANGED
@@ -1,120 +1,28 @@
1
- from typing import Any, Dict, Generator, List, Union
2
- from pydantic import BaseModel
1
+ import json
2
+ import requests
3
+ import base64
3
4
  import os
4
- import anthropic
5
- from openai import OpenAI
6
- from google.generativeai import types
7
- from google import genai
5
+ from PIL import Image
6
+ from typing import Any, Dict, Generator, List, Union
8
7
 
9
- import google.generativeai as genai
10
- from .npc_sysenv import (
8
+ from pydantic import BaseModel
9
+ from npcsh.npc_sysenv import (
11
10
  get_system_message,
12
11
  compress_image,
13
12
  available_chat_models,
14
13
  available_reasoning_models,
15
14
  )
16
15
 
17
- import json
18
- import requests
19
- import base64
20
- from PIL import Image
21
-
22
-
23
- def get_deepseek_response(
24
- prompt: str,
25
- model: str,
26
- images: List[Dict[str, str]] = None,
27
- npc: Any = None,
28
- tools: list = None,
29
- format: Union[str, BaseModel] = None,
30
- messages: List[Dict[str, str]] = None,
31
- api_key: str = None,
32
- **kwargs,
33
- ) -> Dict[str, Any]:
34
- """
35
- Function Description:
36
- This function generates a response using the DeepSeek API.
37
- Args:
38
- prompt (str): The prompt for generating the response.
39
- model (str): The model to use for generating the response.
40
- Keyword Args:
41
- images (List[Dict[str, str]]): The list of images.
42
- npc (Any): The NPC object.
43
- format (str): The format of the response.
44
- messages (List[Dict[str, str]]): The list of messages.
45
- Returns:
46
- Any: The response generated by the DeepSeek API.
47
-
48
-
49
- """
50
- if api_key is None:
51
- api_key = os.getenv("DEEPSEEK_API_KEY", None)
52
- client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
53
-
54
- # print(client)
55
-
56
- system_message = get_system_message(npc) if npc else "You are a helpful assistant."
57
- if messages is None or len(messages) == 0:
58
- messages = [
59
- {"role": "system", "content": system_message},
60
- {"role": "user", "content": [{"type": "text", "text": prompt}]},
61
- ]
62
- if images:
63
- for image in images:
64
- # print(f"Image file exists: {os.path.exists(image['file_path'])}")
65
-
66
- with open(image["file_path"], "rb") as image_file:
67
- image_data = base64.b64encode(compress_image(image_file.read())).decode(
68
- "utf-8"
69
- )
70
- messages[-1]["content"].append(
71
- {
72
- "type": "image_url",
73
- "image_url": {
74
- "url": f"data:image/jpeg;base64,{image_data}",
75
- },
76
- }
77
- )
78
- # print(messages)
79
- # print(model)
80
- response_format = None if format == "json" else format
81
- if response_format is None:
82
- completion = client.chat.completions.create(model=model, messages=messages)
83
- llm_response = completion.choices[0].message.content
84
- items_to_return = {"response": llm_response}
85
-
86
- items_to_return["messages"] = messages
87
- # print(llm_response, model)
88
- if format == "json":
89
- try:
90
- items_to_return["response"] = json.loads(llm_response)
16
+ from litellm import completion
91
17
 
92
- return items_to_return
93
- except json.JSONDecodeError:
94
- print(f"Warning: Expected JSON response, but received: {llm_response}")
95
- return {"error": "Invalid JSON response"}
96
- else:
97
- items_to_return["messages"].append(
98
- {"role": "assistant", "content": llm_response}
99
- )
100
- return items_to_return
18
+ # import litellm
101
19
 
102
- else:
103
- if model in available_reasoning_models:
104
- raise NotImplementedError("Reasoning models do not support JSON output.")
105
- try:
106
- completion = client.beta.chat.completions.parse(
107
- model=model, messages=messages, response_format=response_format
108
- )
109
- items_to_return = {"response": completion.choices[0].message.parsed.dict()}
110
- items_to_return["messages"] = messages
20
+ # litellm._turn_on_debug()
111
21
 
112
- items_to_return["messages"].append(
113
- {"role": "assistant", "content": completion.choices[0].message.parsed}
114
- )
115
- return items_to_return
116
- except Exception as e:
117
- print("pydantic outputs not yet implemented with deepseek?")
22
+ try:
23
+ import ollama
24
+ except:
25
+ pass
118
26
 
119
27
 
120
28
  def get_ollama_response(
@@ -197,55 +105,46 @@ def get_ollama_response(
197
105
 
198
106
  return result
199
107
 
200
- # except Exception as e:
201
- # return {"error": f"Exception occurred: {e}"}
202
108
 
203
-
204
- def get_openai_response(
109
+ def get_litellm_response(
205
110
  prompt: str,
206
111
  model: str,
112
+ provider: str = None,
207
113
  images: List[Dict[str, str]] = None,
208
114
  npc: Any = None,
209
115
  tools: list = None,
210
116
  format: Union[str, BaseModel] = None,
211
- api_key: str = None,
212
117
  messages: List[Dict[str, str]] = None,
118
+ api_key: str = None,
119
+ api_url: str = None,
120
+ tool_choice: Dict = None,
213
121
  **kwargs,
214
- ):
122
+ ) -> Dict[str, Any]:
215
123
  """
216
- Function Description:
217
- This function generates a response using the OpenAI API.
218
- Args:
219
- prompt (str): The prompt for generating the response.
220
- model (str): The model to use for generating the response.
221
- Keyword Args:
222
- images (List[Dict[str, str]]): The list of images.
223
- npc (Any): The NPC object.
224
- format (str): The format of the response.
225
- api_key (str): The API key for accessing the OpenAI API.
226
- messages (List[Dict[str, str]]): The list of messages.
227
- Returns:
228
- Any: The response generated by the OpenAI API.
124
+ Improved version with consistent JSON parsing
229
125
  """
230
-
231
- # try:
232
- if api_key is None:
233
- api_key = os.environ.get("OPENAI_API_KEY", "")
234
- if len(api_key) == 0:
235
- raise ValueError("API key not found.")
236
- client = OpenAI(api_key=api_key)
237
- # print(npc)
126
+ if provider == "ollama":
127
+ return get_ollama_response(
128
+ prompt, model, images, npc, tools, format, messages, **kwargs
129
+ )
238
130
 
239
131
  system_message = get_system_message(npc) if npc else "You are a helpful assistant."
132
+ if format == "json":
133
+ prompt += """If you are a returning a json object, begin directly with the opening {.
134
+ If you are returning a json array, begin directly with the opening [.
135
+ Do not include any additional markdown formatting or leading
136
+ ```json tags in your response. The item keys should be based on the ones provided
137
+ by the user. Do not invent new ones.
138
+
139
+ """
240
140
  if messages is None or len(messages) == 0:
241
141
  messages = [
242
142
  {"role": "system", "content": system_message},
243
143
  {"role": "user", "content": [{"type": "text", "text": prompt}]},
244
144
  ]
145
+
245
146
  if images:
246
147
  for image in images:
247
- # print(f"Image file exists: {os.path.exists(image['file_path'])}")
248
-
249
148
  with open(image["file_path"], "rb") as image_file:
250
149
  image_data = base64.b64encode(compress_image(image_file.read())).decode(
251
150
  "utf-8"
@@ -253,371 +152,121 @@ def get_openai_response(
253
152
  messages[-1]["content"].append(
254
153
  {
255
154
  "type": "image_url",
256
- "image_url": {
257
- "url": f"data:image/jpeg;base64,{image_data}",
258
- },
155
+ "image_url": {"url": f"data:image/jpeg;base64,{image_data}"},
259
156
  }
260
157
  )
261
- # print(model)
262
- response_format = None if format == "json" else format
263
- if response_format is None:
264
- completion = client.chat.completions.create(model=model, messages=messages)
265
- llm_response = completion.choices[0].message.content
266
- items_to_return = {"response": llm_response}
267
-
268
- items_to_return["messages"] = messages
269
- # print(llm_response, model)
270
- if format == "json":
271
- if model in available_reasoning_models:
272
- raise NotImplementedError(
273
- "Reasoning models do not support JSON output."
274
- )
275
- try:
276
- if isinstance(llm_response, str):
277
- if llm_response.startswith("```json"):
278
- llm_response = (
279
- llm_response.replace("```json", "")
280
- .replace("```", "")
281
- .strip()
282
- )
283
- llm_response = json.loads(llm_response)
284
- items_to_return["response"] = llm_response
285
- return items_to_return
158
+ api_params = {
159
+ "messages": messages,
160
+ }
161
+ if provider is None:
162
+ split = model.split("/")
163
+ if len(split) == 2:
164
+ provider = split[0]
165
+ # if provider == "ollama":
166
+ # uncomment the two lines below once litellm works better with ollama
167
+ # litellm works better with ollama_chat
168
+ # api_params["api_base"] = "http://localhost:11434"
169
+ # provider = "ollama_chat"
170
+ api_params["format"] = format
171
+
172
+ # else:
173
+ if api_url is not None:
174
+ # the default api_url is for npcsh's NPCSH_API_URL
175
+ # for an openai-like provider.
176
+ # so the proviuder should only ever be openai-like
177
+ if provider == "openai-like":
178
+ api_params["api_base"] = api_url
286
179
 
287
- except json.JSONDecodeError:
288
- print(f"Warning: Expected JSON response, but received: {llm_response}")
289
- return {"error": "Invalid JSON response"}
290
- else:
291
- items_to_return["messages"].append(
292
- {"role": "assistant", "content": llm_response}
293
- )
294
- return items_to_return
180
+ if format == "json":
181
+ api_params["response_format"] = {"type": "json_object"}
182
+ elif format is not None:
183
+ # pydantic model
184
+ api_params["response_format"] = format
295
185
 
186
+ if "/" not in model: # litellm expects provder/model so let ppl provide like that
187
+ model_str = f"{provider}/{model}"
296
188
  else:
297
- completion = client.beta.chat.completions.parse(
298
- model=model, messages=messages, response_format=response_format
299
- )
300
- items_to_return = {"response": completion.choices[0].message.parsed.dict()}
301
- items_to_return["messages"] = messages
302
-
303
- items_to_return["messages"].append(
304
- {"role": "assistant", "content": completion.choices[0].message.parsed}
305
- )
306
- return items_to_return
307
- # except Exception as e:
308
- # print("openai api key", api_key)
309
- # print(f"Error interacting with OpenAI: {e}")
310
- # return f"Error interacting with OpenAI: {e}"
311
-
312
-
313
- def get_anthropic_response(
314
- prompt: str,
315
- model: str,
316
- images: List[Dict[str, str]] = None,
317
- npc: Any = None,
318
- tools: list = None,
319
- format: str = None,
320
- api_key: str = None,
321
- messages: List[Dict[str, str]] = None,
322
- **kwargs,
323
- ):
324
- """
325
- Function Description:
326
- This function generates a response using the Anthropic API.
327
- Args:
328
- prompt (str): The prompt for generating the response.
329
- model (str): The model to use for generating the response.
330
- Keyword Args:
331
- images (List[Dict[str, str]]): The list of images.
332
- npc (Any): The NPC object.
333
- format (str): The format of the response.
334
- api_key (str): The API key for accessing the Anthropic API.
335
- messages (List[Dict[str, str]]): The list of messages.
336
- Returns:
337
- Any: The response generated by the Anthropic API.
338
- """
189
+ model_str = model
190
+ api_params["model"] = model_str
191
+ if api_key is not None:
192
+ api_params["api_key"] = api_key
193
+ # Add tools if provided
194
+ if tools:
195
+ api_params["tools"] = tools
196
+ # Add tool choice if specified
197
+ if tool_choice:
198
+ api_params["tool_choice"] = tool_choice
199
+ if kwargs:
200
+ for key, value in kwargs.items():
201
+ # minimum parameter set for anthropic to work
202
+ if key in [
203
+ "stream",
204
+ "stop",
205
+ "temperature",
206
+ "top_p",
207
+ "max_tokens",
208
+ "max_completion_tokens",
209
+ "tools",
210
+ "tool_choice",
211
+ "extra_headers",
212
+ "parallel_tool_calls",
213
+ "response_format",
214
+ "user",
215
+ ]:
216
+ api_params[key] = value
339
217
 
340
218
  try:
341
- if api_key is None:
342
- api_key = os.environ.get("ANTHROPIC_API_KEY")
343
- client = anthropic.Anthropic(api_key=api_key)
344
-
345
- if messages[0]["role"] == "system":
346
- system_message = messages[0]
347
- messages = messages[1:]
348
- elif npc is not None:
349
- system_message = get_system_message(npc)
350
-
351
- # Preprocess messages to ensure content is a list of dicts
352
- for message in messages:
353
- if isinstance(message["content"], str):
354
- message["content"] = [{"type": "text", "text": message["content"]}]
355
- # Add images if provided
356
- if images:
357
- for img in images:
358
- with open(img["file_path"], "rb") as image_file:
359
- img["data"] = base64.b64encode(image_file.read()).decode("utf-8")
360
- img["media_type"] = "image/jpeg"
361
- messages[-1]["content"].append(
362
- {
363
- "type": "image",
364
- "source": {
365
- "type": "base64",
366
- "media_type": img["media_type"],
367
- "data": img["data"],
368
- },
369
- }
370
- )
219
+ # print(api_params)
220
+ # litellm completion appears to have some
221
+ # ollama issues, so will default to our
222
+ # custom implementation until we can revisit
223
+ # when its likely more better supported
224
+ resp = completion(
225
+ **api_params,
226
+ )
371
227
 
372
- # Prepare API call parameters
228
+ # Get the raw response content
229
+ llm_response = resp.choices[0].message.content
373
230
 
374
- api_params = {
375
- "model": model,
231
+ # Prepare return dict
232
+ items_to_return = {
233
+ "response": llm_response,
376
234
  "messages": messages,
377
- "max_tokens": kwargs.get("max_tokens", 8192),
378
- "stream": False,
379
- "system": system_message,
235
+ "raw_response": resp, # Include the full response for debugging
380
236
  }
381
237
 
382
- # Add tools if provided
383
- if tools:
384
- api_params["tools"] = tools
385
-
386
- # Add tool choice if specified
387
- if tool_choice:
388
- api_params["tool_choice"] = tool_choice
389
-
390
- # Make the API call
391
- response = client.messages.create(**api_params)
392
-
393
- llm_response = message.content[0].text
394
- items_to_return = {"response": llm_response}
395
- messages.append(
396
- {"role": "assistant", "content": {"type": "text", "text": llm_response}}
397
- )
398
- items_to_return["messages"] = messages
399
-
400
- # Handle JSON format if requested
238
+ # Handle JSON format requests
239
+ print(format)
401
240
  if format == "json":
402
241
  try:
403
242
  if isinstance(llm_response, str):
404
- if llm_response.startswith("```json"):
405
- llm_response = (
406
- llm_response.replace("```json", "")
407
- .replace("```", "")
408
- .strip()
409
- )
410
- llm_response = json.loads(llm_response)
411
- items_to_return["response"] = llm_response
243
+ print("converting the json")
244
+ loaded = json.loads(llm_response)
245
+ else:
246
+ loaded = llm_response # Assume it's already parsed
247
+ if "json" in loaded:
248
+ items_to_return["response"] = loaded["json"]
249
+ else:
250
+ items_to_return["response"] = loaded
251
+
252
+ except (json.JSONDecodeError, TypeError) as e:
253
+ print(f"JSON parsing error: {str(e)}")
254
+ print(f"Raw response: {llm_response}")
255
+ items_to_return["error"] = "Invalid JSON response"
412
256
  return items_to_return
413
- except json.JSONDecodeError:
414
- print(f"Warning: Expected JSON response, but received: {llm_response}")
415
- return {"response": llm_response, "error": "Invalid JSON response"}
416
- else:
417
- # only append to messages if the response is not json
418
- messages.append({"role": "assistant", "content": llm_response})
419
- # print("teststea")
420
- return items_to_return
421
-
422
- except Exception as e:
423
- return f"Error interacting with Anthropic llm response: {e}"
424
-
425
-
426
- def get_openai_like_response(
427
- prompt: str,
428
- model: str,
429
- api_url: str,
430
- api_key: str = None,
431
- npc: Any = None,
432
- tools: list = None,
433
- images: list = None,
434
- messages: list = None,
435
- format=None,
436
- **kwargs,
437
- ) -> Dict[str, Any]:
438
- """
439
- Function Description:
440
- This function generates a response using API.
441
- penai-like
442
- Args:
443
- prompt (str): The prompt for generating the response.
444
- model (str): The model to use for generating the response.
445
- Keyword Args:
446
- images (List[Dict[str, str]]): The list of images.
447
- npc (Any): The NPC object.
448
- format (str): The format of the response.
449
- messages (List[Dict[str, str]]): The list of messages.
450
- Returns:
451
- Any: The response generated by the DeepSeek API.
452
-
453
-
454
- """
455
- if api_key is None:
456
- api_key = "dummy_api_key"
457
- client = OpenAI(api_key=api_key, base_url=api_url)
458
- system_message = get_system_message(npc) if npc else "You are a helpful assistant."
459
- if messages is None or len(messages) == 0:
460
- messages = [
461
- {"role": "system", "content": system_message},
462
- {"role": "user", "content": [{"type": "text", "text": prompt}]},
463
- ]
464
- if images:
465
- for image in images:
466
- # print(f"Image file exists: {os.path.exists(image['file_path'])}")
467
-
468
- with open(image["file_path"], "rb") as image_file:
469
- image_data = base64.b64encode(compress_image(image_file.read())).decode(
470
- "utf-8"
471
- )
472
- messages[-1]["content"].append(
473
- {
474
- "type": "image_url",
475
- "image_url": {
476
- "url": f"data:image/jpeg;base64,{image_data}",
477
- },
478
- }
479
- )
480
-
481
- response_format = None if format == "json" else format
482
- if response_format is None:
483
- completion = client.chat.completions.create(model=model, messages=messages)
484
- llm_response = completion.choices[0].message.content
485
- items_to_return = {"response": llm_response}
486
-
487
- items_to_return["messages"] = messages
488
- # print(llm_response, model)
489
- if format == "json":
490
- if model in available_reasoning_models:
491
- raise NotImplementedError(
492
- "Reasoning models do not support JSON output."
493
- )
494
- try:
495
- if isinstance(llm_response, str):
496
- if llm_response.startswith("```json"):
497
- llm_response = (
498
- llm_response.replace("```json", "")
499
- .replace("```", "")
500
- .strip()
501
- )
502
- # print(llm_response)
503
- items_to_return["response"] = json.loads(llm_response)
504
- return items_to_return
505
- except json.JSONDecodeError:
506
- print(f"Warning: Expected JSON response, but received: {llm_response}")
507
- return {"error": "Invalid JSON response"}
508
- else:
509
- items_to_return["messages"].append(
510
- {"role": "assistant", "content": llm_response}
511
- )
512
- return items_to_return
513
-
514
- else:
515
- if model in available_reasoning_models:
516
- raise NotImplementedError("Reasoning models do not support JSON output.")
517
-
518
- completion = client.beta.chat.completions.parse(
519
- model=model, messages=messages, response_format=response_format
520
- )
521
-
522
- items_to_return = {"response": completion.choices[0].message.parsed.dict()}
523
- items_to_return["messages"] = messages
524
257
 
258
+ # Add assistant response to message history
525
259
  items_to_return["messages"].append(
526
- {"role": "assistant", "content": completion.choices[0].message.parsed}
527
- )
528
- return items_to_return
529
-
530
-
531
- def get_gemini_response(
532
- prompt: str,
533
- model: str,
534
- images: List[Dict[str, str]] = None,
535
- npc: Any = None,
536
- tools: list = None,
537
- format: Union[str, BaseModel] = None,
538
- messages: List[Dict[str, str]] = None,
539
- api_key: str = None,
540
- **kwargs,
541
- ) -> Dict[str, Any]:
542
- """
543
- Generates a response using the Gemini API.
544
- """
545
- # Configure the Gemini API
546
- if api_key is None:
547
- genai.configure(api_key=gemini_api_key)
548
-
549
- # Prepare the system message
550
- system_message = get_system_message(npc) if npc else "You are a helpful assistant."
551
- model = genai.GenerativeModel(model, system_instruction=system_message)
552
-
553
- # Extract just the content to send to the model
554
- if messages is None or len(messages) == 0:
555
- content_to_send = prompt
556
- else:
557
- # Get the latest message's content
558
- latest_message = messages[-1]
559
- content_to_send = (
560
- latest_message["parts"][0]
561
- if "parts" in latest_message
562
- else latest_message.get("content", prompt)
260
+ {
261
+ "role": "assistant",
262
+ "content": (
263
+ llm_response if isinstance(llm_response, str) else str(llm_response)
264
+ ),
265
+ }
563
266
  )
564
- history = []
565
- if messages:
566
- for msg in messages:
567
- if "content" in msg:
568
- # Convert content to parts format
569
- history.append({"role": msg["role"], "parts": [msg["content"]]})
570
- else:
571
- # Already in parts format
572
- history.append(msg)
573
- # If no history, create a new message list
574
- if not history:
575
- history = [{"role": "user", "parts": [prompt]}]
576
- elif isinstance(prompt, str): # Add new prompt to existing history
577
- history.append({"role": "user", "parts": [prompt]})
578
-
579
- # Handle images if provided
580
- # Handle images by adding them to the last message's parts
581
- if images:
582
- for image in images:
583
- with open(image["file_path"], "rb") as image_file:
584
- img = Image.open(image_file)
585
- history[-1]["parts"].append(img)
586
- # Generate the response
587
- # try:
588
- # Send the entire conversation history to maintain context
589
- response = model.generate_content(history)
590
- llm_response = response.text
591
-
592
- # Filter out empty parts
593
- if isinstance(llm_response, list):
594
- llm_response = " ".join([part for part in llm_response if part.strip()])
595
- elif not llm_response.strip():
596
- llm_response = ""
597
267
 
598
- # Prepare the return dictionary
599
- items_to_return = {"response": llm_response, "messages": history}
600
- # print(llm_response, type(llm_response))
601
-
602
- # Handle JSON format if specified
603
- if format == "json":
604
- if isinstance(llm_response, str):
605
- if llm_response.startswith("```json"):
606
- llm_response = (
607
- llm_response.replace("```json", "").replace("```", "").strip()
608
- )
609
-
610
- try:
611
- items_to_return["response"] = json.loads(llm_response)
612
- except json.JSONDecodeError:
613
- print(f"Warning: Expected JSON response, but received: {llm_response}")
614
- return {"error": "Invalid JSON response"}
615
- else:
616
- # Append the model's response to the messages
617
- history.append({"role": "model", "parts": [llm_response]})
618
- items_to_return["messages"] = history
619
-
620
- return items_to_return
268
+ return items_to_return
621
269
 
622
- # except Exception as e:
623
- # return {"error": f"Error generating response: {str(e)}"}
270
+ except Exception as e:
271
+ print(f"Error in get_litellm_response: {str(e)}")
272
+ return {"error": str(e), "messages": messages, "response": None}