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.
- npcsh/audio.py +540 -181
- npcsh/audio_gen.py +1 -0
- npcsh/cli.py +8 -10
- npcsh/conversation.py +14 -251
- npcsh/dataframes.py +13 -5
- npcsh/helpers.py +5 -0
- npcsh/image.py +2 -2
- npcsh/image_gen.py +38 -38
- npcsh/knowledge_graph.py +4 -4
- npcsh/llm_funcs.py +517 -349
- npcsh/npc_compiler.py +32 -23
- npcsh/npc_sysenv.py +5 -0
- npcsh/plonk.py +2 -2
- npcsh/response.py +131 -482
- npcsh/search.py +5 -1
- npcsh/serve.py +210 -203
- npcsh/shell.py +11 -25
- npcsh/shell_helpers.py +489 -99
- npcsh/stream.py +87 -554
- npcsh/video.py +5 -2
- npcsh/video_gen.py +69 -0
- npcsh-0.3.32.dist-info/METADATA +779 -0
- {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/RECORD +49 -47
- npcsh-0.3.31.dist-info/METADATA +0 -1853
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/bash_executer.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/calculator.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/celona.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/code_executor.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/eriane.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/generic_search.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/image_generation.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/lineru.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/local_search.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/maurawa.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/npcsh_executor.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/raone.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/screen_cap.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/slean.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/sql_executor.tool +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/test_pipeline.py +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/turnic.npc +0 -0
- {npcsh-0.3.31.data → npcsh-0.3.32.data}/data/npcsh/npc_team/welxor.npc +0 -0
- {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/WHEEL +0 -0
- {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/entry_points.txt +0 -0
- {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/licenses/LICENSE +0 -0
- {npcsh-0.3.31.dist-info → npcsh-0.3.32.dist-info}/top_level.txt +0 -0
npcsh/llm_funcs.py
CHANGED
|
@@ -19,7 +19,7 @@ from google.generativeai import types
|
|
|
19
19
|
import google.generativeai as genai
|
|
20
20
|
from sqlalchemy import create_engine
|
|
21
21
|
|
|
22
|
-
from .npc_sysenv import (
|
|
22
|
+
from npcsh.npc_sysenv import (
|
|
23
23
|
get_system_message,
|
|
24
24
|
get_available_models,
|
|
25
25
|
get_model_and_provider,
|
|
@@ -35,50 +35,41 @@ from .npc_sysenv import (
|
|
|
35
35
|
NPCSH_REASONING_PROVIDER,
|
|
36
36
|
NPCSH_IMAGE_GEN_MODEL,
|
|
37
37
|
NPCSH_IMAGE_GEN_PROVIDER,
|
|
38
|
-
|
|
38
|
+
NPCSH_VIDEO_GEN_MODEL,
|
|
39
|
+
NPCSH_VIDEO_GEN_PROVIDER,
|
|
39
40
|
NPCSH_VISION_MODEL,
|
|
40
41
|
NPCSH_VISION_PROVIDER,
|
|
41
42
|
available_reasoning_models,
|
|
42
43
|
available_chat_models,
|
|
43
44
|
)
|
|
44
45
|
|
|
45
|
-
from .stream import
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
get_anthropic_stream,
|
|
49
|
-
get_openai_like_stream,
|
|
50
|
-
get_deepseek_stream,
|
|
51
|
-
get_gemini_stream,
|
|
46
|
+
from npcsh.stream import get_litellm_stream
|
|
47
|
+
from npcsh.conversation import (
|
|
48
|
+
get_litellm_conversation,
|
|
52
49
|
)
|
|
53
|
-
from .
|
|
54
|
-
|
|
55
|
-
get_openai_conversation,
|
|
56
|
-
get_openai_like_conversation,
|
|
57
|
-
get_anthropic_conversation,
|
|
58
|
-
get_deepseek_conversation,
|
|
59
|
-
get_gemini_conversation,
|
|
50
|
+
from npcsh.response import (
|
|
51
|
+
get_litellm_response,
|
|
60
52
|
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
get_ollama_response,
|
|
64
|
-
get_openai_response,
|
|
65
|
-
get_anthropic_response,
|
|
66
|
-
get_openai_like_response,
|
|
67
|
-
get_deepseek_response,
|
|
68
|
-
get_gemini_response,
|
|
53
|
+
from npcsh.image_gen import (
|
|
54
|
+
generate_image_litellm,
|
|
69
55
|
)
|
|
70
|
-
from .
|
|
71
|
-
|
|
72
|
-
generate_image_hf_diffusion,
|
|
56
|
+
from npcsh.video_gen import (
|
|
57
|
+
generate_video_diffusers,
|
|
73
58
|
)
|
|
74
59
|
|
|
75
|
-
from .embeddings import (
|
|
60
|
+
from npcsh.embeddings import (
|
|
76
61
|
get_ollama_embeddings,
|
|
77
62
|
get_openai_embeddings,
|
|
78
63
|
get_anthropic_embeddings,
|
|
79
64
|
store_embeddings_for_model,
|
|
80
65
|
)
|
|
81
66
|
|
|
67
|
+
import asyncio
|
|
68
|
+
import sys
|
|
69
|
+
from queue import Queue
|
|
70
|
+
from threading import Thread
|
|
71
|
+
import select
|
|
72
|
+
|
|
82
73
|
|
|
83
74
|
def generate_image(
|
|
84
75
|
prompt: str,
|
|
@@ -87,9 +78,7 @@ def generate_image(
|
|
|
87
78
|
filename: str = None,
|
|
88
79
|
npc: Any = None,
|
|
89
80
|
):
|
|
90
|
-
"""
|
|
91
|
-
Function Description:
|
|
92
|
-
This function generates an image using the specified provider and model.
|
|
81
|
+
"""This function generates an image using the specified provider and model.
|
|
93
82
|
Args:
|
|
94
83
|
prompt (str): The prompt for generating the image.
|
|
95
84
|
Keyword Args:
|
|
@@ -118,23 +107,11 @@ def generate_image(
|
|
|
118
107
|
os.path.expanduser("~/.npcsh/images/")
|
|
119
108
|
+ f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
|
120
109
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
prompt,
|
|
127
|
-
model,
|
|
128
|
-
npc=npc,
|
|
129
|
-
)
|
|
130
|
-
# elif provider == "anthropic":
|
|
131
|
-
# image = generate_image_anthropic(prompt, model, anthropic_api_key)
|
|
132
|
-
# elif provider == "openai-like":
|
|
133
|
-
# image = generate_image_openai_like(prompt, model, npc.api_url, openai_api_key)
|
|
134
|
-
elif provider == "diffusers":
|
|
135
|
-
image = generate_image_hf_diffusion(prompt, model)
|
|
136
|
-
else:
|
|
137
|
-
image = None
|
|
110
|
+
generate_image_litellm(
|
|
111
|
+
prompt=prompt,
|
|
112
|
+
model=model,
|
|
113
|
+
provider=provider,
|
|
114
|
+
)
|
|
138
115
|
# save image
|
|
139
116
|
# check if image is a PIL image
|
|
140
117
|
if isinstance(image, PIL.Image.Image):
|
|
@@ -191,9 +168,7 @@ def get_llm_response(
|
|
|
191
168
|
context=None,
|
|
192
169
|
**kwargs,
|
|
193
170
|
):
|
|
194
|
-
"""
|
|
195
|
-
Function Description:
|
|
196
|
-
This function generates a response using the specified provider and model.
|
|
171
|
+
"""This function generates a response using the specified provider and model.
|
|
197
172
|
Args:
|
|
198
173
|
prompt (str): The prompt for generating the response.
|
|
199
174
|
Keyword Args:
|
|
@@ -208,6 +183,7 @@ def get_llm_response(
|
|
|
208
183
|
"""
|
|
209
184
|
if model is not None and provider is not None:
|
|
210
185
|
pass
|
|
186
|
+
|
|
211
187
|
elif provider is None and model is not None:
|
|
212
188
|
provider = lookup_provider(model)
|
|
213
189
|
|
|
@@ -227,84 +203,17 @@ def get_llm_response(
|
|
|
227
203
|
model = "llama3.2"
|
|
228
204
|
# print(provider, model)
|
|
229
205
|
# print(provider, model)
|
|
230
|
-
if provider == "ollama":
|
|
231
|
-
if model is None:
|
|
232
|
-
if images is not None:
|
|
233
|
-
model = "llama:7b"
|
|
234
|
-
else:
|
|
235
|
-
model = "llama3.2"
|
|
236
|
-
elif images is not None and model not in [
|
|
237
|
-
"x/llama3.2-vision",
|
|
238
|
-
"llama3.2-vision",
|
|
239
|
-
"llava-llama3",
|
|
240
|
-
"bakllava",
|
|
241
|
-
"moondream",
|
|
242
|
-
"llava-phi3",
|
|
243
|
-
"minicpm-v",
|
|
244
|
-
"hhao/openbmb-minicpm-llama3-v-2_5",
|
|
245
|
-
"aiden_lu/minicpm-v2.6",
|
|
246
|
-
"xuxx/minicpm2.6",
|
|
247
|
-
"benzie/llava-phi-3",
|
|
248
|
-
"mskimomadto/chat-gph-vision",
|
|
249
|
-
"xiayu/openbmb-minicpm-llama3-v-2_5",
|
|
250
|
-
"0ssamaak0/xtuner-llava",
|
|
251
|
-
"srizon/pixie",
|
|
252
|
-
"jyan1/paligemma-mix-224",
|
|
253
|
-
"qnguyen3/nanollava",
|
|
254
|
-
"knoopx/llava-phi-2",
|
|
255
|
-
"nsheth/llama-3-lumimaid-8b-v0.1-iq-imatrix",
|
|
256
|
-
"bigbug/minicpm-v2.5",
|
|
257
|
-
]:
|
|
258
|
-
model = "llava:7b"
|
|
259
|
-
# print(model)
|
|
260
|
-
return get_ollama_response(
|
|
261
|
-
prompt, model, npc=npc, messages=messages, images=images, **kwargs
|
|
262
|
-
)
|
|
263
|
-
elif provider == "gemini":
|
|
264
|
-
if model is None:
|
|
265
|
-
model = "gemini-2.0-flash"
|
|
266
|
-
return get_gemini_response(
|
|
267
|
-
prompt, model, npc=npc, messages=messages, images=images, **kwargs
|
|
268
|
-
)
|
|
269
206
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
# print(model)
|
|
281
|
-
return get_openai_response(
|
|
282
|
-
prompt, model, npc=npc, messages=messages, images=images, **kwargs
|
|
283
|
-
)
|
|
284
|
-
elif provider == "openai-like":
|
|
285
|
-
if api_url is None:
|
|
286
|
-
raise ValueError("api_url is required for openai-like provider")
|
|
287
|
-
return get_openai_like_response(
|
|
288
|
-
prompt,
|
|
289
|
-
model,
|
|
290
|
-
api_url,
|
|
291
|
-
api_key,
|
|
292
|
-
npc=npc,
|
|
293
|
-
messages=messages,
|
|
294
|
-
images=images,
|
|
295
|
-
**kwargs,
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
elif provider == "anthropic":
|
|
299
|
-
if model is None:
|
|
300
|
-
model = "claude-3-haiku-20240307"
|
|
301
|
-
return get_anthropic_response(
|
|
302
|
-
prompt, model, npc=npc, messages=messages, images=images, **kwargs
|
|
303
|
-
)
|
|
304
|
-
else:
|
|
305
|
-
# print(provider)
|
|
306
|
-
# print(model)
|
|
307
|
-
return "Error: Invalid provider specified."
|
|
207
|
+
response = get_litellm_response(
|
|
208
|
+
prompt,
|
|
209
|
+
model=model,
|
|
210
|
+
provider=provider,
|
|
211
|
+
npc=npc,
|
|
212
|
+
api_url=api_url,
|
|
213
|
+
api_key=api_key,
|
|
214
|
+
**kwargs,
|
|
215
|
+
)
|
|
216
|
+
return response
|
|
308
217
|
|
|
309
218
|
|
|
310
219
|
def get_stream(
|
|
@@ -318,9 +227,7 @@ def get_stream(
|
|
|
318
227
|
context=None,
|
|
319
228
|
**kwargs,
|
|
320
229
|
) -> List[Dict[str, str]]:
|
|
321
|
-
"""
|
|
322
|
-
Function Description:
|
|
323
|
-
This function generates a streaming response using the specified provider and model
|
|
230
|
+
"""This function generates a streaming response using the specified provider and model
|
|
324
231
|
Args:
|
|
325
232
|
messages (List[Dict[str, str]]): The list of messages in the conversation.
|
|
326
233
|
Keyword Args:
|
|
@@ -335,6 +242,7 @@ def get_stream(
|
|
|
335
242
|
if model is not None and provider is not None:
|
|
336
243
|
pass
|
|
337
244
|
elif model is not None and provider is None:
|
|
245
|
+
print(provider)
|
|
338
246
|
provider = lookup_provider(model)
|
|
339
247
|
elif npc is not None:
|
|
340
248
|
if npc.provider is not None:
|
|
@@ -347,32 +255,50 @@ def get_stream(
|
|
|
347
255
|
provider = "ollama"
|
|
348
256
|
model = "llama3.2"
|
|
349
257
|
# print(model, provider)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
258
|
+
|
|
259
|
+
return get_litellm_stream(
|
|
260
|
+
messages,
|
|
261
|
+
model=model,
|
|
262
|
+
provider=provider,
|
|
263
|
+
npc=npc,
|
|
264
|
+
api_url=api_url,
|
|
265
|
+
api_key=api_key,
|
|
266
|
+
images=images,
|
|
267
|
+
**kwargs,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def generate_video(
|
|
272
|
+
prompt,
|
|
273
|
+
model: str = NPCSH_VIDEO_GEN_MODEL,
|
|
274
|
+
provider: str = NPCSH_VIDEO_GEN_PROVIDER,
|
|
275
|
+
npc: Any = None,
|
|
276
|
+
device: str = "cpu",
|
|
277
|
+
output_path="",
|
|
278
|
+
num_inference_steps=10,
|
|
279
|
+
num_frames=10,
|
|
280
|
+
height=256,
|
|
281
|
+
width=256,
|
|
282
|
+
messages: list = None,
|
|
283
|
+
):
|
|
284
|
+
"""
|
|
285
|
+
Function Description:
|
|
286
|
+
This function generates a video using the Stable Diffusion API.
|
|
287
|
+
Args:
|
|
288
|
+
prompt (str): The prompt for generating the video.
|
|
289
|
+
model_id (str): The Hugging Face model ID to use for Stable Diffusion.
|
|
290
|
+
device (str): The device to run the model on ('cpu' or 'cuda').
|
|
291
|
+
Returns:
|
|
292
|
+
PIL.Image: The generated image.
|
|
293
|
+
"""
|
|
294
|
+
output_path = generate_video_diffusers(
|
|
295
|
+
prompt,
|
|
296
|
+
model,
|
|
297
|
+
npc=npc,
|
|
298
|
+
device=device,
|
|
299
|
+
)
|
|
300
|
+
if provider == "diffusers":
|
|
301
|
+
return {"output": "output path at " + output_path, "messages": messages}
|
|
376
302
|
|
|
377
303
|
|
|
378
304
|
def get_conversation(
|
|
@@ -385,9 +311,7 @@ def get_conversation(
|
|
|
385
311
|
context=None,
|
|
386
312
|
**kwargs,
|
|
387
313
|
) -> List[Dict[str, str]]:
|
|
388
|
-
"""
|
|
389
|
-
Function Description:
|
|
390
|
-
This function generates a conversation using the specified provider and model.
|
|
314
|
+
"""This function generates a conversation using the specified provider and model.
|
|
391
315
|
Args:
|
|
392
316
|
messages (List[Dict[str, str]]): The list of messages in the conversation.
|
|
393
317
|
Keyword Args:
|
|
@@ -397,7 +321,6 @@ def get_conversation(
|
|
|
397
321
|
Returns:
|
|
398
322
|
List[Dict[str, str]]: The list of messages in the conversation.
|
|
399
323
|
"""
|
|
400
|
-
|
|
401
324
|
if model is not None and provider is not None:
|
|
402
325
|
pass # Use explicitly provided model and provider
|
|
403
326
|
elif model is not None and provider is None:
|
|
@@ -410,30 +333,15 @@ def get_conversation(
|
|
|
410
333
|
provider = "ollama"
|
|
411
334
|
model = "llava:7b" if images is not None else "llama3.2"
|
|
412
335
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
elif provider == "openai-like":
|
|
423
|
-
return get_openai_like_conversation(
|
|
424
|
-
messages, model, api_url, npc=npc, images=images, **kwargs
|
|
425
|
-
)
|
|
426
|
-
elif provider == "anthropic":
|
|
427
|
-
return get_anthropic_conversation(
|
|
428
|
-
messages, model, npc=npc, images=images, **kwargs
|
|
429
|
-
)
|
|
430
|
-
elif provider == "gemini":
|
|
431
|
-
return get_gemini_conversation(messages, model, npc=npc, **kwargs)
|
|
432
|
-
elif provider == "deepseek":
|
|
433
|
-
return get_deepseek_conversation(messages, model, npc=npc, **kwargs)
|
|
434
|
-
|
|
435
|
-
else:
|
|
436
|
-
return "Error: Invalid provider specified."
|
|
336
|
+
return get_litellm_conversation(
|
|
337
|
+
messages,
|
|
338
|
+
model=model,
|
|
339
|
+
provider=provider,
|
|
340
|
+
npc=npc,
|
|
341
|
+
api_url=api_url,
|
|
342
|
+
images=images,
|
|
343
|
+
**kwargs,
|
|
344
|
+
)
|
|
437
345
|
|
|
438
346
|
|
|
439
347
|
def execute_llm_question(
|
|
@@ -540,9 +448,7 @@ def execute_llm_command(
|
|
|
540
448
|
stream=False,
|
|
541
449
|
context=None,
|
|
542
450
|
) -> str:
|
|
543
|
-
"""
|
|
544
|
-
Function Description:
|
|
545
|
-
This function executes an LLM command.
|
|
451
|
+
"""This function executes an LLM command.
|
|
546
452
|
Args:
|
|
547
453
|
command (str): The command to execute.
|
|
548
454
|
|
|
@@ -749,16 +655,16 @@ def check_llm_command(
|
|
|
749
655
|
api_url: str = NPCSH_API_URL,
|
|
750
656
|
api_key: str = None,
|
|
751
657
|
npc: Any = None,
|
|
658
|
+
npc_team: Any = None,
|
|
752
659
|
retrieved_docs=None,
|
|
753
660
|
messages: List[Dict[str, str]] = None,
|
|
754
661
|
images: list = None,
|
|
755
662
|
n_docs=5,
|
|
756
663
|
stream=False,
|
|
757
664
|
context=None,
|
|
665
|
+
whisper=False,
|
|
758
666
|
):
|
|
759
|
-
"""
|
|
760
|
-
Function Description:
|
|
761
|
-
This function checks an LLM command.
|
|
667
|
+
"""This function checks an LLM command.
|
|
762
668
|
Args:
|
|
763
669
|
command (str): The command to check.
|
|
764
670
|
Keyword Args:
|
|
@@ -814,71 +720,73 @@ ReAct choices then will enter reasoning flow
|
|
|
814
720
|
|
|
815
721
|
4. Is it a complex request that actually requires more than one
|
|
816
722
|
tool to be called, perhaps in a sequence?
|
|
723
|
+
Sequences should only be used for more than one consecutive tool call. do not invoke seequences for single tool calls.
|
|
817
724
|
|
|
818
725
|
5. is there a need for the user to provide additional input to fulfill the request?
|
|
819
726
|
|
|
820
727
|
|
|
821
728
|
|
|
822
|
-
Available tools:
|
|
823
729
|
"""
|
|
824
730
|
|
|
825
|
-
if
|
|
826
|
-
npc.
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
"""
|
|
731
|
+
if npc is not None or npc_team is not None:
|
|
732
|
+
if (npc.tools_dict is None or npc.tools_dict == {}) & (
|
|
733
|
+
npc.all_tools_dict is None or npc.all_tools_dict == {}
|
|
734
|
+
):
|
|
735
|
+
prompt += "No tools available. Do not invoke tools."
|
|
736
|
+
else:
|
|
737
|
+
prompt += "Available tools: \n"
|
|
738
|
+
tools_set = {}
|
|
739
|
+
|
|
740
|
+
if npc.tools_dict is not None:
|
|
741
|
+
for tool_name, tool in npc.tools_dict.items():
|
|
742
|
+
if tool_name not in tools_set:
|
|
743
|
+
tools_set[tool_name] = tool.description
|
|
744
|
+
if npc.all_tools_dict is not None:
|
|
745
|
+
for tool_name, tool in npc.all_tools_dict.items():
|
|
746
|
+
if tool_name not in tools_set:
|
|
747
|
+
tools_set[tool_name] = tool.description
|
|
748
|
+
|
|
749
|
+
for tool_name, tool_description in tools_set.items():
|
|
750
|
+
prompt += f"""
|
|
846
751
|
|
|
847
|
-
|
|
848
|
-
|
|
752
|
+
{tool_name} : {tool_description} \n
|
|
753
|
+
"""
|
|
849
754
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
else:
|
|
854
|
-
print(npc.resolved_npcs)
|
|
855
|
-
for i, npc_in_network in enumerate(npc.resolved_npcs):
|
|
856
|
-
name = list(npc_in_network.keys())[0]
|
|
857
|
-
npc_obj = npc_in_network[name]
|
|
858
|
-
|
|
859
|
-
if hasattr(npc_obj, "name"):
|
|
860
|
-
name_to_include = npc_obj.name
|
|
861
|
-
elif "name " in npc_obj:
|
|
862
|
-
name_to_include = npc_obj["name"]
|
|
863
|
-
|
|
864
|
-
if hasattr(npc_obj, "primary_directive"):
|
|
865
|
-
primary_directive_to_include = npc_obj.primary_directive
|
|
866
|
-
elif "primary_directive" in npc_obj:
|
|
867
|
-
primary_directive_to_include = npc_obj["primary_directive"]
|
|
755
|
+
if len(npc.resolved_npcs) == 0:
|
|
756
|
+
prompt += "No NPCs available for alternative answers."
|
|
757
|
+
else:
|
|
868
758
|
prompt += f"""
|
|
869
|
-
|
|
759
|
+
Available NPCs for alternative answers:
|
|
870
760
|
|
|
871
|
-
|
|
872
|
-
|
|
761
|
+
"""
|
|
762
|
+
print(npc.resolved_npcs)
|
|
763
|
+
for i, npc_in_network in enumerate(npc.resolved_npcs):
|
|
764
|
+
name = list(npc_in_network.keys())[0]
|
|
765
|
+
npc_obj = npc_in_network[name]
|
|
766
|
+
|
|
767
|
+
if hasattr(npc_obj, "name"):
|
|
768
|
+
name_to_include = npc_obj.name
|
|
769
|
+
elif "name " in npc_obj:
|
|
770
|
+
name_to_include = npc_obj["name"]
|
|
771
|
+
|
|
772
|
+
if hasattr(npc_obj, "primary_directive"):
|
|
773
|
+
primary_directive_to_include = npc_obj.primary_directive
|
|
774
|
+
elif "primary_directive" in npc_obj:
|
|
775
|
+
primary_directive_to_include = npc_obj["primary_directive"]
|
|
776
|
+
prompt += f"""
|
|
777
|
+
({i})
|
|
873
778
|
|
|
779
|
+
NPC: {name_to_include}
|
|
780
|
+
Primary Directive : {primary_directive_to_include}
|
|
781
|
+
|
|
782
|
+
"""
|
|
783
|
+
if npc.shared_context:
|
|
784
|
+
prompt += f"""
|
|
785
|
+
Relevant shared context for the npc:
|
|
786
|
+
{npc.shared_context}
|
|
874
787
|
"""
|
|
875
|
-
|
|
876
|
-
prompt
|
|
877
|
-
Relevant shared context for the npc:
|
|
878
|
-
{npc.shared_context}
|
|
879
|
-
"""
|
|
880
|
-
# print("shared_context: " + str(npc.shared_context))
|
|
881
|
-
# print(prompt)
|
|
788
|
+
# print("shared_context: " + str(npc.shared_context))
|
|
789
|
+
# print(prompt)
|
|
882
790
|
|
|
883
791
|
prompt += f"""
|
|
884
792
|
In considering how to answer this, consider:
|
|
@@ -891,7 +799,7 @@ ReAct choices then will enter reasoning flow
|
|
|
891
799
|
- Whether a tool should be used.
|
|
892
800
|
|
|
893
801
|
|
|
894
|
-
Excluding time-sensitive phenomena,
|
|
802
|
+
Excluding time-sensitive phenomena or ones that require external data inputs /information,
|
|
895
803
|
most general questions can be answered without any
|
|
896
804
|
extra tools or agent passes.
|
|
897
805
|
Only use tools or pass to other NPCs
|
|
@@ -926,7 +834,8 @@ ReAct choices then will enter reasoning flow
|
|
|
926
834
|
}}
|
|
927
835
|
|
|
928
836
|
If you execute a sequence, ensure that you have a specified NPC for each tool use.
|
|
929
|
-
|
|
837
|
+
question answering is not a tool use.
|
|
838
|
+
"invoke_tool" should never be used in the list of tools when executing a sequence.
|
|
930
839
|
Remember, do not include ANY ADDITIONAL MARKDOWN FORMATTING.
|
|
931
840
|
There should be no leading ```json.
|
|
932
841
|
|
|
@@ -946,6 +855,27 @@ ReAct choices then will enter reasoning flow
|
|
|
946
855
|
{context}
|
|
947
856
|
|
|
948
857
|
"""
|
|
858
|
+
if whisper:
|
|
859
|
+
prompt += f"""
|
|
860
|
+
IMPORTANT!!!
|
|
861
|
+
|
|
862
|
+
This check is part of a npcsh whisper mode conversation.
|
|
863
|
+
|
|
864
|
+
This mode is a mode wherein the user speaks and receives
|
|
865
|
+
audio that has been played through TTS.
|
|
866
|
+
Thus, consider it to be a more casual conversation
|
|
867
|
+
and engage in regular conversation
|
|
868
|
+
unless they specifically mention in their request.
|
|
869
|
+
And if something is confusing or seems like it needs
|
|
870
|
+
additional context,
|
|
871
|
+
do not worry too mucuh about it because this
|
|
872
|
+
information is likely contained within the historical messages between
|
|
873
|
+
the user and the LLM and you can let the downstream
|
|
874
|
+
agent navigate asking followup questions .
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
"""
|
|
878
|
+
|
|
949
879
|
action_response = get_llm_response(
|
|
950
880
|
prompt,
|
|
951
881
|
model=model,
|
|
@@ -975,10 +905,11 @@ ReAct choices then will enter reasoning flow
|
|
|
975
905
|
response_content_parsed = response_content
|
|
976
906
|
|
|
977
907
|
action = response_content_parsed.get("action")
|
|
978
|
-
explanation =
|
|
908
|
+
explanation = response_content_parsed.get("explanation")
|
|
979
909
|
print(f"action chosen: {action}")
|
|
980
910
|
print(f"explanation given: {explanation}")
|
|
981
911
|
|
|
912
|
+
# print(response_content)
|
|
982
913
|
if response_content_parsed.get("tool_name"):
|
|
983
914
|
print(f"tool name: {response_content_parsed.get('tool_name')}")
|
|
984
915
|
|
|
@@ -1110,15 +1041,22 @@ ReAct choices then will enter reasoning flow
|
|
|
1110
1041
|
elif action == "execute_sequence":
|
|
1111
1042
|
tool_names = response_content_parsed.get("tool_name")
|
|
1112
1043
|
npc_names = response_content_parsed.get("npc_name")
|
|
1044
|
+
|
|
1113
1045
|
# print(npc_names)
|
|
1114
1046
|
npcs = []
|
|
1115
1047
|
# print(tool_names, npc_names)
|
|
1116
1048
|
if isinstance(npc_names, list):
|
|
1049
|
+
if len(npc_names) == 0:
|
|
1050
|
+
# if no npcs are specified, just have the npc take care of it itself instead of trying to force it to generate npc names for sequences all the time
|
|
1051
|
+
|
|
1052
|
+
npcs = [npc] * len(tool_names)
|
|
1117
1053
|
for npc_name in npc_names:
|
|
1118
1054
|
for npc_obj in npc.resolved_npcs:
|
|
1119
1055
|
if npc_name in npc_obj:
|
|
1120
1056
|
npcs.append(npc_obj[npc_name])
|
|
1121
1057
|
break
|
|
1058
|
+
if len(npcs) < len(tool_names):
|
|
1059
|
+
npcs.append(npc)
|
|
1122
1060
|
|
|
1123
1061
|
output = ""
|
|
1124
1062
|
results_tool_calls = []
|
|
@@ -1137,10 +1075,11 @@ ReAct choices then will enter reasoning flow
|
|
|
1137
1075
|
retrieved_docs=retrieved_docs,
|
|
1138
1076
|
stream=stream,
|
|
1139
1077
|
)
|
|
1078
|
+
# print(result)
|
|
1140
1079
|
results_tool_calls.append(result)
|
|
1141
1080
|
messages = result.get("messages", messages)
|
|
1142
1081
|
output += result.get("output", "")
|
|
1143
|
-
print(
|
|
1082
|
+
# print(results_tool_calls)
|
|
1144
1083
|
else:
|
|
1145
1084
|
for npc_obj in npcs:
|
|
1146
1085
|
result = npc.handle_agent_pass(
|
|
@@ -1154,7 +1093,7 @@ ReAct choices then will enter reasoning flow
|
|
|
1154
1093
|
|
|
1155
1094
|
messages = result.get("messages", messages)
|
|
1156
1095
|
results_tool_calls.append(result.get("response"))
|
|
1157
|
-
print(messages[-1])
|
|
1096
|
+
# print(messages[-1])
|
|
1158
1097
|
# import pdb
|
|
1159
1098
|
|
|
1160
1099
|
# pdb.set_trace()
|
|
@@ -1181,9 +1120,7 @@ def handle_tool_call(
|
|
|
1181
1120
|
attempt=0,
|
|
1182
1121
|
context=None,
|
|
1183
1122
|
) -> Union[str, Dict[str, Any]]:
|
|
1184
|
-
"""
|
|
1185
|
-
Function Description:
|
|
1186
|
-
This function handles a tool call.
|
|
1123
|
+
"""This function handles a tool call.
|
|
1187
1124
|
Args:
|
|
1188
1125
|
command (str): The command.
|
|
1189
1126
|
tool_name (str): The tool name.
|
|
@@ -1312,22 +1249,22 @@ def handle_tool_call(
|
|
|
1312
1249
|
# try:
|
|
1313
1250
|
print("Executing tool with input values:", input_values)
|
|
1314
1251
|
|
|
1315
|
-
try:
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1252
|
+
# try:
|
|
1253
|
+
tool_output = tool.execute(
|
|
1254
|
+
input_values,
|
|
1255
|
+
npc.all_tools_dict,
|
|
1256
|
+
jinja_env,
|
|
1257
|
+
command,
|
|
1258
|
+
model=model,
|
|
1259
|
+
provider=provider,
|
|
1260
|
+
npc=npc,
|
|
1261
|
+
stream=stream,
|
|
1262
|
+
messages=messages,
|
|
1263
|
+
)
|
|
1264
|
+
if not stream:
|
|
1265
|
+
if "Error" in tool_output:
|
|
1266
|
+
raise Exception(tool_output)
|
|
1267
|
+
# except Exception as e:
|
|
1331
1268
|
# diagnose_problem = get_llm_response(
|
|
1332
1269
|
## f"""a problem has occurred.
|
|
1333
1270
|
# Please provide a diagnosis of the problem and a suggested #fix.
|
|
@@ -1352,24 +1289,32 @@ def handle_tool_call(
|
|
|
1352
1289
|
# suggested_solution = diagnose_problem.get("response", {}).get(
|
|
1353
1290
|
# "suggested_solution"
|
|
1354
1291
|
# )
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1292
|
+
'''
|
|
1293
|
+
print(f"An error occurred while executing the tool: {e}")
|
|
1294
|
+
print(f"trying again, attempt {attempt+1}")
|
|
1295
|
+
if attempt < n_attempts:
|
|
1296
|
+
tool_output = handle_tool_call(
|
|
1297
|
+
command,
|
|
1298
|
+
tool_name,
|
|
1299
|
+
model=model,
|
|
1300
|
+
provider=provider,
|
|
1301
|
+
messages=messages,
|
|
1302
|
+
npc=npc,
|
|
1303
|
+
api_url=api_url,
|
|
1304
|
+
api_key=api_key,
|
|
1305
|
+
retrieved_docs=retrieved_docs,
|
|
1306
|
+
n_docs=n_docs,
|
|
1307
|
+
stream=stream,
|
|
1308
|
+
attempt=attempt + 1,
|
|
1309
|
+
n_attempts=n_attempts,
|
|
1310
|
+
context=f""" \n \n \n "tool failed: {e} \n \n \n here was the previous attempt: {input_values}""",
|
|
1311
|
+
)
|
|
1312
|
+
else:
|
|
1313
|
+
user_input = input(
|
|
1314
|
+
"the tool execution has failed after three tries, can you add more context to help or would you like to run again?"
|
|
1315
|
+
)
|
|
1316
|
+
return
|
|
1317
|
+
'''
|
|
1373
1318
|
if stream:
|
|
1374
1319
|
return tool_output
|
|
1375
1320
|
# print(f"Tool output: {tool_output}")
|
|
@@ -1388,9 +1333,7 @@ def execute_data_operations(
|
|
|
1388
1333
|
npc: Any = None,
|
|
1389
1334
|
db_path: str = "~/npcsh_history.db",
|
|
1390
1335
|
):
|
|
1391
|
-
"""
|
|
1392
|
-
Function Description:
|
|
1393
|
-
This function executes data operations.
|
|
1336
|
+
"""This function executes data operations.
|
|
1394
1337
|
Args:
|
|
1395
1338
|
query (str): The query to execute.
|
|
1396
1339
|
|
|
@@ -1777,7 +1720,7 @@ def get_data_response(
|
|
|
1777
1720
|
# failures.append(str(e))
|
|
1778
1721
|
|
|
1779
1722
|
|
|
1780
|
-
def
|
|
1723
|
+
def enter_chat_human_in_the_loop(
|
|
1781
1724
|
messages: List[Dict[str, str]],
|
|
1782
1725
|
reasoning_model: str = NPCSH_REASONING_MODEL,
|
|
1783
1726
|
reasoning_provider: str = NPCSH_REASONING_PROVIDER,
|
|
@@ -1826,7 +1769,35 @@ def enter_reasoning_human_in_the_loop(
|
|
|
1826
1769
|
in_think_block = False
|
|
1827
1770
|
|
|
1828
1771
|
for chunk in response_stream:
|
|
1829
|
-
#
|
|
1772
|
+
# Check for user interrupt
|
|
1773
|
+
if not input_queue.empty():
|
|
1774
|
+
user_interrupt = input_queue.get()
|
|
1775
|
+
yield "\n[Stream interrupted by user]\n"
|
|
1776
|
+
yield "Enter your additional input: "
|
|
1777
|
+
|
|
1778
|
+
# Get the full interrupt message
|
|
1779
|
+
full_interrupt = user_interrupt
|
|
1780
|
+
while not input_queue.empty():
|
|
1781
|
+
full_interrupt += "\n" + input_queue.get()
|
|
1782
|
+
|
|
1783
|
+
# Add the interruption to messages and restart stream
|
|
1784
|
+
messages.append(
|
|
1785
|
+
{"role": "user", "content": f"[INTERRUPT] {full_interrupt}"}
|
|
1786
|
+
)
|
|
1787
|
+
|
|
1788
|
+
yield f"\n[Continuing with added context...]\n"
|
|
1789
|
+
yield from enter_chat_human_in_the_loop(
|
|
1790
|
+
messages,
|
|
1791
|
+
reasoning_model=reasoning_model,
|
|
1792
|
+
reasoning_provider=reasoning_provider,
|
|
1793
|
+
chat_model=chat_model,
|
|
1794
|
+
chat_provider=chat_provider,
|
|
1795
|
+
npc=npc,
|
|
1796
|
+
answer_only=True,
|
|
1797
|
+
)
|
|
1798
|
+
return
|
|
1799
|
+
|
|
1800
|
+
# Extract content based on provider
|
|
1830
1801
|
if reasoning_provider == "ollama":
|
|
1831
1802
|
chunk_content = chunk.get("message", {}).get("content", "")
|
|
1832
1803
|
elif reasoning_provider == "openai" or reasoning_provider == "deepseek":
|
|
@@ -1841,80 +1812,191 @@ def enter_reasoning_human_in_the_loop(
|
|
|
1841
1812
|
else:
|
|
1842
1813
|
chunk_content = ""
|
|
1843
1814
|
else:
|
|
1844
|
-
# Default extraction
|
|
1845
1815
|
chunk_content = str(chunk)
|
|
1846
1816
|
|
|
1847
|
-
# Always yield the chunk whether in think block or not
|
|
1848
1817
|
response_chunks.append(chunk_content)
|
|
1849
|
-
|
|
1850
|
-
|
|
1818
|
+
combined_text = "".join(response_chunks)
|
|
1819
|
+
|
|
1820
|
+
# Check for LLM request block
|
|
1821
|
+
if (
|
|
1822
|
+
"<request_for_input>" in combined_text
|
|
1823
|
+
and "</request_for_input>" not in combined_text
|
|
1824
|
+
):
|
|
1825
|
+
in_think_block = True
|
|
1826
|
+
|
|
1827
|
+
if in_think_block:
|
|
1828
|
+
thoughts.append(chunk_content)
|
|
1851
1829
|
yield chunk
|
|
1852
|
-
else:
|
|
1853
|
-
if "<th" in "".join(response_chunks) and "/th" not in "".join(
|
|
1854
|
-
response_chunks
|
|
1855
|
-
):
|
|
1856
|
-
in_think_block = True
|
|
1857
|
-
|
|
1858
|
-
if in_think_block:
|
|
1859
|
-
thoughts.append(chunk_content)
|
|
1860
|
-
yield chunk # Show the thoughts as they come
|
|
1861
|
-
|
|
1862
|
-
if "</th" in "".join(response_chunks):
|
|
1863
|
-
thought_text = "".join(thoughts)
|
|
1864
|
-
# Analyze thoughts before stopping
|
|
1865
|
-
input_needed = analyze_thoughts_for_input(
|
|
1866
|
-
thought_text, model=chat_model, provider=chat_provider
|
|
1867
|
-
)
|
|
1868
1830
|
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1831
|
+
if "</request_for_input>" in combined_text:
|
|
1832
|
+
# Process the LLM's input request
|
|
1833
|
+
request_text = "".join(thoughts)
|
|
1834
|
+
yield "\nPlease provide the requested information: "
|
|
1835
|
+
|
|
1836
|
+
# Wait for user input (blocking here is OK since we explicitly asked)
|
|
1837
|
+
user_input = input()
|
|
1838
|
+
|
|
1839
|
+
# Add the interaction to messages and restart stream
|
|
1840
|
+
messages.append({"role": "assistant", "content": request_text})
|
|
1841
|
+
messages.append({"role": "user", "content": user_input})
|
|
1842
|
+
|
|
1843
|
+
yield "\n[Continuing with provided information...]\n"
|
|
1844
|
+
yield from enter_chat_human_in_the_loop(
|
|
1845
|
+
messages,
|
|
1846
|
+
reasoning_model=reasoning_model,
|
|
1847
|
+
reasoning_provider=reasoning_provider,
|
|
1848
|
+
chat_model=chat_model,
|
|
1849
|
+
chat_provider=chat_provider,
|
|
1850
|
+
npc=npc,
|
|
1851
|
+
answer_only=True,
|
|
1852
|
+
)
|
|
1853
|
+
return
|
|
1872
1854
|
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
"role": "assistant",
|
|
1876
|
-
"content": f"""its clear that extra input is required.
|
|
1877
|
-
could you please provide it? Here is the reason:
|
|
1855
|
+
if not in_think_block:
|
|
1856
|
+
yield chunk
|
|
1878
1857
|
|
|
1879
|
-
{input_needed['reason']},
|
|
1880
1858
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1859
|
+
def enter_reasoning_human_in_the_loop(
|
|
1860
|
+
messages: List[Dict[str, str]],
|
|
1861
|
+
reasoning_model: str = NPCSH_REASONING_MODEL,
|
|
1862
|
+
reasoning_provider: str = NPCSH_REASONING_PROVIDER,
|
|
1863
|
+
chat_model: str = NPCSH_CHAT_MODEL,
|
|
1864
|
+
chat_provider: str = NPCSH_CHAT_PROVIDER,
|
|
1865
|
+
npc: Any = None,
|
|
1866
|
+
answer_only: bool = False,
|
|
1867
|
+
context=None,
|
|
1868
|
+
) -> Generator[str, None, None]:
|
|
1869
|
+
"""
|
|
1870
|
+
Stream responses while checking for think tokens and handling human input when needed.
|
|
1884
1871
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
chat_model=chat_model,
|
|
1891
|
-
chat_provider=chat_provider,
|
|
1892
|
-
npc=npc,
|
|
1893
|
-
answer_only=True,
|
|
1894
|
-
)
|
|
1895
|
-
else:
|
|
1896
|
-
# If no input needed, just get the answer
|
|
1897
|
-
messages.append({"role": "assistant", "content": thought_text})
|
|
1898
|
-
messages.append(
|
|
1899
|
-
{"role": "user", "content": messages[-2]["content"]}
|
|
1900
|
-
)
|
|
1901
|
-
yield from enter_reasoning_human_in_the_loop( # Restart with new context
|
|
1902
|
-
messages,
|
|
1903
|
-
reasoning_model=reasoning_model,
|
|
1904
|
-
reasoning_provider=reasoning_provider,
|
|
1905
|
-
chat_model=chat_model,
|
|
1906
|
-
chat_provider=chat_provider,
|
|
1907
|
-
npc=npc,
|
|
1908
|
-
answer_only=True,
|
|
1909
|
-
)
|
|
1872
|
+
Args:
|
|
1873
|
+
messages: List of conversation messages
|
|
1874
|
+
model: LLM model to use
|
|
1875
|
+
provider: Model provider
|
|
1876
|
+
npc: NPC instance if applicable
|
|
1910
1877
|
|
|
1911
|
-
|
|
1878
|
+
Yields:
|
|
1879
|
+
Streamed response chunks
|
|
1880
|
+
"""
|
|
1881
|
+
# Get the initial stream
|
|
1882
|
+
if answer_only:
|
|
1883
|
+
messages[-1]["content"] = (
|
|
1884
|
+
messages[-1]["content"].replace(
|
|
1885
|
+
"Think first though and use <think> tags", ""
|
|
1886
|
+
)
|
|
1887
|
+
+ " Do not think just answer. "
|
|
1888
|
+
)
|
|
1889
|
+
else:
|
|
1890
|
+
messages[-1]["content"] = (
|
|
1891
|
+
messages[-1]["content"]
|
|
1892
|
+
+ " Think first though and use <think> tags. "
|
|
1893
|
+
)
|
|
1894
|
+
|
|
1895
|
+
response_stream = get_stream(
|
|
1896
|
+
messages,
|
|
1897
|
+
model=reasoning_model,
|
|
1898
|
+
provider=reasoning_provider,
|
|
1899
|
+
npc=npc,
|
|
1900
|
+
context=context,
|
|
1901
|
+
)
|
|
1902
|
+
|
|
1903
|
+
thoughts = []
|
|
1904
|
+
response_chunks = []
|
|
1905
|
+
in_think_block = False
|
|
1906
|
+
|
|
1907
|
+
for chunk in response_stream:
|
|
1908
|
+
# Check for user interrupt
|
|
1909
|
+
if not input_queue.empty():
|
|
1910
|
+
user_interrupt = input_queue.get()
|
|
1911
|
+
yield "\n[Stream interrupted by user]\n"
|
|
1912
|
+
yield "Enter your additional input: "
|
|
1913
|
+
|
|
1914
|
+
# Get the full interrupt message
|
|
1915
|
+
full_interrupt = user_interrupt
|
|
1916
|
+
while not input_queue.empty():
|
|
1917
|
+
full_interrupt += "\n" + input_queue.get()
|
|
1918
|
+
|
|
1919
|
+
# Add the interruption to messages and restart stream
|
|
1920
|
+
messages.append(
|
|
1921
|
+
{"role": "user", "content": f"[INTERRUPT] {full_interrupt}"}
|
|
1922
|
+
)
|
|
1923
|
+
|
|
1924
|
+
yield f"\n[Continuing with added context...]\n"
|
|
1925
|
+
yield from enter_reasoning_human_in_the_loop(
|
|
1926
|
+
messages,
|
|
1927
|
+
reasoning_model=reasoning_model,
|
|
1928
|
+
reasoning_provider=reasoning_provider,
|
|
1929
|
+
chat_model=chat_model,
|
|
1930
|
+
chat_provider=chat_provider,
|
|
1931
|
+
npc=npc,
|
|
1932
|
+
answer_only=True,
|
|
1933
|
+
)
|
|
1934
|
+
return
|
|
1935
|
+
|
|
1936
|
+
# Extract content based on provider
|
|
1937
|
+
if reasoning_provider == "ollama":
|
|
1938
|
+
chunk_content = chunk.get("message", {}).get("content", "")
|
|
1939
|
+
elif reasoning_provider == "openai" or reasoning_provider == "deepseek":
|
|
1940
|
+
chunk_content = "".join(
|
|
1941
|
+
choice.delta.content
|
|
1942
|
+
for choice in chunk.choices
|
|
1943
|
+
if choice.delta.content is not None
|
|
1944
|
+
)
|
|
1945
|
+
elif reasoning_provider == "anthropic":
|
|
1946
|
+
if chunk.type == "content_block_delta":
|
|
1947
|
+
chunk_content = chunk.delta.text
|
|
1948
|
+
else:
|
|
1949
|
+
chunk_content = ""
|
|
1950
|
+
else:
|
|
1951
|
+
chunk_content = str(chunk)
|
|
1952
|
+
|
|
1953
|
+
response_chunks.append(chunk_content)
|
|
1954
|
+
combined_text = "".join(response_chunks)
|
|
1955
|
+
|
|
1956
|
+
# Check for LLM request block
|
|
1957
|
+
if (
|
|
1958
|
+
"<request_for_input>" in combined_text
|
|
1959
|
+
and "</request_for_input>" not in combined_text
|
|
1960
|
+
):
|
|
1961
|
+
in_think_block = True
|
|
1962
|
+
|
|
1963
|
+
if in_think_block:
|
|
1964
|
+
thoughts.append(chunk_content)
|
|
1965
|
+
yield chunk
|
|
1966
|
+
|
|
1967
|
+
if "</request_for_input>" in combined_text:
|
|
1968
|
+
# Process the LLM's input request
|
|
1969
|
+
request_text = "".join(thoughts)
|
|
1970
|
+
yield "\nPlease provide the requested information: "
|
|
1971
|
+
|
|
1972
|
+
# Wait for user input (blocking here is OK since we explicitly asked)
|
|
1973
|
+
user_input = input()
|
|
1974
|
+
|
|
1975
|
+
# Add the interaction to messages and restart stream
|
|
1976
|
+
messages.append({"role": "assistant", "content": request_text})
|
|
1977
|
+
messages.append({"role": "user", "content": user_input})
|
|
1978
|
+
|
|
1979
|
+
yield "\n[Continuing with provided information...]\n"
|
|
1980
|
+
yield from enter_reasoning_human_in_the_loop(
|
|
1981
|
+
messages,
|
|
1982
|
+
reasoning_model=reasoning_model,
|
|
1983
|
+
reasoning_provider=reasoning_provider,
|
|
1984
|
+
chat_model=chat_model,
|
|
1985
|
+
chat_provider=chat_provider,
|
|
1986
|
+
npc=npc,
|
|
1987
|
+
answer_only=True,
|
|
1988
|
+
)
|
|
1989
|
+
return
|
|
1990
|
+
|
|
1991
|
+
if not in_think_block:
|
|
1992
|
+
yield chunk
|
|
1912
1993
|
|
|
1913
1994
|
|
|
1914
1995
|
def handle_request_input(
|
|
1915
1996
|
context: str,
|
|
1916
1997
|
model: str = NPCSH_CHAT_MODEL,
|
|
1917
1998
|
provider: str = NPCSH_CHAT_PROVIDER,
|
|
1999
|
+
whisper: bool = False,
|
|
1918
2000
|
):
|
|
1919
2001
|
"""
|
|
1920
2002
|
Analyze text and decide what to request from the user
|
|
@@ -1947,7 +2029,7 @@ def handle_request_input(
|
|
|
1947
2029
|
result = json.loads(result)
|
|
1948
2030
|
|
|
1949
2031
|
user_input = request_user_input(
|
|
1950
|
-
{"reason": result["request_reason"], "prompt": result["request_prompt"]}
|
|
2032
|
+
{"reason": result["request_reason"], "prompt": result["request_prompt"]},
|
|
1951
2033
|
)
|
|
1952
2034
|
return user_input
|
|
1953
2035
|
|
|
@@ -2025,3 +2107,89 @@ def request_user_input(input_request: Dict[str, str]) -> str:
|
|
|
2025
2107
|
"""
|
|
2026
2108
|
print(f"\nAdditional input needed: {input_request['reason']}")
|
|
2027
2109
|
return input(f"{input_request['prompt']}: ")
|
|
2110
|
+
|
|
2111
|
+
|
|
2112
|
+
def check_user_input() -> Optional[str]:
|
|
2113
|
+
"""
|
|
2114
|
+
Non-blocking check for user input.
|
|
2115
|
+
Returns None if no input is available, otherwise returns the input string.
|
|
2116
|
+
"""
|
|
2117
|
+
if select.select([sys.stdin], [], [], 0.0)[0]:
|
|
2118
|
+
return input()
|
|
2119
|
+
return None
|
|
2120
|
+
|
|
2121
|
+
|
|
2122
|
+
def input_listener(input_queue: Queue):
|
|
2123
|
+
"""
|
|
2124
|
+
Continuously listen for user input in a separate thread.
|
|
2125
|
+
"""
|
|
2126
|
+
while True:
|
|
2127
|
+
try:
|
|
2128
|
+
user_input = input()
|
|
2129
|
+
input_queue.put(user_input)
|
|
2130
|
+
except EOFError:
|
|
2131
|
+
break
|
|
2132
|
+
|
|
2133
|
+
|
|
2134
|
+
def stream_with_interrupts(
|
|
2135
|
+
messages: List[Dict[str, str]],
|
|
2136
|
+
model: str,
|
|
2137
|
+
provider: str,
|
|
2138
|
+
npc: Any = None,
|
|
2139
|
+
context=None,
|
|
2140
|
+
) -> Generator[str, None, None]:
|
|
2141
|
+
"""Stream responses with basic Ctrl+C handling and recursive conversation loop."""
|
|
2142
|
+
response_stream = get_stream(
|
|
2143
|
+
messages, model=model, provider=provider, npc=npc, context=context
|
|
2144
|
+
)
|
|
2145
|
+
|
|
2146
|
+
try:
|
|
2147
|
+
# Flag to track if streaming is complete
|
|
2148
|
+
streaming_complete = False
|
|
2149
|
+
|
|
2150
|
+
for chunk in response_stream:
|
|
2151
|
+
if provider == "ollama":
|
|
2152
|
+
chunk_content = chunk.get("message", {}).get("content", "")
|
|
2153
|
+
elif provider in ["openai", "deepseek"]:
|
|
2154
|
+
chunk_content = "".join(
|
|
2155
|
+
choice.delta.content
|
|
2156
|
+
for choice in chunk.choices
|
|
2157
|
+
if choice.delta.content is not None
|
|
2158
|
+
)
|
|
2159
|
+
elif provider == "anthropic":
|
|
2160
|
+
chunk_content = (
|
|
2161
|
+
chunk.delta.text if chunk.type == "content_block_delta" else ""
|
|
2162
|
+
)
|
|
2163
|
+
else:
|
|
2164
|
+
chunk_content = str(chunk)
|
|
2165
|
+
|
|
2166
|
+
yield chunk_content
|
|
2167
|
+
|
|
2168
|
+
# Optional: Mark streaming as complete when no more chunks
|
|
2169
|
+
if not chunk_content:
|
|
2170
|
+
streaming_complete = True
|
|
2171
|
+
|
|
2172
|
+
except KeyboardInterrupt:
|
|
2173
|
+
# Handle keyboard interrupt by getting user input
|
|
2174
|
+
user_input = input("\n> ")
|
|
2175
|
+
messages.append({"role": "user", "content": user_input})
|
|
2176
|
+
yield from stream_with_interrupts(
|
|
2177
|
+
messages, model=model, provider=provider, npc=npc, context=context
|
|
2178
|
+
)
|
|
2179
|
+
|
|
2180
|
+
finally:
|
|
2181
|
+
# Prompt for next input and continue conversation
|
|
2182
|
+
while True:
|
|
2183
|
+
user_input = input("\n> ")
|
|
2184
|
+
|
|
2185
|
+
# Option to exit the loop
|
|
2186
|
+
if user_input.lower() in ["exit", "quit", "q"]:
|
|
2187
|
+
break
|
|
2188
|
+
|
|
2189
|
+
# Add user input to messages
|
|
2190
|
+
messages.append({"role": "user", "content": user_input})
|
|
2191
|
+
|
|
2192
|
+
# Recursively continue the conversation
|
|
2193
|
+
yield from stream_with_interrupts(
|
|
2194
|
+
messages, model=model, provider=provider, npc=npc, context=context
|
|
2195
|
+
)
|