sunholo 0.91.3__py3-none-any.whl → 0.92.2__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.
- sunholo/genai/process_funcs_cls.py +187 -1
- {sunholo-0.91.3.dist-info → sunholo-0.92.2.dist-info}/METADATA +2 -2
- {sunholo-0.91.3.dist-info → sunholo-0.92.2.dist-info}/RECORD +7 -7
- {sunholo-0.91.3.dist-info → sunholo-0.92.2.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.91.3.dist-info → sunholo-0.92.2.dist-info}/WHEEL +0 -0
- {sunholo-0.91.3.dist-info → sunholo-0.92.2.dist-info}/entry_points.txt +0 -0
- {sunholo-0.91.3.dist-info → sunholo-0.92.2.dist-info}/top_level.txt +0 -0
|
@@ -60,6 +60,11 @@ class GenAIFunctionProcessor:
|
|
|
60
60
|
|
|
61
61
|
self.config = config
|
|
62
62
|
self.funcs = self.construct_tools()
|
|
63
|
+
|
|
64
|
+
# Add default 'decide_to_go_on' if not provided in construct_tools
|
|
65
|
+
if 'decide_to_go_on' not in self.funcs:
|
|
66
|
+
self.funcs['decide_to_go_on'] = self.decide_to_go_on
|
|
67
|
+
|
|
63
68
|
self.model_name = config.vacConfig("model") if config.vacConfig("llm") == "vertex" else "gemini-1.5-flash"
|
|
64
69
|
self.last_api_requests_and_responses = []
|
|
65
70
|
self._validate_functions()
|
|
@@ -317,4 +322,185 @@ class GenAIFunctionProcessor:
|
|
|
317
322
|
return model
|
|
318
323
|
except Exception as err:
|
|
319
324
|
log.error(f"Error initializing model: {str(err)}")
|
|
320
|
-
return None
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
def run_agent_loop(self, chat, content, callback, guardrail_max=10):
|
|
328
|
+
"""
|
|
329
|
+
Runs the agent loop, sending messages to the orchestrator, processing responses, and executing functions.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
chat: The chat object for interaction with the orchestrator.
|
|
333
|
+
content: The initial content to send to the agent.
|
|
334
|
+
callback: The callback object for handling intermediate responses.
|
|
335
|
+
guardrail_max (int): The maximum number of iterations for the loop.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
tuple: (big_text, usage_metadata) from the loop execution.
|
|
339
|
+
"""
|
|
340
|
+
guardrail = 0
|
|
341
|
+
big_text = ""
|
|
342
|
+
usage_metadata = {
|
|
343
|
+
"prompt_token_count": 0,
|
|
344
|
+
"candidates_token_count": 0,
|
|
345
|
+
"total_token_count": 0
|
|
346
|
+
}
|
|
347
|
+
functions_called =[]
|
|
348
|
+
function_results = []
|
|
349
|
+
|
|
350
|
+
while guardrail < guardrail_max:
|
|
351
|
+
|
|
352
|
+
callback.on_llm_new_token(
|
|
353
|
+
token=f"\n----Loop [{guardrail}] Start------\nFunctions:\n{self.funcs.keys()}\n"
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
content_parse = ""
|
|
357
|
+
for i, chunk in enumerate(content):
|
|
358
|
+
content_parse += f"\n - {i}) {chunk}"
|
|
359
|
+
content_parse += f"\n== End input content for loop [{guardrail}] =="
|
|
360
|
+
|
|
361
|
+
log.info(f"== Start input content for loop [{guardrail}]\n ## Content: {content_parse}")
|
|
362
|
+
this_text = "" # reset for this loop
|
|
363
|
+
response = []
|
|
364
|
+
|
|
365
|
+
try:
|
|
366
|
+
callback.on_llm_new_token(token="\n= Calling Agent\n")
|
|
367
|
+
response = chat.send_message(content, stream=True)
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
msg = f"Error sending {content} to model: {str(e)}"
|
|
371
|
+
log.info(msg)
|
|
372
|
+
callback.on_llm_new_token(token=msg)
|
|
373
|
+
break
|
|
374
|
+
|
|
375
|
+
loop_metadata = response.usage_metadata
|
|
376
|
+
if loop_metadata:
|
|
377
|
+
usage_metadata = {
|
|
378
|
+
"prompt_token_count": usage_metadata["prompt_token_count"] + (loop_metadata.prompt_token_count or 0),
|
|
379
|
+
"candidates_token_count": usage_metadata["candidates_token_count"] + (loop_metadata.candidates_token_count or 0),
|
|
380
|
+
"total_token_count": usage_metadata["total_token_count"] + (loop_metadata.total_token_count or 0),
|
|
381
|
+
}
|
|
382
|
+
callback.on_llm_new_token(token=(
|
|
383
|
+
"\n-- Agent response\n"
|
|
384
|
+
f"prompt_token_count: [{loop_metadata.prompt_token_count}]/[{usage_metadata['prompt_token_count']}] "
|
|
385
|
+
f"candidates_token_count: [{loop_metadata.candidates_token_count}]/[{usage_metadata['candidates_token_count']}] "
|
|
386
|
+
f"total_token_count: [{loop_metadata.total_token_count}]/[{usage_metadata['total_token_count']}] \n"
|
|
387
|
+
))
|
|
388
|
+
loop_metadata = None
|
|
389
|
+
|
|
390
|
+
for chunk in response:
|
|
391
|
+
if not chunk:
|
|
392
|
+
continue
|
|
393
|
+
|
|
394
|
+
log.debug(f"[{guardrail}] {chunk=}")
|
|
395
|
+
try:
|
|
396
|
+
if hasattr(chunk, 'text') and isinstance(chunk.text, str):
|
|
397
|
+
token = chunk.text
|
|
398
|
+
callback.on_llm_new_token(token=token)
|
|
399
|
+
big_text += token
|
|
400
|
+
this_text += token
|
|
401
|
+
else:
|
|
402
|
+
log.info("skipping chunk with no text")
|
|
403
|
+
|
|
404
|
+
except ValueError as err:
|
|
405
|
+
callback.on_llm_new_token(token=f"{str(err)} for {chunk=}")
|
|
406
|
+
|
|
407
|
+
executed_responses = self.process_funcs(response)
|
|
408
|
+
log.info(f"[{guardrail}] {executed_responses=}")
|
|
409
|
+
|
|
410
|
+
if executed_responses:
|
|
411
|
+
callback.on_llm_new_token(token="\nAgent function execution:\n")
|
|
412
|
+
for executed_response in executed_responses:
|
|
413
|
+
token = ""
|
|
414
|
+
fn = executed_response.function_response.name
|
|
415
|
+
fn_args = executed_response.function_response.response.get("args")
|
|
416
|
+
fn_result = executed_response.function_response.response["result"]
|
|
417
|
+
fn_log = f"{fn}({fn_args})"
|
|
418
|
+
log.info(fn_log)
|
|
419
|
+
functions_called.append(fn_log)
|
|
420
|
+
function_results.append(fn_result)
|
|
421
|
+
callback.on_llm_new_token(token=f"\n--- function calling: {fn_log} ...\n")
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
fn_result_json = json.loads(fn_result)
|
|
425
|
+
if not isinstance(fn_result_json, dict):
|
|
426
|
+
log.warning(f"{fn_result} was loaded but is not a dictionary")
|
|
427
|
+
fn_result_json = None
|
|
428
|
+
except json.JSONDecodeError:
|
|
429
|
+
log.warning(f"{fn_result} was not JSON decoded")
|
|
430
|
+
fn_result_json = None
|
|
431
|
+
except Exception as err:
|
|
432
|
+
log.warning(f"{fn_result} was not json decoded due to unknown exception: {str(err)}")
|
|
433
|
+
fn_result_json = None
|
|
434
|
+
|
|
435
|
+
if fn == "decide_to_go_on":
|
|
436
|
+
token = f"\n\n{'STOPPING' if not fn_result.get('go_on') else 'CONTINUE'}: {fn_result.get('chat_summary')}"
|
|
437
|
+
else:
|
|
438
|
+
token = f"--- function call result: {fn_log} ---"
|
|
439
|
+
if fn_result_json:
|
|
440
|
+
if fn_result_json.get('stdout'):
|
|
441
|
+
text = fn_result_json.get('stdout').encode('utf-8').decode('unicode_escape')
|
|
442
|
+
token += text
|
|
443
|
+
if fn_result_json.get('stderr'):
|
|
444
|
+
text = fn_result_json.get('stdout').encode('utf-8').decode('unicode_escape')
|
|
445
|
+
token += text
|
|
446
|
+
if not fn_result_json.get('stdout') and fn_result_json.get('stderr'):
|
|
447
|
+
token += f" - result:\n{fn_result}\n"
|
|
448
|
+
else:
|
|
449
|
+
token += f" - result:\n{fn_result}\n"
|
|
450
|
+
|
|
451
|
+
big_text += token
|
|
452
|
+
this_text += token
|
|
453
|
+
callback.on_llm_new_token(token=token)
|
|
454
|
+
else:
|
|
455
|
+
token = "\nNo function executions were found\n"
|
|
456
|
+
callback.on_llm_new_token(token=token)
|
|
457
|
+
big_text += token
|
|
458
|
+
this_text += token
|
|
459
|
+
|
|
460
|
+
if this_text:
|
|
461
|
+
content.append(f"Agent: {this_text}")
|
|
462
|
+
log.info(f"[{guardrail}] Updated content:\n{this_text}")
|
|
463
|
+
else:
|
|
464
|
+
log.warning(f"[{guardrail}] No content created this loop")
|
|
465
|
+
content.append(f"Agent: No response was found for loop [{guardrail}]")
|
|
466
|
+
|
|
467
|
+
callback.on_llm_new_token(
|
|
468
|
+
token=f"\n----Loop [{guardrail}] End------\n{usage_metadata}\n----------------------"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
go_on_check = self.check_function_result("decide_to_go_on", {"go_on": False})
|
|
472
|
+
if go_on_check:
|
|
473
|
+
log.info("Breaking agent loop")
|
|
474
|
+
break
|
|
475
|
+
|
|
476
|
+
guardrail += 1
|
|
477
|
+
if guardrail > guardrail_max:
|
|
478
|
+
log.warning("Guardrail kicked in, more than 10 loops")
|
|
479
|
+
break
|
|
480
|
+
|
|
481
|
+
usage_metadata["functions_called"] = functions_called
|
|
482
|
+
usage_metadata["function_results"] = function_results
|
|
483
|
+
|
|
484
|
+
return big_text, usage_metadata
|
|
485
|
+
|
|
486
|
+
# needs to be static to avoid bound methods in function call
|
|
487
|
+
@staticmethod
|
|
488
|
+
def decide_to_go_on(go_on: bool, chat_summary: str) -> dict:
|
|
489
|
+
"""
|
|
490
|
+
Examine the chat history. If the answer to the user's question has been answered, then go_on=False.
|
|
491
|
+
If the chat history indicates the answer is still being looked for, then go_on=True.
|
|
492
|
+
If there is no chat history, then go_on=True.
|
|
493
|
+
If there is an error that can't be corrected or solved by you, then go_on=False.
|
|
494
|
+
If there is an error but you think you can solve it by correcting your function arguments (such as an incorrect source), then go_on=True
|
|
495
|
+
If you want to ask the user a question or for some more feedback, then go_on=False.
|
|
496
|
+
Avoid asking the user if you suspect you can solve it yourself with the functions at your disposal - you get top marks if you solve it yourself without help.
|
|
497
|
+
When calling, please also add a chat summary of why you think the function should be called to end.
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
go_on: boolean Whether to continue searching for an answer
|
|
501
|
+
chat_summary: string A brief explanation on why go_on is TRUE or FALSE
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
boolean: True to carry on, False to continue
|
|
505
|
+
"""
|
|
506
|
+
return {"go_on": go_on, "chat_summary": chat_summary}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.92.2
|
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
|
5
5
|
Home-page: https://github.com/sunholo-data/sunholo-py
|
|
6
|
-
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.92.2.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -86,7 +86,7 @@ sunholo/gcs/download_url.py,sha256=q1NiJSvEhdNrmU5ZJ-sBCMC_J5CxzrajY8LRgdPOV_M,6
|
|
|
86
86
|
sunholo/gcs/metadata.py,sha256=oQLcXi4brsZ74aegWyC1JZmhlaEV270HS5_UWtAYYWE,898
|
|
87
87
|
sunholo/genai/__init__.py,sha256=dBl6IA3-Fx6-Vx81r0XqxHlUq6WeW1iDX188dpChu8s,115
|
|
88
88
|
sunholo/genai/init.py,sha256=yG8E67TduFCTQPELo83OJuWfjwTnGZsyACospahyEaY,687
|
|
89
|
-
sunholo/genai/process_funcs_cls.py,sha256=
|
|
89
|
+
sunholo/genai/process_funcs_cls.py,sha256=Yqxht7GHj0U52T2woEAOzMHFZY6SAHoVRHORGzO1IX0,21992
|
|
90
90
|
sunholo/genai/safety.py,sha256=mkFDO_BeEgiKjQd9o2I4UxB6XI7a9U-oOFjZ8LGRUC4,1238
|
|
91
91
|
sunholo/invoke/__init__.py,sha256=bELcqIjzKvaupcIN5OQmDgGx_8jARtH9T6PCe8UgcvE,99
|
|
92
92
|
sunholo/invoke/async_class.py,sha256=vmLT6DqE1YaPd4W88_QzPQvSzsjwLUAwt23vGZm-BEs,5767
|
|
@@ -144,9 +144,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
|
|
|
144
144
|
sunholo/vertex/memory_tools.py,sha256=q_phxgGX2TG2j2MXNULF2xGzQnQPENwjPN9nZ_A9Gh0,7526
|
|
145
145
|
sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
|
|
146
146
|
sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
|
|
147
|
-
sunholo-0.
|
|
148
|
-
sunholo-0.
|
|
149
|
-
sunholo-0.
|
|
150
|
-
sunholo-0.
|
|
151
|
-
sunholo-0.
|
|
152
|
-
sunholo-0.
|
|
147
|
+
sunholo-0.92.2.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
|
148
|
+
sunholo-0.92.2.dist-info/METADATA,sha256=dPdRvFNLmwtj7keMVXSHHBB3zQaIm8a8VjD4KW-b8Zk,7806
|
|
149
|
+
sunholo-0.92.2.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
150
|
+
sunholo-0.92.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
|
151
|
+
sunholo-0.92.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
|
152
|
+
sunholo-0.92.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|