sunholo 0.59.7__tar.gz → 0.60.1__tar.gz

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 (110) hide show
  1. {sunholo-0.59.7 → sunholo-0.60.1}/PKG-INFO +2 -2
  2. {sunholo-0.59.7 → sunholo-0.60.1}/setup.py +1 -1
  3. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/dispatch_to_qa.py +5 -1
  4. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/route.py +2 -2
  5. sunholo-0.60.1/sunholo/cli/chat_vac.py +141 -0
  6. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/cli/cli.py +12 -2
  7. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/cli/cli_init.py +3 -23
  8. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/cli/configs.py +1 -1
  9. sunholo-0.60.1/sunholo/cli/merge_texts.py +41 -0
  10. sunholo-0.60.1/sunholo/cli/run_proxy.py +181 -0
  11. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/alloydb.py +1 -1
  12. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/utils/big_context.py +10 -1
  13. sunholo-0.60.1/sunholo/utils/user_ids.py +39 -0
  14. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo.egg-info/PKG-INFO +2 -2
  15. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo.egg-info/SOURCES.txt +4 -0
  16. {sunholo-0.59.7 → sunholo-0.60.1}/LICENSE.txt +0 -0
  17. {sunholo-0.59.7 → sunholo-0.60.1}/MANIFEST.in +0 -0
  18. {sunholo-0.59.7 → sunholo-0.60.1}/README.md +0 -0
  19. {sunholo-0.59.7 → sunholo-0.60.1}/setup.cfg +0 -0
  20. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/__init__.py +0 -0
  21. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/__init__.py +0 -0
  22. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/chat_history.py +0 -0
  23. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/fastapi/__init__.py +0 -0
  24. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/fastapi/base.py +0 -0
  25. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/fastapi/qna_routes.py +0 -0
  26. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/flask/__init__.py +0 -0
  27. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/flask/base.py +0 -0
  28. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/flask/qna_routes.py +0 -0
  29. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/langserve.py +0 -0
  30. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/pubsub.py +0 -0
  31. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/special_commands.py +0 -0
  32. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/agents/test_chat_history.py +0 -0
  33. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/archive/__init__.py +0 -0
  34. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/archive/archive.py +0 -0
  35. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/auth/__init__.py +0 -0
  36. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/auth/run.py +0 -0
  37. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/bots/__init__.py +0 -0
  38. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/bots/discord.py +0 -0
  39. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/bots/github_webhook.py +0 -0
  40. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/bots/webapp.py +0 -0
  41. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/__init__.py +0 -0
  42. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
  43. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/doc_handling.py +0 -0
  44. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/images.py +0 -0
  45. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/loaders.py +0 -0
  46. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/message_data.py +0 -0
  47. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/pdfs.py +0 -0
  48. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/publish.py +0 -0
  49. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/chunker/splitter.py +0 -0
  50. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/cli/__init__.py +0 -0
  51. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/cli/deploy.py +0 -0
  52. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/components/__init__.py +0 -0
  53. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/components/llm.py +0 -0
  54. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/components/prompt.py +0 -0
  55. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/components/retriever.py +0 -0
  56. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/components/vectorstore.py +0 -0
  57. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/__init__.py +0 -0
  58. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/database.py +0 -0
  59. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/lancedb.py +0 -0
  60. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/sql/sb/create_function.sql +0 -0
  61. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  62. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/sql/sb/create_table.sql +0 -0
  63. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  64. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/sql/sb/return_sources.sql +0 -0
  65. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/sql/sb/setup.sql +0 -0
  66. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/static_dbs.py +0 -0
  67. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/database/uuid.py +0 -0
  68. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/embedder/__init__.py +0 -0
  69. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/embedder/embed_chunk.py +0 -0
  70. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/gcs/__init__.py +0 -0
  71. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/gcs/add_file.py +0 -0
  72. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/gcs/download_url.py +0 -0
  73. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/gcs/metadata.py +0 -0
  74. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/langfuse/__init__.py +0 -0
  75. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/langfuse/callback.py +0 -0
  76. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/langfuse/prompts.py +0 -0
  77. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/llamaindex/__init__.py +0 -0
  78. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/llamaindex/generate.py +0 -0
  79. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/llamaindex/import_files.py +0 -0
  80. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/logging.py +0 -0
  81. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/lookup/__init__.py +0 -0
  82. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/lookup/model_lookup.yaml +0 -0
  83. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/patches/__init__.py +0 -0
  84. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/patches/langchain/__init__.py +0 -0
  85. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/patches/langchain/lancedb.py +0 -0
  86. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/patches/langchain/vertexai.py +0 -0
  87. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/pubsub/__init__.py +0 -0
  88. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/pubsub/process_pubsub.py +0 -0
  89. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/pubsub/pubsub_manager.py +0 -0
  90. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/qna/__init__.py +0 -0
  91. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/qna/parsers.py +0 -0
  92. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/qna/retry.py +0 -0
  93. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/streaming/__init__.py +0 -0
  94. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/streaming/content_buffer.py +0 -0
  95. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/streaming/langserve.py +0 -0
  96. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/streaming/streaming.py +0 -0
  97. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/summarise/__init__.py +0 -0
  98. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/summarise/summarise.py +0 -0
  99. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/utils/__init__.py +0 -0
  100. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/utils/config.py +0 -0
  101. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/utils/config_schema.py +0 -0
  102. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/utils/gcp.py +0 -0
  103. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/utils/parsers.py +0 -0
  104. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/vertex/__init__.py +0 -0
  105. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo/vertex/init_vertex.py +0 -0
  106. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo.egg-info/dependency_links.txt +0 -0
  107. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo.egg-info/entry_points.txt +0 -0
  108. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo.egg-info/requires.txt +0 -0
  109. {sunholo-0.59.7 → sunholo-0.60.1}/sunholo.egg-info/top_level.txt +0 -0
  110. {sunholo-0.59.7 → sunholo-0.60.1}/test/test_dispatch_to_qa.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.59.7
3
+ Version: 0.60.1
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.59.7.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.60.1.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
  # Define your base version
4
- version = '0.59.7'
4
+ version = '0.60.1'
5
5
 
6
6
  setup(
7
7
  name='sunholo',
@@ -52,8 +52,12 @@ def prep_request_payload(user_input, chat_history, vector_name, stream, **kwargs
52
52
  agent = load_config_key("agent", vector_name=vector_name, kind="vacConfig")
53
53
  agent_type = load_config_key("agent_type", vector_name=vector_name, kind="vacConfig")
54
54
 
55
+ override_endpoint = kwargs.get("endpoint_url")
56
+ if override_endpoint:
57
+ log.info(f"Overriding endpoint with {override_endpoint}")
58
+
55
59
  # {'stream': '', 'invoke': ''}
56
- endpoints = route_endpoint(vector_name)
60
+ endpoints = route_endpoint(vector_name, override_endpoint=override_endpoint)
57
61
 
58
62
  qna_endpoint = endpoints["stream"] if stream else endpoints["invoke"]
59
63
 
@@ -35,13 +35,13 @@ def route_qna(vector_name):
35
35
  log.info(f'agent_url: {agent_url}')
36
36
  return agent_url
37
37
 
38
- def route_endpoint(vector_name):
38
+ def route_endpoint(vector_name, override_endpoint=None):
39
39
 
40
40
  agent_type = load_config_key('agent_type', vector_name, kind="vacConfig")
41
41
  if not agent_type:
42
42
  agent_type = load_config_key('agent', vector_name, kind="vacConfig")
43
43
 
44
- stem = route_qna(vector_name)
44
+ stem = route_qna(vector_name) if not override_endpoint else override_endpoint
45
45
 
46
46
  agent_config, _ = load_config('config/agent_config.yaml')
47
47
 
@@ -0,0 +1,141 @@
1
+ from ..agents import send_to_qa_async
2
+ from ..streaming import generate_proxy_stream_async, generate_proxy_stream
3
+ from ..utils.user_ids import generate_uuid_from_gcloud_user
4
+
5
+ from .run_proxy import load_proxies, start_proxy
6
+
7
+ import uuid
8
+
9
+ def get_service_url(service_name):
10
+ proxies = load_proxies()
11
+ if service_name in proxies:
12
+ port = proxies[service_name]['port']
13
+ return f"http://127.0.0.1:{port}"
14
+ else:
15
+ print(f"No proxy found running for service: {service_name} - attempting to connect")
16
+ return start_proxy(service_name)
17
+
18
+ async def stream_chat_session(service_name, chat_history):
19
+
20
+ service_url = get_service_url(service_name)
21
+ user_id = generate_uuid_from_gcloud_user()
22
+ while True:
23
+ session_id = str(uuid.uuid4())
24
+ user_input = input("You: ")
25
+ if user_input.lower() in ["exit", "quit"]:
26
+ print("Exiting chat session.")
27
+ break
28
+
29
+ chat_history.append({"role": "user", "content": user_input})
30
+
31
+
32
+
33
+ async def stream_response():
34
+ generate = await generate_proxy_stream_async(
35
+ send_to_qa_async,
36
+ user_input,
37
+ vector_name=service_name,
38
+ chat_history=chat_history,
39
+ generate_f_output=lambda x: x, # Replace with actual processing function
40
+ stream_wait_time=0.5,
41
+ stream_timeout=120,
42
+ message_author=user_id,
43
+ #TODO: populate these
44
+ image_url=None,
45
+ source_filters=None,
46
+ search_kwargs=None,
47
+ private_docs=None,
48
+ whole_document=False,
49
+ source_filters_and_or=False,
50
+ # system kwargs
51
+ configurable={
52
+ "vector_name": service_name,
53
+ },
54
+ user_id=user_id,
55
+ session_id=session_id,
56
+ message_source="cli",
57
+ override_endpoint=service_url
58
+ )
59
+ async for part in generate():
60
+ yield part
61
+
62
+ print("Assistant: ", end='', flush=True)
63
+ async for token in stream_response():
64
+ if isinstance(token, bytes):
65
+ token = token.decode('utf-8')
66
+ print(token, end='', flush=True)
67
+
68
+ chat_history.append({"role": "assistant", "content": token})
69
+ print() # For new line after streaming ends
70
+
71
+ async def headless_mode(service_name, user_input, chat_history=None):
72
+ chat_history = chat_history or []
73
+ chat_history.append({"role": "user", "content": user_input})
74
+ service_url = get_service_url(service_name)
75
+ user_id = generate_uuid_from_gcloud_user()
76
+ session_id = str(uuid.uuid4())
77
+
78
+ async def stream_response():
79
+ generate = await generate_proxy_stream_async(
80
+ send_to_qa_async,
81
+ user_input,
82
+ vector_name=service_name,
83
+ chat_history=chat_history,
84
+ generate_f_output=lambda x: x, # Replace with actual processing function
85
+ stream_wait_time=0.5,
86
+ stream_timeout=120,
87
+ message_author=user_id,
88
+ #TODO: populate these
89
+ image_url=None,
90
+ source_filters=None,
91
+ search_kwargs=None,
92
+ private_docs=None,
93
+ whole_document=False,
94
+ source_filters_and_or=False,
95
+ # system kwargs
96
+ configurable={
97
+ "vector_name": service_name,
98
+ },
99
+ user_id=user_id,
100
+ session_id=session_id,
101
+ message_source="cli",
102
+ override_endpoint=service_url
103
+ )
104
+ async for part in generate():
105
+ yield part
106
+
107
+ print("Assistant: ", end='', flush=True)
108
+ async for token in stream_response():
109
+ if isinstance(token, bytes):
110
+ token = token.decode('utf-8')
111
+ print(token, end='', flush=True)
112
+
113
+ chat_history.append({"role": "assistant", "content": token})
114
+ print() # For new line after streaming ends
115
+
116
+
117
+ def vac_command(args):
118
+ try:
119
+ service_url = get_service_url(args.service_name)
120
+ except ValueError as e:
121
+ print(e)
122
+ return
123
+
124
+ if args.headless:
125
+ headless_mode(service_url, args.user_input, args.chat_history)
126
+ else:
127
+ stream_chat_session(service_url)
128
+
129
+ def setup_vac_subparser(subparsers):
130
+ """
131
+ Sets up an argparse subparser for the 'vac' command.
132
+
133
+ Args:
134
+ subparsers: The subparsers object from argparse.ArgumentParser().
135
+ """
136
+ vac_parser = subparsers.add_parser('vac', help='Interact with the VAC service.')
137
+ vac_parser.add_argument('service_name', help='Name of the VAC service.')
138
+ vac_parser.add_argument('user_input', help='User input for the VAC service.', nargs='?', default=None)
139
+ vac_parser.add_argument('--headless', action='store_true', help='Run in headless mode.')
140
+ vac_parser.add_argument('--chat_history', help='Chat history for headless mode (as JSON string).', default=None)
141
+ vac_parser.set_defaults(func=vac_command)
@@ -3,6 +3,10 @@ import argparse
3
3
  from .configs import setup_list_configs_subparser
4
4
  from .deploy import setup_deploy_subparser
5
5
  from .cli_init import setup_init_subparser
6
+ from .merge_texts import setup_merge_text_subparser
7
+ from .run_proxy import setup_proxy_subparser
8
+ from .chat_vac import setup_vac_subparser
9
+
6
10
 
7
11
  def main(args=None):
8
12
  """
@@ -21,12 +25,18 @@ def main(args=None):
21
25
  dest='command',
22
26
  required=True)
23
27
 
24
- # Setup deploy command
28
+ # deploy command
25
29
  setup_deploy_subparser(subparsers)
26
30
  # Setup list-configs command
27
31
  setup_list_configs_subparser(subparsers)
28
- # init
32
+ # init command
29
33
  setup_init_subparser(subparsers)
34
+ # merge-text command
35
+ setup_merge_text_subparser(subparsers)
36
+ # proxy command
37
+ setup_proxy_subparser(subparsers)
38
+ # vac command
39
+ setup_vac_subparser(subparsers)
30
40
 
31
41
  args = parser.parse_args(args)
32
42
 
@@ -6,28 +6,9 @@ def init_project(args):
6
6
  """
7
7
  Initializes a new sunholo project with a basic configuration file and directory structure.
8
8
 
9
- **Explanation:**
10
-
11
- 1. **Import Necessary Modules:**
12
- - `os` for file system operations.
13
- - `shutil` for copying files and directories.
14
- - `log` from `sunholo.logging` for logging messages.
15
- - `get_module_filepath` from `sunholo.utils.config` to get the absolute path of template files.
16
-
17
- 2. **`init_project` Function:**
18
- - Takes an `args` object from argparse, containing the `project_name`.
19
- - Creates the project directory using `os.makedirs`.
20
- - Copies template files from the `templates/project` directory to the new project directory using `shutil.copy` and `shutil.copytree`.
21
- - Logs informative messages about the initialization process.
22
-
23
- 3. **`setup_init_subparser` Function:**
24
- - Sets up the `init` subcommand for the `sunholo` CLI.
25
- - Adds an argument `project_name` to specify the name of the new project.
26
- - Sets the `func` attribute to `init_project`, so the parser knows which function to call when the `init` command is used.
27
-
28
9
  **Template Files (`templates/project`):**
29
10
 
30
- You'll need to create a `templates/project` directory within your `sunholo` package and place the following template files in it:
11
+ A `templates/project` directory is within the `sunholo` package with the following template files in it:
31
12
 
32
13
  * **`config/llm_config.yaml`:** A basic configuration file with placeholders for LLM settings, vector stores, etc.
33
14
  * **`config/cloud_run_urls.json`:** A template for Cloud Run URLs.
@@ -37,14 +18,13 @@ You'll need to create a `templates/project` directory within your `sunholo` pack
37
18
 
38
19
  **Usage:**
39
20
 
40
- After adding this code to your `cli.py` and creating the template files, users can initialize a new project using the following command:
21
+ Users can initialize a new project using the following command:
41
22
 
42
23
  ```bash
43
24
  sunholo init my_genai_project
44
25
  ```
45
26
 
46
27
  This will create a new directory named `my_genai_project` with the template files, allowing users to start building their GenAI application.
47
-
48
28
  """
49
29
  project_name = args.project_name
50
30
  project_dir = os.path.join(os.getcwd(), project_name)
@@ -75,6 +55,6 @@ def setup_init_subparser(subparsers):
75
55
  """
76
56
  Sets up an argparse subparser for the 'init' command.
77
57
  """
78
- init_parser = subparsers.add_parser('init', help='Initializes a new sunholo project.')
58
+ init_parser = subparsers.add_parser('init', help='Initializes a new Multivac project.')
79
59
  init_parser.add_argument('project_name', help='The name of the new project.')
80
60
  init_parser.set_defaults(func=init_project)
@@ -113,7 +113,7 @@ def setup_list_configs_subparser(subparsers):
113
113
  subparsers = parser.add_subparsers()
114
114
  setup_list_configs_subparser(subparsers)
115
115
  """
116
- list_configs_parser = subparsers.add_parser('list-configs', help='Lists all configuration files and their details e.g. `sunholo list-configs --kind=vacConfig --vac=edmonbrain`')
116
+ list_configs_parser = subparsers.add_parser('list-configs', help='Lists all configuration files and their details')
117
117
  list_configs_parser.add_argument('--kind', help='Filter configurations by kind e.g. `--kind=vacConfig`')
118
118
  list_configs_parser.add_argument('--vac', help='Filter configurations by VAC name e.g. `--vac=edmonbrain`')
119
119
  list_configs_parser.add_argument('--validate', action='store_true', help='Validate the configuration files.')
@@ -0,0 +1,41 @@
1
+ import os
2
+ from pprint import pprint
3
+
4
+ from ..utils.big_context import load_gitignore_patterns, merge_text_files
5
+
6
+ def setup_merge_text_subparser(subparsers):
7
+ """
8
+ Sets up an argparse subparser for the 'merge-text' command.
9
+
10
+ Args:
11
+ subparsers: The subparsers object from argparse.ArgumentParser().
12
+ """
13
+ merge_text_parser = subparsers.add_parser('merge-text', help='Merge text files from a source folder into a single output file.')
14
+ merge_text_parser.add_argument('source_folder', help='Folder containing the text files.')
15
+ merge_text_parser.add_argument('output_file', help='Output file to write the merged text.')
16
+ merge_text_parser.add_argument('--gitignore', help='Path to .gitignore file to exclude patterns.', default=None)
17
+ merge_text_parser.add_argument('--output_tree', action='store_true', help='Set to output the file tree in the console after merging', default=None)
18
+
19
+ merge_text_parser.set_defaults(func=merge_text_files_command)
20
+
21
+ def merge_text_files_command(args):
22
+ """
23
+ Command to merge text files based on the provided arguments.
24
+
25
+ Args:
26
+ args: Command-line arguments.
27
+ """
28
+ gitignore_path = os.path.join(args.source_folder, '.gitignore') if not args.gitignore else args.gitignore
29
+
30
+ if os.path.exists(gitignore_path):
31
+ patterns = load_gitignore_patterns(gitignore_path)
32
+ print(f"Ignoring patterns from {gitignore_path}")
33
+ else:
34
+ patterns = [] # Empty list if no .gitignore
35
+
36
+ print(f"Merging text files within {args.source_folder} to {args.output_file}")
37
+ file_tree = merge_text_files(args.source_folder, args.output_file, patterns)
38
+ print(f"OK: Merged files available in {args.output_file}")
39
+ if args.output_tree:
40
+ print(f"==File Tree for {args.source_folder}")
41
+ pprint(file_tree)
@@ -0,0 +1,181 @@
1
+ import subprocess
2
+ import os
3
+ import signal
4
+ import json
5
+
6
+ PROXY_TRACKER_FILE = '.vac_proxy_tracker.json'
7
+ DEFAULT_PORT = 8080
8
+
9
+ def create_hyperlink(url, text):
10
+ """
11
+ Creates a hyperlink for the console.
12
+
13
+ Args:
14
+ url (str): The URL for the hyperlink.
15
+ text (str): The text to display for the hyperlink.
16
+
17
+ Returns:
18
+ str: The formatted hyperlink.
19
+ """
20
+ return f"\033]8;;{url}\033\\{text}\033]8;;\033\\"
21
+
22
+
23
+ def get_next_available_port(proxies, default_port):
24
+ """
25
+ Get the next available port starting from the default port.
26
+
27
+ Args:
28
+ proxies (dict): Current proxies with their assigned ports.
29
+ default_port (int): Default starting port.
30
+
31
+ Returns:
32
+ int: The next available port.
33
+ """
34
+ used_ports = {info["port"] for info in proxies.values()}
35
+ port = default_port
36
+ while port in used_ports:
37
+ port += 1
38
+ return port
39
+
40
+ def check_gcloud():
41
+ """
42
+ Checks if gcloud is installed and authenticated.
43
+
44
+ Returns:
45
+ bool: True if gcloud is installed and authenticated, False otherwise.
46
+ """
47
+ try:
48
+ # Check if gcloud is installed
49
+ result = subprocess.run(["gcloud", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
50
+ if result.returncode != 0:
51
+ print("ERROR: gcloud is not installed or not found in PATH.")
52
+ return False
53
+
54
+ # Check if gcloud is authenticated
55
+ result = subprocess.run(["gcloud", "auth", "list"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
56
+ if result.returncode != 0 or "ACTIVE" not in result.stdout.decode():
57
+ print("ERROR: gcloud is not authenticated. Please run 'gcloud auth login'.")
58
+ return False
59
+
60
+ print("gcloud is installed and authenticated.")
61
+ return True
62
+ except Exception as e:
63
+ print(f"ERROR: An unexpected error occurred: {e}")
64
+ return False
65
+
66
+ def load_proxies():
67
+ if os.path.exists(PROXY_TRACKER_FILE):
68
+ with open(PROXY_TRACKER_FILE, 'r') as file:
69
+ return json.load(file)
70
+ return {}
71
+
72
+ def save_proxies(proxies):
73
+ with open(PROXY_TRACKER_FILE, 'w') as file:
74
+ json.dump(proxies, file, indent=4)
75
+
76
+ def start_proxy(service_name, region, project, port=None):
77
+ """
78
+ Starts the gcloud proxy to the Cloud Run service and stores the PID.
79
+
80
+ Args:
81
+ service_name (str): Name of the Cloud Run service.
82
+ region (str): Region of the Cloud Run service.
83
+ project (str): GCP project of the Cloud Run service.
84
+ port (int, optional): Port to run the proxy on. If not provided, auto-assigns the next available port.
85
+ """
86
+ proxies = load_proxies()
87
+
88
+ if service_name in proxies:
89
+ print(f"Proxy for service {service_name} is already running on port {proxies[service_name]['port']}.")
90
+ return
91
+
92
+ if not port:
93
+ port = get_next_available_port(proxies, DEFAULT_PORT)
94
+
95
+ command = [
96
+ "gcloud", "run", "services", "proxy", service_name,
97
+ "--region", region,
98
+ "--project", project,
99
+ "--port", str(port)
100
+ ]
101
+ with open(os.devnull, 'w') as devnull:
102
+ process = subprocess.Popen(command, stdout=devnull, stderr=devnull, preexec_fn=os.setpgrp)
103
+
104
+ proxies[service_name] = {
105
+ "pid": process.pid,
106
+ "port": port
107
+ }
108
+ save_proxies(proxies)
109
+
110
+ print(f"Proxy for {service_name} setup complete on port {port}")
111
+ list_proxies()
112
+
113
+ return f"http://127.0.0.1:{port}"
114
+
115
+
116
+ def stop_proxy(service_name):
117
+ """
118
+ Stops the gcloud proxy to the Cloud Run service using the stored PID.
119
+
120
+ Args:
121
+ service_name (str): Name of the Cloud Run service.
122
+ """
123
+ proxies = load_proxies()
124
+
125
+ if service_name not in proxies:
126
+ print(f"No proxy found for service: {service_name}")
127
+ return
128
+
129
+ pid = proxies[service_name]["pid"]
130
+ try:
131
+ os.kill(pid, signal.SIGTERM)
132
+ del proxies[service_name]
133
+ save_proxies(proxies)
134
+ print(f"Proxy for {service_name} stopped.")
135
+ except ProcessLookupError:
136
+ print(f"No process found with PID: {pid}")
137
+ except Exception as e:
138
+ print(f"Error stopping proxy for {service_name}: {e}")
139
+
140
+ list_proxies()
141
+
142
+ def list_proxies():
143
+ """
144
+ Lists all running proxies.
145
+ """
146
+ proxies = load_proxies()
147
+ if not proxies:
148
+ print("No proxies currently running.")
149
+ else:
150
+ print("=== VAC Proxies Running ===")
151
+ for service_name, info in proxies.items():
152
+ url = f"http://127.0.0.1:{info['port']}"
153
+ hyperlink = create_hyperlink(url, url)
154
+ print(f"- VAC: {service_name} Port: {info['port']} PID: {info['pid']} URL: {hyperlink}")
155
+
156
+
157
+ def setup_proxy_subparser(subparsers):
158
+ """
159
+ Sets up an argparse subparser for the 'proxy' command.
160
+
161
+ Args:
162
+ subparsers: The subparsers object from argparse.ArgumentParser().
163
+ """
164
+ proxy_parser = subparsers.add_parser('proxy', help='Set up or stop a proxy to the Cloud Run service.')
165
+ proxy_subparsers = proxy_parser.add_subparsers(dest='proxy_command', required=True)
166
+
167
+ start_parser = proxy_subparsers.add_parser('start', help='Start the proxy to the Cloud Run service.')
168
+ start_parser.add_argument('service_name', help='Name of the Cloud Run service.')
169
+ start_parser.add_argument('--region', default='europe-west1', help='Region of the Cloud Run service.')
170
+ start_parser.add_argument('--project', default='multivac-internal-dev', help='GCP project of the Cloud Run service.')
171
+ start_parser.add_argument('--port', type=int, help='Port to run the proxy on. Auto-assigns if not provided.')
172
+ start_parser.set_defaults(func=lambda args: start_proxy(args.service_name, args.region, args.project, args.port))
173
+
174
+ stop_parser = proxy_subparsers.add_parser('stop', help='Stop the proxy to the Cloud Run service.')
175
+ stop_parser.add_argument('service_name', help='Name of the Cloud Run service.')
176
+ stop_parser.set_defaults(func=lambda args: stop_proxy(args.service_name))
177
+
178
+ list_parser = proxy_subparsers.add_parser('list', help='List all running proxies.')
179
+ list_parser.set_defaults(func=lambda args: list_proxies())
180
+
181
+
@@ -178,7 +178,7 @@ def create_alloydb_table(vector_name, engine, type = "vectorstore", alloydb_conf
178
178
  try:
179
179
  if type == "vectorstore":
180
180
  from .database import get_vector_size
181
- vector_size = get_vector_size(vector_name, config_file="config/llm_config.yaml")
181
+ vector_size = get_vector_size(vector_name)
182
182
  table_name = f"{vector_name}_{type}_{vector_size}"
183
183
  if table_name in alloydb_table_cache:
184
184
  log.info(f"AlloyDB Table '{table_name}' exists in cache, skipping creation.")
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from fnmatch import fnmatch
2
3
 
3
4
  def has_text_extension(file_path):
4
5
  """
@@ -42,6 +43,7 @@ def load_gitignore_patterns(gitignore_path):
42
43
  """
43
44
  with open(gitignore_path, 'r') as f:
44
45
  patterns = [line.strip() for line in f if line.strip() and not line.startswith('#')]
46
+ patterns.extend(["*.git/*", "*.terraform/*"])
45
47
  return patterns
46
48
 
47
49
  def should_ignore(file_path, patterns):
@@ -59,13 +61,15 @@ def should_ignore(file_path, patterns):
59
61
  >>> should_ignore("path/to/file.txt", ["*.txt", "node_modules/"])
60
62
  True
61
63
  """
62
- from fnmatch import fnmatch
63
64
  rel_path = os.path.relpath(file_path)
65
+
64
66
  for pattern in patterns:
65
67
  if fnmatch(rel_path, pattern) or fnmatch(os.path.basename(rel_path), pattern):
66
68
  return True
69
+
67
70
  return False
68
71
 
72
+
69
73
  def build_file_tree(source_folder, patterns):
70
74
  """
71
75
  Build a hierarchical file tree structure of a directory, ignoring files and directories in .gitignore.
@@ -112,6 +116,7 @@ def merge_text_files(source_folder, output_file, patterns):
112
116
  file_tree = build_file_tree(source_folder, patterns)
113
117
  with open(output_file, 'w', encoding='utf-8') as outfile:
114
118
  for root, dirs, files in os.walk(source_folder):
119
+ print(f"- merging {root}...")
115
120
  # Filter out ignored directories
116
121
  dirs[:] = [d for d in dirs if not should_ignore(os.path.join(root, d), patterns)]
117
122
  # Filter out ignored files
@@ -119,6 +124,8 @@ def merge_text_files(source_folder, output_file, patterns):
119
124
 
120
125
  for file_name in files:
121
126
  file_path = os.path.join(root, file_name)
127
+ if file_path == output_file:
128
+ continue
122
129
  if has_text_extension(file_path):
123
130
  try:
124
131
  with open(file_path, 'r', encoding='utf-8') as infile:
@@ -129,6 +136,8 @@ def merge_text_files(source_folder, output_file, patterns):
129
136
  print(f"Skipping file (cannot read as text): {file_path}")
130
137
  outfile.write("\n--- File Tree ---\n")
131
138
  outfile.write("\n".join(file_tree))
139
+
140
+ return file_tree
132
141
 
133
142
  # Example usage
134
143
  if __name__ == "__main__":
@@ -0,0 +1,39 @@
1
+ import uuid
2
+ from google.auth import default
3
+
4
+ from ..logging import log
5
+
6
+ import hashlib
7
+ import platform
8
+ import socket
9
+
10
+ def generate_user_id():
11
+ data = f"{socket.gethostname()}-{platform.platform()}-{platform.processor()}"
12
+ hashed_id = hashlib.sha256(data.encode('utf-8')).hexdigest()
13
+ return hashed_id
14
+
15
+ def generate_uuid_from_gcloud_user():
16
+ """
17
+ Generates a UUID using the Google Cloud authorized user's email address, or if not available via os settings.
18
+
19
+ Returns:
20
+ str: The generated UUID as a string.
21
+ """
22
+ _, credentials = default() # Get the default credentials
23
+
24
+ if credentials:
25
+ user_email = credentials.service_account_email # Get email for service accounts
26
+ if not user_email:
27
+ user_email = credentials.id_token['email'] # Get email for user accounts
28
+
29
+ if user_email:
30
+ # Create a UUID using the user's email as a source for randomness
31
+ user_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, user_email)
32
+ return str(user_uuid)
33
+ else:
34
+ log.warning("Unable to get user email from Google Cloud credentials.")
35
+
36
+ else:
37
+ log.warning("No Google Cloud credentials found.")
38
+
39
+ return str(generate_user_id())
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.59.7
3
+ Version: 0.60.1
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.59.7.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.60.1.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -43,10 +43,13 @@ sunholo/chunker/pdfs.py
43
43
  sunholo/chunker/publish.py
44
44
  sunholo/chunker/splitter.py
45
45
  sunholo/cli/__init__.py
46
+ sunholo/cli/chat_vac.py
46
47
  sunholo/cli/cli.py
47
48
  sunholo/cli/cli_init.py
48
49
  sunholo/cli/configs.py
49
50
  sunholo/cli/deploy.py
51
+ sunholo/cli/merge_texts.py
52
+ sunholo/cli/run_proxy.py
50
53
  sunholo/components/__init__.py
51
54
  sunholo/components/llm.py
52
55
  sunholo/components/prompt.py
@@ -100,6 +103,7 @@ sunholo/utils/config.py
100
103
  sunholo/utils/config_schema.py
101
104
  sunholo/utils/gcp.py
102
105
  sunholo/utils/parsers.py
106
+ sunholo/utils/user_ids.py
103
107
  sunholo/vertex/__init__.py
104
108
  sunholo/vertex/init_vertex.py
105
109
  test/test_dispatch_to_qa.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes