sunholo 0.71.29__py3-none-any.whl → 0.73.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/agents/dispatch_to_qa.py +10 -7
- sunholo/agents/langserve.py +1 -1
- sunholo/agents/route.py +24 -9
- sunholo/cli/chat_vac.py +20 -60
- sunholo/cli/cli.py +3 -3
- sunholo/cli/embedder.py +2 -1
- sunholo/components/retriever.py +2 -2
- sunholo/gcs/add_file.py +19 -11
- sunholo/invoke/__init__.py +1 -0
- sunholo/invoke/invoke_vac_utils.py +151 -0
- sunholo/langfuse/prompts.py +9 -3
- sunholo/llamaindex/import_files.py +8 -7
- sunholo/streaming/langserve.py +4 -1
- sunholo/utils/__init__.py +2 -1
- sunholo/utils/config.py +1 -1
- sunholo/utils/config_class.py +219 -0
- sunholo/vertex/extensions_class.py +179 -64
- sunholo/vertex/memory_tools.py +1 -1
- {sunholo-0.71.29.dist-info → sunholo-0.73.2.dist-info}/METADATA +4 -3
- {sunholo-0.71.29.dist-info → sunholo-0.73.2.dist-info}/RECORD +24 -22
- sunholo/vertex/extensions.py +0 -326
- {sunholo-0.71.29.dist-info → sunholo-0.73.2.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.71.29.dist-info → sunholo-0.73.2.dist-info}/WHEEL +0 -0
- {sunholo-0.71.29.dist-info → sunholo-0.73.2.dist-info}/entry_points.txt +0 -0
- {sunholo-0.71.29.dist-info → sunholo-0.73.2.dist-info}/top_level.txt +0 -0
sunholo/agents/dispatch_to_qa.py
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from ..logging import log
|
|
15
|
-
from ..utils import
|
|
15
|
+
from ..utils import ConfigManager
|
|
16
16
|
from ..auth import get_header
|
|
17
17
|
import requests
|
|
18
18
|
import aiohttp
|
|
@@ -46,26 +46,29 @@ def prep_request_payload(user_input, chat_history, vector_name, stream, **kwargs
|
|
|
46
46
|
```
|
|
47
47
|
"""
|
|
48
48
|
|
|
49
|
+
config = ConfigManager(vector_name)
|
|
50
|
+
|
|
49
51
|
# Add chat_history/vector_name to kwargs so langserve can use them too
|
|
50
52
|
kwargs['chat_history'] = chat_history
|
|
51
53
|
|
|
52
|
-
agent =
|
|
53
|
-
agent_type =
|
|
54
|
+
agent = config.vacConfig("agent")
|
|
55
|
+
agent_type = config.vacConfig("agent_type")
|
|
54
56
|
|
|
55
57
|
override_endpoint = kwargs.get("override_endpoint")
|
|
56
58
|
if override_endpoint:
|
|
57
59
|
log.info(f"Overriding endpoint with {override_endpoint}")
|
|
58
60
|
|
|
59
61
|
# {'stream': '', 'invoke': ''}
|
|
60
|
-
|
|
62
|
+
post_endpoints = route_endpoint(override_endpoint=override_endpoint, config=config)
|
|
61
63
|
|
|
62
64
|
if stream:
|
|
63
|
-
qna_endpoint =
|
|
65
|
+
qna_endpoint = post_endpoints["stream"]
|
|
64
66
|
else:
|
|
65
|
-
qna_endpoint =
|
|
67
|
+
qna_endpoint = post_endpoints["invoke"]
|
|
66
68
|
|
|
67
69
|
if agent == "langserve" or agent_type == "langserve":
|
|
68
|
-
|
|
70
|
+
get_endpoints = route_endpoint(override_endpoint=override_endpoint, method = 'get', config=config)
|
|
71
|
+
qna_data = prepare_request_data(user_input, get_endpoints["input_schema"], vector_name, **kwargs)
|
|
69
72
|
else:
|
|
70
73
|
# Base qna_data dictionary
|
|
71
74
|
qna_data = {
|
sunholo/agents/langserve.py
CHANGED
|
@@ -97,6 +97,6 @@ def prepare_request_data(user_input, endpoint, vector_name, **kwargs):
|
|
|
97
97
|
|
|
98
98
|
return request_data
|
|
99
99
|
else:
|
|
100
|
-
log.error("Invalid or no input schema available
|
|
100
|
+
log.error(f"Invalid or no input schema available for {endpoint=} {input_schema=}")
|
|
101
101
|
return None
|
|
102
102
|
|
sunholo/agents/route.py
CHANGED
|
@@ -12,18 +12,24 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from ..logging import log
|
|
15
|
-
from ..utils import
|
|
15
|
+
from ..utils import load_config, ConfigManager
|
|
16
16
|
|
|
17
|
-
def route_vac(vector_name: str) -> str :
|
|
17
|
+
def route_vac(vector_name: str=None, config=None) -> str :
|
|
18
18
|
"""
|
|
19
19
|
Considers what VAC this vector_name belongs to
|
|
20
20
|
"""
|
|
21
|
-
|
|
21
|
+
if not vector_name and not config:
|
|
22
|
+
raise ValueError("Must provide config or vector_name argument")
|
|
23
|
+
|
|
24
|
+
if not config:
|
|
25
|
+
config = ConfigManager(vector_name)
|
|
26
|
+
|
|
27
|
+
agent_url = config.vacConfig('agent_url')
|
|
22
28
|
if agent_url:
|
|
23
29
|
log.info('agent_url found in llm_config.yaml')
|
|
24
30
|
return agent_url
|
|
25
31
|
|
|
26
|
-
agent =
|
|
32
|
+
agent = config.vacConfig('agent')
|
|
27
33
|
log.info(f'agent_type: {agent}')
|
|
28
34
|
|
|
29
35
|
agent_route, _ = load_config('config/cloud_run_urls.json')
|
|
@@ -37,15 +43,24 @@ def route_vac(vector_name: str) -> str :
|
|
|
37
43
|
log.info(f'agent_url: {agent_url}')
|
|
38
44
|
return agent_url
|
|
39
45
|
|
|
40
|
-
def route_endpoint(vector_name, method = 'post', override_endpoint=None):
|
|
46
|
+
def route_endpoint(vector_name=None, method = 'post', override_endpoint=None, config=None):
|
|
47
|
+
|
|
48
|
+
if vector_name is None and config is None:
|
|
49
|
+
raise ValueError('vector_name and config can not both be None')
|
|
50
|
+
|
|
51
|
+
if config:
|
|
52
|
+
vector_name = config.vector_name
|
|
53
|
+
|
|
54
|
+
if not config:
|
|
55
|
+
config = ConfigManager(vector_name)
|
|
41
56
|
|
|
42
|
-
agent_type =
|
|
57
|
+
agent_type = config.vacConfig('agent_type')
|
|
43
58
|
if not agent_type:
|
|
44
|
-
agent_type =
|
|
59
|
+
agent_type = config.vacConfig('agent')
|
|
45
60
|
|
|
46
|
-
stem = route_vac(
|
|
61
|
+
stem = route_vac(config=config) if not override_endpoint else override_endpoint
|
|
47
62
|
|
|
48
|
-
agents_config =
|
|
63
|
+
agents_config = config.agentConfig(agent_type)
|
|
49
64
|
|
|
50
65
|
log.info(f"agents_config: {agents_config}")
|
|
51
66
|
if method not in agents_config:
|
sunholo/cli/chat_vac.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
from ..agents import send_to_qa, handle_special_commands
|
|
2
2
|
from ..streaming import generate_proxy_stream, can_agent_stream
|
|
3
3
|
from ..utils.user_ids import generate_user_id
|
|
4
|
-
from ..utils
|
|
4
|
+
from ..utils import ConfigManager
|
|
5
5
|
from ..utils.api_key import has_multivac_api_key
|
|
6
6
|
from ..logging import log
|
|
7
7
|
from ..qna.parsers import parse_output
|
|
8
8
|
from ..gcs.add_file import add_file_to_gcs
|
|
9
9
|
from .run_proxy import clean_proxy_list, start_proxy, stop_proxy
|
|
10
|
+
from ..invoke import invoke_vac
|
|
10
11
|
|
|
11
12
|
import uuid
|
|
12
13
|
import os
|
|
13
14
|
import sys
|
|
14
15
|
import subprocess
|
|
15
16
|
import json
|
|
16
|
-
import requests
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
19
|
from rich import print
|
|
@@ -30,7 +30,7 @@ def get_service_url(vac_name, project, region, no_config=False):
|
|
|
30
30
|
if no_config:
|
|
31
31
|
agent_name = vac_name
|
|
32
32
|
else:
|
|
33
|
-
agent_name =
|
|
33
|
+
agent_name = ConfigManager(vac_name).vacConfig("agent")
|
|
34
34
|
|
|
35
35
|
proxies = clean_proxy_list()
|
|
36
36
|
if agent_name in proxies:
|
|
@@ -50,7 +50,7 @@ def handle_file_upload(file, vector_name):
|
|
|
50
50
|
if not Path(file).is_file():
|
|
51
51
|
return None
|
|
52
52
|
|
|
53
|
-
agent_name =
|
|
53
|
+
agent_name = ConfigManager(vector_name).vacConfig("agent")
|
|
54
54
|
# vertex can't handle directories
|
|
55
55
|
bucket_filepath = f"{vector_name}/uploads/{os.path.basename(file)}" if agent_name != "vertex-genai" else os.path.basename(file)
|
|
56
56
|
|
|
@@ -65,7 +65,8 @@ def stream_chat_session(service_url, service_name, stream=True):
|
|
|
65
65
|
|
|
66
66
|
user_id = generate_user_id()
|
|
67
67
|
chat_history = []
|
|
68
|
-
agent_name =
|
|
68
|
+
agent_name = ConfigManager(service_name).vacConfig("agent")
|
|
69
|
+
file_reply = None
|
|
69
70
|
while True:
|
|
70
71
|
session_id = str(uuid.uuid4())
|
|
71
72
|
user_input = Prompt.ask("[bold cyan]You[/bold cyan]")
|
|
@@ -274,15 +275,19 @@ def resolve_service_url(args, no_config=False):
|
|
|
274
275
|
|
|
275
276
|
return args.url_override
|
|
276
277
|
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
config = ConfigManager(args.vac_name)
|
|
279
|
+
global_config = ConfigManager("global")
|
|
280
|
+
|
|
281
|
+
agent_name = config.vacConfig("agent")
|
|
282
|
+
agent_url = config.vacConfig("agent_url")
|
|
283
|
+
|
|
279
284
|
if agent_url:
|
|
280
285
|
console.print("Found agent_url within vacConfig: {agent_url}")
|
|
281
286
|
|
|
282
287
|
# via public cloud endpoints - assumes no gcloud auth
|
|
283
288
|
if has_multivac_api_key():
|
|
284
289
|
log.debug("Found MULTIVAC_API_KEY")
|
|
285
|
-
gcp_config =
|
|
290
|
+
gcp_config = global_config.vacConfig("gcp_config")
|
|
286
291
|
endpoints_base_url = gcp_config.get("endpoints_base_url")
|
|
287
292
|
if not endpoints_base_url:
|
|
288
293
|
console.print("[bold red]MULTIVAC_API_KEY env var is set but no config.gcp_config.endpoints_base_url can be found[/bold red]")
|
|
@@ -310,6 +315,8 @@ def resolve_service_url(args, no_config=False):
|
|
|
310
315
|
|
|
311
316
|
def vac_command(args):
|
|
312
317
|
|
|
318
|
+
config = ConfigManager(args.vac_name)
|
|
319
|
+
|
|
313
320
|
if args.action == 'list':
|
|
314
321
|
|
|
315
322
|
list_cloud_run_services(args.project, args.region)
|
|
@@ -324,7 +331,7 @@ def vac_command(args):
|
|
|
324
331
|
|
|
325
332
|
elif args.action == 'chat':
|
|
326
333
|
service_url = resolve_service_url(args)
|
|
327
|
-
agent_name =
|
|
334
|
+
agent_name = config.vacConfig("agent")
|
|
328
335
|
|
|
329
336
|
streamer = can_agent_stream(agent_name)
|
|
330
337
|
log.debug(f"streamer: {streamer}")
|
|
@@ -334,9 +341,10 @@ def vac_command(args):
|
|
|
334
341
|
if args.headless:
|
|
335
342
|
headless_mode(service_url, args.vac_name, args.user_input, args.chat_history, stream=streamer)
|
|
336
343
|
else:
|
|
337
|
-
display_name =
|
|
338
|
-
description =
|
|
339
|
-
endpoints_config =
|
|
344
|
+
display_name = config.vacConfig("display_name")
|
|
345
|
+
description = config.vacConfig("description")
|
|
346
|
+
endpoints_config = config.agentConfig(agent_name)
|
|
347
|
+
|
|
340
348
|
post_endpoints = endpoints_config['post']
|
|
341
349
|
|
|
342
350
|
display_endpoints = ' '.join(f"{key}: {value}" for key, value in post_endpoints.items())
|
|
@@ -362,54 +370,6 @@ def vac_command(args):
|
|
|
362
370
|
|
|
363
371
|
invoke_vac(service_url, args.data, is_file=args.is_file)
|
|
364
372
|
|
|
365
|
-
def invoke_vac(service_url, data, vector_name=None, metadata=None, is_file=False):
|
|
366
|
-
try:
|
|
367
|
-
if is_file:
|
|
368
|
-
console.print("Uploading file...")
|
|
369
|
-
# Handle file upload
|
|
370
|
-
if not isinstance(data, Path) or not data.is_file():
|
|
371
|
-
raise ValueError("For file uploads, 'data' must be a Path object pointing to a valid file.")
|
|
372
|
-
|
|
373
|
-
files = {
|
|
374
|
-
'file': (data.name, open(data, 'rb')),
|
|
375
|
-
}
|
|
376
|
-
form_data = {
|
|
377
|
-
'vector_name': vector_name,
|
|
378
|
-
'metadata': json.dumps(metadata) if metadata else '',
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
response = requests.post(service_url, files=files, data=form_data)
|
|
382
|
-
else:
|
|
383
|
-
console.print("Uploading JSON...")
|
|
384
|
-
try:
|
|
385
|
-
if isinstance(data, dict):
|
|
386
|
-
json_data = data
|
|
387
|
-
else:
|
|
388
|
-
json_data = json.loads(data)
|
|
389
|
-
except json.JSONDecodeError as err:
|
|
390
|
-
console.print(f"[bold red]ERROR: invalid JSON: {str(err)} [/bold red]")
|
|
391
|
-
sys.exit(1)
|
|
392
|
-
except Exception as err:
|
|
393
|
-
console.print(f"[bold red]ERROR: could not parse JSON: {str(err)} [/bold red]")
|
|
394
|
-
sys.exit(1)
|
|
395
|
-
|
|
396
|
-
log.debug(f"Sending data: {data} or json_data: {json.dumps(json_data)}")
|
|
397
|
-
# Handle JSON data
|
|
398
|
-
headers = {"Content-Type": "application/json"}
|
|
399
|
-
response = requests.post(service_url, headers=headers, data=json.dumps(json_data))
|
|
400
|
-
|
|
401
|
-
response.raise_for_status()
|
|
402
|
-
|
|
403
|
-
the_data = response.json()
|
|
404
|
-
console.print(the_data)
|
|
405
|
-
|
|
406
|
-
return the_data
|
|
407
|
-
|
|
408
|
-
except requests.exceptions.RequestException as e:
|
|
409
|
-
console.print(f"[bold red]ERROR: Failed to invoke VAC: {e}[/bold red]")
|
|
410
|
-
except Exception as e:
|
|
411
|
-
console.print(f"[bold red]ERROR: An unexpected error occurred: {e}[/bold red]")
|
|
412
|
-
|
|
413
373
|
|
|
414
374
|
def list_cloud_run_services(project, region):
|
|
415
375
|
"""
|
sunholo/cli/cli.py
CHANGED
|
@@ -10,7 +10,7 @@ from .chat_vac import setup_vac_subparser
|
|
|
10
10
|
from .embedder import setup_embedder_subparser
|
|
11
11
|
from .swagger import setup_swagger_subparser
|
|
12
12
|
|
|
13
|
-
from ..utils
|
|
13
|
+
from ..utils import ConfigManager
|
|
14
14
|
|
|
15
15
|
from ..logging import log
|
|
16
16
|
|
|
@@ -20,9 +20,9 @@ from rich.panel import Panel
|
|
|
20
20
|
|
|
21
21
|
def load_default_gcp_config():
|
|
22
22
|
try:
|
|
23
|
-
gcp_config =
|
|
23
|
+
gcp_config = ConfigManager("global").vacConfig("gcp_config")
|
|
24
24
|
except FileNotFoundError as e:
|
|
25
|
-
console.print(f"{e} - move config/ folder to working directory or set the
|
|
25
|
+
console.print(f"{e} - move config/ folder to working directory or set the VAC_CONFIG_FOLDER environment variable to its location")
|
|
26
26
|
sys.exit(1)
|
|
27
27
|
|
|
28
28
|
if gcp_config:
|
sunholo/cli/embedder.py
CHANGED
|
@@ -8,7 +8,8 @@ from pathlib import Path
|
|
|
8
8
|
from .sun_rich import console
|
|
9
9
|
from rich.progress import Progress
|
|
10
10
|
|
|
11
|
-
from
|
|
11
|
+
from ..invoke import invoke_vac
|
|
12
|
+
from .chat_vac import resolve_service_url
|
|
12
13
|
from .run_proxy import stop_proxy
|
|
13
14
|
|
|
14
15
|
def create_metadata(vac, metadata):
|
sunholo/components/retriever.py
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from ..logging import log
|
|
15
15
|
from .vectorstore import pick_vectorstore
|
|
16
|
-
from ..utils import load_config_key
|
|
16
|
+
from ..utils import load_config_key, ConfigManager
|
|
17
17
|
from .llm import get_embeddings
|
|
18
18
|
from ..utils.gcp_project import get_gcp_project
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ from langchain.retrievers import ContextualCompressionRetriever
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def load_memories(vector_name):
|
|
30
|
-
memories =
|
|
30
|
+
memories = ConfigManager(vector_name).vacConfig("memory")
|
|
31
31
|
log.info(f"Found memory settings for {vector_name}: {memories}")
|
|
32
32
|
if not memories or len(memories) == 0:
|
|
33
33
|
log.info(f"No memory settings found for {vector_name}")
|
sunholo/gcs/add_file.py
CHANGED
|
@@ -22,7 +22,7 @@ except ImportError:
|
|
|
22
22
|
storage = None
|
|
23
23
|
|
|
24
24
|
from ..logging import log
|
|
25
|
-
from ..utils
|
|
25
|
+
from ..utils import load_config_key, ConfigManager
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def handle_base64_image(base64_data: str, vector_name: str, extension: str):
|
|
@@ -37,7 +37,8 @@ def handle_base64_image(base64_data: str, vector_name: str, extension: str):
|
|
|
37
37
|
Returns:
|
|
38
38
|
Tuple[str, str]: The URI of the uploaded image and the MIME type.
|
|
39
39
|
"""
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
model = ConfigManager(vector_name).vacConfig("llm")
|
|
41
42
|
if model.startswith("openai"): # pass it to gpt directly
|
|
42
43
|
return base64_data, base64_data.split(",", 1)
|
|
43
44
|
|
|
@@ -69,16 +70,19 @@ def handle_base64_image(base64_data: str, vector_name: str, extension: str):
|
|
|
69
70
|
|
|
70
71
|
def resolve_bucket(vector_name):
|
|
71
72
|
if os.getenv('EXTENSIONS_BUCKET'):
|
|
73
|
+
log.warning('Resolving to EXTENSIONS_BUCKET environment variable')
|
|
72
74
|
return os.getenv('EXTENSIONS_BUCKET')
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
if vector_name:
|
|
77
|
+
bucket_config = ConfigManager(vector_name).vacConfig("upload")
|
|
78
|
+
|
|
79
|
+
if bucket_config:
|
|
80
|
+
if bucket_config.get("buckets"):
|
|
81
|
+
bucket_name = bucket_config.get("buckets").get("all")
|
|
82
|
+
|
|
83
|
+
bucket_name = bucket_name or os.getenv('GCS_BUCKET')
|
|
84
|
+
if not bucket_name:
|
|
85
|
+
raise ValueError("No bucket found to upload to: GCS_BUCKET returned None")
|
|
82
86
|
|
|
83
87
|
if bucket_name.startswith("gs://"):
|
|
84
88
|
bucket_name = bucket_name.removeprefix("gs://")
|
|
@@ -86,7 +90,7 @@ def resolve_bucket(vector_name):
|
|
|
86
90
|
return bucket_name
|
|
87
91
|
|
|
88
92
|
def add_file_to_gcs(filename: str,
|
|
89
|
-
vector_name:str,
|
|
93
|
+
vector_name:str=None,
|
|
90
94
|
bucket_name: str=None,
|
|
91
95
|
metadata:dict=None,
|
|
92
96
|
bucket_filepath:str=None):
|
|
@@ -114,7 +118,11 @@ def add_file_to_gcs(filename: str,
|
|
|
114
118
|
if os.getenv('EXTENSIONS_BUCKET'):
|
|
115
119
|
bucket_filepath = os.path.basename(filename)
|
|
116
120
|
|
|
121
|
+
if vector_name is None:
|
|
122
|
+
vector_name = "global"
|
|
123
|
+
|
|
117
124
|
if not bucket_filepath:
|
|
125
|
+
|
|
118
126
|
bucket_filepath = f"{vector_name}/{year}/{month}/{day}/{hour}/{os.path.basename(filename)}"
|
|
119
127
|
bucket_filepath_prev = f"{vector_name}/{year}/{month}/{day}/{hour_prev}/{os.path.basename(filename)}"
|
|
120
128
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .invoke_vac_utils import invoke_vac, invoke_vac_qa
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..logging import log
|
|
7
|
+
from ..agents import send_to_qa
|
|
8
|
+
from ..qna.parsers import parse_output
|
|
9
|
+
from ..streaming import generate_proxy_stream
|
|
10
|
+
|
|
11
|
+
def invoke_vac_qa(vac_input: dict, vac_name: str, chat_history=[], stream=False):
|
|
12
|
+
"""
|
|
13
|
+
This lets VACs call other VAC Q&A endpoints within their code
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
if 'user_input' not in vac_input:
|
|
17
|
+
raise ValueError('vac_input must contain at least "user_input" key - got {vac_input}')
|
|
18
|
+
|
|
19
|
+
user_id = vac_input.get('user_id')
|
|
20
|
+
session_id = vac_input.get('session_id')
|
|
21
|
+
image_uri = vac_input.get('image_url') or vac_input.get('image_uri')
|
|
22
|
+
|
|
23
|
+
if not stream:
|
|
24
|
+
log.info(f'Batch invoke_vac_qa with {vac_input=}')
|
|
25
|
+
vac_response = send_to_qa(
|
|
26
|
+
vac_input["user_input"],
|
|
27
|
+
vector_name=vac_name,
|
|
28
|
+
chat_history=chat_history,
|
|
29
|
+
message_author=user_id,
|
|
30
|
+
#TODO: populate these
|
|
31
|
+
image_url=image_uri,
|
|
32
|
+
source_filters=None,
|
|
33
|
+
search_kwargs=None,
|
|
34
|
+
private_docs=None,
|
|
35
|
+
whole_document=False,
|
|
36
|
+
source_filters_and_or=False,
|
|
37
|
+
# system kwargs
|
|
38
|
+
configurable={
|
|
39
|
+
"vector_name": vac_name,
|
|
40
|
+
},
|
|
41
|
+
user_id=user_id,
|
|
42
|
+
session_id=session_id,
|
|
43
|
+
message_source="sunholo.invoke_vac_qa.invoke")
|
|
44
|
+
|
|
45
|
+
# ensures {'answer': answer}
|
|
46
|
+
answer = parse_output(vac_response)
|
|
47
|
+
chat_history.append({"name": "Human", "content": vac_input})
|
|
48
|
+
chat_history.append({"name": "AI", "content": answer})
|
|
49
|
+
answer["chat_history"] = chat_history
|
|
50
|
+
|
|
51
|
+
return answer
|
|
52
|
+
|
|
53
|
+
log.info(f"Streaming invoke_vac_qa with {vac_input=}")
|
|
54
|
+
def stream_response():
|
|
55
|
+
generate = generate_proxy_stream(
|
|
56
|
+
send_to_qa,
|
|
57
|
+
vac_input["user_input"],
|
|
58
|
+
vector_name=vac_name,
|
|
59
|
+
chat_history=chat_history,
|
|
60
|
+
generate_f_output=lambda x: x, # Replace with actual processing function
|
|
61
|
+
stream_wait_time=0.5,
|
|
62
|
+
stream_timeout=120,
|
|
63
|
+
message_author=user_id,
|
|
64
|
+
#TODO: populate these
|
|
65
|
+
image_url=image_uri,
|
|
66
|
+
source_filters=None,
|
|
67
|
+
search_kwargs=None,
|
|
68
|
+
private_docs=None,
|
|
69
|
+
whole_document=False,
|
|
70
|
+
source_filters_and_or=False,
|
|
71
|
+
# system kwargs
|
|
72
|
+
configurable={
|
|
73
|
+
"vector_name": vac_name,
|
|
74
|
+
},
|
|
75
|
+
user_id=user_id,
|
|
76
|
+
session_id=session_id,
|
|
77
|
+
message_source="sunholo.invoke_vac_qa.stream"
|
|
78
|
+
)
|
|
79
|
+
for part in generate():
|
|
80
|
+
yield part
|
|
81
|
+
|
|
82
|
+
answer = ""
|
|
83
|
+
|
|
84
|
+
for token in stream_response():
|
|
85
|
+
if isinstance(token, bytes):
|
|
86
|
+
token = token.decode('utf-8')
|
|
87
|
+
yield token
|
|
88
|
+
if isinstance(token, dict):
|
|
89
|
+
# ?
|
|
90
|
+
pass
|
|
91
|
+
elif isinstance(token, str):
|
|
92
|
+
answer += token
|
|
93
|
+
|
|
94
|
+
if answer:
|
|
95
|
+
chat_history.append({"name": "Human", "content": vac_input})
|
|
96
|
+
chat_history.append({"name": "AI", "content": answer})
|
|
97
|
+
|
|
98
|
+
return chat_history
|
|
99
|
+
|
|
100
|
+
def invoke_vac(service_url, data, vector_name=None, metadata=None, is_file=False):
|
|
101
|
+
"""
|
|
102
|
+
This lets a VAC be invoked by directly calling its URL, used for file uploads
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
if is_file:
|
|
106
|
+
log.info("Uploading file...")
|
|
107
|
+
# Handle file upload
|
|
108
|
+
if not isinstance(data, Path) or not data.is_file():
|
|
109
|
+
raise ValueError("For file uploads, 'data' must be a Path object pointing to a valid file.")
|
|
110
|
+
|
|
111
|
+
files = {
|
|
112
|
+
'file': (data.name, open(data, 'rb')),
|
|
113
|
+
}
|
|
114
|
+
form_data = {
|
|
115
|
+
'vector_name': vector_name,
|
|
116
|
+
'metadata': json.dumps(metadata) if metadata else '',
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
response = requests.post(service_url, files=files, data=form_data)
|
|
120
|
+
else:
|
|
121
|
+
log.info("Uploading JSON...")
|
|
122
|
+
try:
|
|
123
|
+
if isinstance(data, dict):
|
|
124
|
+
json_data = data
|
|
125
|
+
else:
|
|
126
|
+
json_data = json.loads(data)
|
|
127
|
+
except json.JSONDecodeError as err:
|
|
128
|
+
log.error(f"[bold red]ERROR: invalid JSON: {str(err)} [/bold red]")
|
|
129
|
+
raise err
|
|
130
|
+
except Exception as err:
|
|
131
|
+
log.error(f"[bold red]ERROR: could not parse JSON: {str(err)} [/bold red]")
|
|
132
|
+
raise err
|
|
133
|
+
|
|
134
|
+
log.debug(f"Sending data: {data} or json_data: {json.dumps(json_data)}")
|
|
135
|
+
# Handle JSON data
|
|
136
|
+
headers = {"Content-Type": "application/json"}
|
|
137
|
+
response = requests.post(service_url, headers=headers, data=json.dumps(json_data))
|
|
138
|
+
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
|
|
141
|
+
the_data = response.json()
|
|
142
|
+
log.info(the_data)
|
|
143
|
+
|
|
144
|
+
return the_data
|
|
145
|
+
|
|
146
|
+
except requests.exceptions.RequestException as e:
|
|
147
|
+
log.error(f"[bold red]ERROR: Failed to invoke VAC: {e}[/bold red]")
|
|
148
|
+
raise e
|
|
149
|
+
except Exception as e:
|
|
150
|
+
log.error(f"[bold red]ERROR: An unexpected error occurred: {e}[/bold red]")
|
|
151
|
+
raise e
|
sunholo/langfuse/prompts.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from ..logging import log
|
|
2
|
-
from ..utils import
|
|
2
|
+
from ..utils import ConfigManager
|
|
3
3
|
|
|
4
4
|
# Load the YAML file
|
|
5
|
-
def load_prompt_from_yaml(key, prefix="sunholo",
|
|
5
|
+
def load_prompt_from_yaml(key, prefix="sunholo", load_from_file=False):
|
|
6
6
|
"""
|
|
7
7
|
Returns a string you can use with Langfuse PromptTemplate.from_template()
|
|
8
8
|
|
|
@@ -17,6 +17,12 @@ def load_prompt_from_yaml(key, prefix="sunholo", file_path=None):
|
|
|
17
17
|
from langchain_core.prompts import PromptTemplate
|
|
18
18
|
|
|
19
19
|
"""
|
|
20
|
+
config = ConfigManager(prefix)
|
|
21
|
+
if load_from_file:
|
|
22
|
+
|
|
23
|
+
return config.promptConfig(key)
|
|
24
|
+
|
|
25
|
+
|
|
20
26
|
from langfuse import Langfuse
|
|
21
27
|
|
|
22
28
|
# Initialize Langfuse client
|
|
@@ -35,4 +41,4 @@ def load_prompt_from_yaml(key, prefix="sunholo", file_path=None):
|
|
|
35
41
|
except Exception as err:
|
|
36
42
|
log.warning(f"Could not find langfuse template: {langfuse_template} - {str(err)} - attempting to load from promptConfig")
|
|
37
43
|
|
|
38
|
-
return
|
|
44
|
+
return config.promptConfig(key)
|
|
@@ -4,7 +4,7 @@ except ImportError:
|
|
|
4
4
|
rag = None
|
|
5
5
|
|
|
6
6
|
from ..logging import log
|
|
7
|
-
from ..utils
|
|
7
|
+
from ..utils import ConfigManager
|
|
8
8
|
from ..vertex import init_vertex
|
|
9
9
|
from .get_files import fetch_corpus
|
|
10
10
|
from ..components import load_memories
|
|
@@ -41,7 +41,8 @@ def do_llamaindex(message_data, metadata, vector_name):
|
|
|
41
41
|
if not rag:
|
|
42
42
|
raise ValueError("Need to install vertexai module via `pip install sunholo[gcp]`")
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
config = ConfigManager(vector_name)
|
|
45
|
+
gcp_config = config.vacConfig("gcp_config")
|
|
45
46
|
if not gcp_config:
|
|
46
47
|
raise ValueError(f"Need config.{vector_name}.gcp_config to configure llamaindex on VertexAI")
|
|
47
48
|
|
|
@@ -81,7 +82,7 @@ def do_llamaindex(message_data, metadata, vector_name):
|
|
|
81
82
|
|
|
82
83
|
corpuses.append(corpus)
|
|
83
84
|
if not corpuses:
|
|
84
|
-
log.
|
|
85
|
+
log.info("No Vertex Llamaindex RAG corpus to import data")
|
|
85
86
|
return None
|
|
86
87
|
|
|
87
88
|
try:
|
|
@@ -93,8 +94,7 @@ def do_llamaindex(message_data, metadata, vector_name):
|
|
|
93
94
|
log.info(f"Found llamaindex corpus: {corpuses}")
|
|
94
95
|
|
|
95
96
|
# native support for cloud storage and drive links
|
|
96
|
-
chunker_config =
|
|
97
|
-
|
|
97
|
+
chunker_config = config.vacConfig("chunker")
|
|
98
98
|
|
|
99
99
|
if message_data.startswith("gs://") or message_data.startswith("https://drive.google.com"):
|
|
100
100
|
log.info(f"rag.import_files for {message_data}")
|
|
@@ -115,7 +115,8 @@ def do_llamaindex(message_data, metadata, vector_name):
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def check_llamaindex_in_memory(vector_name):
|
|
118
|
-
memories =
|
|
118
|
+
memories = ConfigManager(vector_name).vacConfig("memory")
|
|
119
|
+
|
|
119
120
|
for memory in memories: # Iterate over the list
|
|
120
121
|
for key, value in memory.items(): # Now iterate over the dictionary
|
|
121
122
|
log.info(f"Found memory {key}")
|
|
@@ -130,7 +131,7 @@ def check_llamaindex_in_memory(vector_name):
|
|
|
130
131
|
|
|
131
132
|
def llamaindex_chunker_check(message_data, metadata, vector_name):
|
|
132
133
|
# llamaindex handles its own chunking/embedding
|
|
133
|
-
memories =
|
|
134
|
+
memories = ConfigManager(vector_name).vacConfig("memory")
|
|
134
135
|
total_memories = len(memories)
|
|
135
136
|
llama = None
|
|
136
137
|
if check_llamaindex_in_memory(vector_name):
|
sunholo/streaming/langserve.py
CHANGED
|
@@ -158,11 +158,14 @@ def parse_json_data(json_data: dict):
|
|
|
158
158
|
try:
|
|
159
159
|
if isinstance(json_data, dict):
|
|
160
160
|
content = json_data.get('content', None)
|
|
161
|
+
metadata = json_data.get('metadata', None)
|
|
161
162
|
if content is not None: # content can be '' empty string
|
|
162
163
|
#log.debug(f'Yield content: {content}')
|
|
163
164
|
yield content
|
|
165
|
+
elif metadata is not None:
|
|
166
|
+
log.debug('Metadata:', metadata)
|
|
164
167
|
else:
|
|
165
|
-
log.debug(f'No "content" key found, yielding all json data dict: {json_data}')
|
|
168
|
+
log.debug(f'No "content" or "metadata" key found, yielding all json data dict: {json_data}')
|
|
166
169
|
yield json_data # Yielding all JSON data
|
|
167
170
|
elif isinstance(json_data, str):
|
|
168
171
|
yield json_data
|
sunholo/utils/__init__.py
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
from .config import load_config_key, load_config
|
|
1
|
+
from .config import load_config_key, load_config
|
|
2
|
+
from .config_class import ConfigManager
|
sunholo/utils/config.py
CHANGED
|
@@ -57,7 +57,7 @@ def load_all_configs():
|
|
|
57
57
|
"""
|
|
58
58
|
from ..logging import log
|
|
59
59
|
|
|
60
|
-
if not os.getenv("_CONFIG_FOLDER"
|
|
60
|
+
if not os.getenv("_CONFIG_FOLDER"):
|
|
61
61
|
log.debug("_CONFIG_FOLDER is not set, using os.getcwd() instead")
|
|
62
62
|
else:
|
|
63
63
|
log.debug(f"_CONFIG_FOLDER set to: {os.getenv('_CONFIG_FOLDER')}")
|