sunholo 0.60.1__tar.gz → 0.60.3__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 (112) hide show
  1. {sunholo-0.60.1 → sunholo-0.60.3}/PKG-INFO +10 -6
  2. {sunholo-0.60.1 → sunholo-0.60.3}/setup.py +11 -5
  3. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/__init__.py +3 -0
  4. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/dispatch_to_qa.py +1 -1
  5. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/flask/qna_routes.py +4 -0
  6. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/chat_vac.py +75 -44
  7. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/cli.py +35 -2
  8. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/run_proxy.py +119 -12
  9. sunholo-0.60.3/sunholo/cli/sun_rich.py +3 -0
  10. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/components/retriever.py +2 -1
  11. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/components/vectorstore.py +27 -19
  12. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/gcs/download_url.py +1 -1
  13. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/logging.py +8 -6
  14. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/utils/config.py +15 -11
  15. sunholo-0.60.3/sunholo/utils/user_ids.py +10 -0
  16. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo.egg-info/PKG-INFO +10 -6
  17. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo.egg-info/SOURCES.txt +1 -0
  18. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo.egg-info/requires.txt +9 -4
  19. sunholo-0.60.1/sunholo/utils/user_ids.py +0 -39
  20. {sunholo-0.60.1 → sunholo-0.60.3}/LICENSE.txt +0 -0
  21. {sunholo-0.60.1 → sunholo-0.60.3}/MANIFEST.in +0 -0
  22. {sunholo-0.60.1 → sunholo-0.60.3}/README.md +0 -0
  23. {sunholo-0.60.1 → sunholo-0.60.3}/setup.cfg +0 -0
  24. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/__init__.py +0 -0
  25. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/chat_history.py +0 -0
  26. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/fastapi/__init__.py +0 -0
  27. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/fastapi/base.py +0 -0
  28. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/fastapi/qna_routes.py +0 -0
  29. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/flask/__init__.py +0 -0
  30. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/flask/base.py +0 -0
  31. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/langserve.py +0 -0
  32. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/pubsub.py +0 -0
  33. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/route.py +0 -0
  34. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/special_commands.py +0 -0
  35. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/agents/test_chat_history.py +0 -0
  36. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/archive/__init__.py +0 -0
  37. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/archive/archive.py +0 -0
  38. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/auth/__init__.py +0 -0
  39. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/auth/run.py +0 -0
  40. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/bots/__init__.py +0 -0
  41. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/bots/discord.py +0 -0
  42. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/bots/github_webhook.py +0 -0
  43. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/bots/webapp.py +0 -0
  44. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/__init__.py +0 -0
  45. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
  46. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/doc_handling.py +0 -0
  47. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/images.py +0 -0
  48. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/loaders.py +0 -0
  49. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/message_data.py +0 -0
  50. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/pdfs.py +0 -0
  51. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/publish.py +0 -0
  52. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/chunker/splitter.py +0 -0
  53. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/__init__.py +0 -0
  54. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/cli_init.py +0 -0
  55. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/configs.py +0 -0
  56. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/deploy.py +0 -0
  57. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/cli/merge_texts.py +0 -0
  58. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/components/__init__.py +0 -0
  59. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/components/llm.py +0 -0
  60. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/components/prompt.py +0 -0
  61. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/__init__.py +0 -0
  62. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/alloydb.py +0 -0
  63. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/database.py +0 -0
  64. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/lancedb.py +0 -0
  65. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/sql/sb/create_function.sql +0 -0
  66. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  67. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/sql/sb/create_table.sql +0 -0
  68. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  69. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/sql/sb/return_sources.sql +0 -0
  70. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/sql/sb/setup.sql +0 -0
  71. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/static_dbs.py +0 -0
  72. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/database/uuid.py +0 -0
  73. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/embedder/__init__.py +0 -0
  74. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/embedder/embed_chunk.py +0 -0
  75. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/gcs/__init__.py +0 -0
  76. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/gcs/add_file.py +0 -0
  77. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/gcs/metadata.py +0 -0
  78. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/langfuse/__init__.py +0 -0
  79. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/langfuse/callback.py +0 -0
  80. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/langfuse/prompts.py +0 -0
  81. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/llamaindex/__init__.py +0 -0
  82. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/llamaindex/generate.py +0 -0
  83. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/llamaindex/import_files.py +0 -0
  84. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/lookup/__init__.py +0 -0
  85. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/lookup/model_lookup.yaml +0 -0
  86. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/patches/__init__.py +0 -0
  87. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/patches/langchain/__init__.py +0 -0
  88. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/patches/langchain/lancedb.py +0 -0
  89. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/patches/langchain/vertexai.py +0 -0
  90. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/pubsub/__init__.py +0 -0
  91. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/pubsub/process_pubsub.py +0 -0
  92. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/pubsub/pubsub_manager.py +0 -0
  93. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/qna/__init__.py +0 -0
  94. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/qna/parsers.py +0 -0
  95. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/qna/retry.py +0 -0
  96. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/streaming/__init__.py +0 -0
  97. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/streaming/content_buffer.py +0 -0
  98. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/streaming/langserve.py +0 -0
  99. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/streaming/streaming.py +0 -0
  100. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/summarise/__init__.py +0 -0
  101. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/summarise/summarise.py +0 -0
  102. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/utils/__init__.py +0 -0
  103. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/utils/big_context.py +0 -0
  104. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/utils/config_schema.py +0 -0
  105. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/utils/gcp.py +0 -0
  106. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/utils/parsers.py +0 -0
  107. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/vertex/__init__.py +0 -0
  108. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo/vertex/init_vertex.py +0 -0
  109. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo.egg-info/dependency_links.txt +0 -0
  110. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo.egg-info/entry_points.txt +0 -0
  111. {sunholo-0.60.1 → sunholo-0.60.3}/sunholo.egg-info/top_level.txt +0 -0
  112. {sunholo-0.60.1 → sunholo-0.60.3}/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.60.1
3
+ Version: 0.60.3
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.60.1.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.60.3.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -18,13 +18,13 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
- Requires-Dist: jsonschema
21
+ Requires-Dist: google-auth
22
22
  Requires-Dist: langchain
23
23
  Requires-Dist: langchain_experimental
24
24
  Requires-Dist: langchain-community
25
- Requires-Dist: google-auth
26
25
  Provides-Extra: all
27
26
  Requires-Dist: asyncpg; extra == "all"
27
+ Requires-Dist: fastapi; extra == "all"
28
28
  Requires-Dist: flask; extra == "all"
29
29
  Requires-Dist: google-auth; extra == "all"
30
30
  Requires-Dist: google-auth-httplib2; extra == "all"
@@ -42,6 +42,7 @@ Requires-Dist: google-generativeai; extra == "all"
42
42
  Requires-Dist: gunicorn; extra == "all"
43
43
  Requires-Dist: httpcore; extra == "all"
44
44
  Requires-Dist: httpx; extra == "all"
45
+ Requires-Dist: jsonschema; extra == "all"
45
46
  Requires-Dist: lancedb; extra == "all"
46
47
  Requires-Dist: langchain; extra == "all"
47
48
  Requires-Dist: langchain_experimental; extra == "all"
@@ -55,10 +56,13 @@ Requires-Dist: pg8000; extra == "all"
55
56
  Requires-Dist: pgvector; extra == "all"
56
57
  Requires-Dist: psycopg2-binary; extra == "all"
57
58
  Requires-Dist: pypdf; extra == "all"
58
- Requires-Dist: fastapi; extra == "all"
59
+ Requires-Dist: python-socketio; extra == "all"
60
+ Requires-Dist: rich; extra == "all"
59
61
  Requires-Dist: supabase; extra == "all"
60
62
  Requires-Dist: tiktoken; extra == "all"
61
- Requires-Dist: python-socketio; extra == "all"
63
+ Provides-Extra: cli
64
+ Requires-Dist: jsonschema; extra == "cli"
65
+ Requires-Dist: rich; extra == "cli"
62
66
  Provides-Extra: database
63
67
  Requires-Dist: asyncpg; extra == "database"
64
68
  Requires-Dist: supabase; extra == "database"
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
  # Define your base version
4
- version = '0.60.1'
4
+ version = '0.60.3'
5
5
 
6
6
  setup(
7
7
  name='sunholo',
@@ -28,17 +28,17 @@ setup(
28
28
  },
29
29
  install_requires=[
30
30
  # Base dependencies
31
- "jsonschema",
31
+ "google-auth", # to check if on gcp
32
32
  "langchain",
33
33
  "langchain_experimental",
34
34
  "langchain-community",
35
- "google-auth" # to check if on gcp
36
35
  # Add the minimal dependencies that your package requires here
37
36
  ],
38
37
  extras_require={
39
38
  # Define optional dependencies with feature names
40
39
  'all': [
41
40
  "asyncpg",
41
+ "fastapi",
42
42
  "flask",
43
43
  "google-auth",
44
44
  "google-auth-httplib2",
@@ -56,6 +56,7 @@ setup(
56
56
  "gunicorn",
57
57
  "httpcore",
58
58
  "httpx",
59
+ "jsonschema",
59
60
  "lancedb",
60
61
  "langchain",
61
62
  "langchain_experimental",
@@ -69,10 +70,15 @@ setup(
69
70
  "pgvector",
70
71
  "psycopg2-binary",
71
72
  "pypdf",
72
- "fastapi",
73
+ "python-socketio",
74
+ "rich",
73
75
  "supabase",
74
76
  "tiktoken",
75
- "python-socketio"
77
+
78
+ ],
79
+ 'cli': [
80
+ "jsonschema",
81
+ "rich"
76
82
  ],
77
83
  'database': [
78
84
  "asyncpg",
@@ -19,6 +19,7 @@ from . import utils
19
19
  from . import vertex
20
20
  import logging
21
21
 
22
+
22
23
  __all__ = ['agents',
23
24
  'archive',
24
25
  'auth',
@@ -37,5 +38,7 @@ __all__ = ['agents',
37
38
  'qna',
38
39
  'streaming',
39
40
  'utils',
41
+ 'vertex',
40
42
  'logging']
41
43
 
44
+
@@ -52,7 +52,7 @@ 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")
55
+ override_endpoint = kwargs.get("override_endpoint")
56
56
  if override_endpoint:
57
57
  log.info(f"Overriding endpoint with {override_endpoint}")
58
58
 
@@ -37,6 +37,10 @@ except ImportError as err:
37
37
 
38
38
  def register_qna_routes(app, stream_interpreter, vac_interpreter):
39
39
 
40
+ @app.route("/")
41
+ def home():
42
+ return jsonify("OK")
43
+
40
44
  @app.route('/vac/streaming/<vector_name>', methods=['POST'])
41
45
  @observe()
42
46
  def stream_qa(vector_name):
@@ -1,38 +1,46 @@
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
1
+ from ..agents import send_to_qa
2
+ from ..streaming import generate_proxy_stream
3
+ from ..utils.user_ids import generate_user_id
4
+ from ..utils.config import load_config_key
4
5
 
5
- from .run_proxy import load_proxies, start_proxy
6
+ from .run_proxy import clean_proxy_list, start_proxy
6
7
 
7
8
  import uuid
8
9
 
9
- def get_service_url(service_name):
10
- proxies = load_proxies()
10
+ from rich import print
11
+ from .sun_rich import console
12
+
13
+ from rich.prompt import Prompt
14
+ from rich.panel import Panel
15
+ from rich.text import Text
16
+
17
+
18
+ def get_service_url(service_name, project, region):
19
+ proxies = clean_proxy_list()
11
20
  if service_name in proxies:
12
21
  port = proxies[service_name]['port']
13
22
  return f"http://127.0.0.1:{port}"
14
23
  else:
15
24
  print(f"No proxy found running for service: {service_name} - attempting to connect")
16
- return start_proxy(service_name)
25
+ return start_proxy(service_name, region, project)
17
26
 
18
- async def stream_chat_session(service_name, chat_history):
27
+ def stream_chat_session(service_name, project, region):
19
28
 
20
- service_url = get_service_url(service_name)
21
- user_id = generate_uuid_from_gcloud_user()
29
+ service_url = get_service_url(service_name, project, region)
30
+ user_id = generate_user_id()
31
+ chat_history = []
22
32
  while True:
23
33
  session_id = str(uuid.uuid4())
24
- user_input = input("You: ")
34
+ user_input = Prompt.ask("[bold cyan]You[/bold cyan]")
25
35
  if user_input.lower() in ["exit", "quit"]:
26
- print("Exiting chat session.")
36
+ console.print("[bold red]Exiting chat session.[/bold red]")
27
37
  break
28
38
 
29
- chat_history.append({"role": "user", "content": user_input})
30
-
31
-
39
+ chat_history.append({"role": "Human", "content": user_input})
32
40
 
33
- async def stream_response():
34
- generate = await generate_proxy_stream_async(
35
- send_to_qa_async,
41
+ def stream_response():
42
+ generate = generate_proxy_stream(
43
+ send_to_qa,
36
44
  user_input,
37
45
  vector_name=service_name,
38
46
  chat_history=chat_history,
@@ -56,28 +64,40 @@ async def stream_chat_session(service_name, chat_history):
56
64
  message_source="cli",
57
65
  override_endpoint=service_url
58
66
  )
59
- async for part in generate():
67
+ for part in generate():
60
68
  yield part
61
69
 
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)
70
+ response_started = False
71
+ vac_response = ""
67
72
 
68
- chat_history.append({"role": "assistant", "content": token})
69
- print() # For new line after streaming ends
73
+ # point or star?
74
+ with console.status("[bold orange]Thinking...[/bold orange]", spinner="star") as status:
75
+ for token in stream_response():
76
+ if not response_started:
77
+ status.stop()
78
+ console.print(f"[bold yellow]{service_name}:[/bold yellow] ", end='')
79
+ response_started = True
70
80
 
71
- async def headless_mode(service_name, user_input, chat_history=None):
81
+ if isinstance(token, bytes):
82
+ token = token.decode('utf-8')
83
+ console.print(token, end='')
84
+ vac_response += token
85
+
86
+ chat_history.append({"role": "AI", "content": vac_response})
87
+ response_started = False
88
+ console.print()
89
+ console.rule()
90
+
91
+ def headless_mode(service_name, user_input, project, region, chat_history=None):
72
92
  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()
93
+ chat_history.append({"role": "Human", "content": user_input})
94
+ service_url = get_service_url(project, region)
95
+ user_id = generate_user_id()
76
96
  session_id = str(uuid.uuid4())
77
97
 
78
- async def stream_response():
79
- generate = await generate_proxy_stream_async(
80
- send_to_qa_async,
98
+ def stream_response():
99
+ generate = generate_proxy_stream(
100
+ send_to_qa,
81
101
  user_input,
82
102
  vector_name=service_name,
83
103
  chat_history=chat_history,
@@ -101,30 +121,41 @@ async def headless_mode(service_name, user_input, chat_history=None):
101
121
  message_source="cli",
102
122
  override_endpoint=service_url
103
123
  )
104
- async for part in generate():
124
+ for part in generate():
105
125
  yield part
106
126
 
107
- print("Assistant: ", end='', flush=True)
108
- async for token in stream_response():
127
+ print(f"VAC {service_name}: ", end='', flush=True)
128
+ for token in stream_response():
109
129
  if isinstance(token, bytes):
110
130
  token = token.decode('utf-8')
111
131
  print(token, end='', flush=True)
112
132
 
113
- chat_history.append({"role": "assistant", "content": token})
133
+ chat_history.append({"role": "AI", "content": token})
114
134
  print() # For new line after streaming ends
115
135
 
136
+ return chat_history
137
+
116
138
 
117
139
  def vac_command(args):
118
140
  try:
119
- service_url = get_service_url(args.service_name)
141
+ service_url = get_service_url(args.service_name, args.project, args.region)
120
142
  except ValueError as e:
121
- print(e)
143
+ console.print(f"[bold red]ERROR: Could not start {args.service_name} proxy URL: {str(e)}[/bold red]")
122
144
  return
123
-
145
+
146
+ display_name = load_config_key("display_name", vector_name=args.service_name, kind="vacConfig")
147
+ description = load_config_key("description", vector_name=args.service_name, kind="vacConfig")
148
+
149
+ print(
150
+ Panel(description or "Starting VAC chat session",
151
+ title=display_name or args.service_name,
152
+ subtitle=service_url)
153
+ )
154
+
124
155
  if args.headless:
125
- headless_mode(service_url, args.user_input, args.chat_history)
156
+ headless_mode(args.service_name, args.user_input, args.project, args.region, args.chat_history)
126
157
  else:
127
- stream_chat_session(service_url)
158
+ stream_chat_session(args.service_name, args.project, args.region)
128
159
 
129
160
  def setup_vac_subparser(subparsers):
130
161
  """
@@ -133,9 +164,9 @@ def setup_vac_subparser(subparsers):
133
164
  Args:
134
165
  subparsers: The subparsers object from argparse.ArgumentParser().
135
166
  """
136
- vac_parser = subparsers.add_parser('vac', help='Interact with the VAC service.')
167
+ vac_parser = subparsers.add_parser('vac', help='Interact with deployed VAC services.')
137
168
  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)
169
+ vac_parser.add_argument('user_input', help='User input for the VAC service when in headless mode.', nargs='?', default=None)
139
170
  vac_parser.add_argument('--headless', action='store_true', help='Run in headless mode.')
140
171
  vac_parser.add_argument('--chat_history', help='Chat history for headless mode (as JSON string).', default=None)
141
172
  vac_parser.set_defaults(func=vac_command)
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import logging
2
3
 
3
4
  from .configs import setup_list_configs_subparser
4
5
  from .deploy import setup_deploy_subparser
@@ -6,19 +7,44 @@ from .cli_init import setup_init_subparser
6
7
  from .merge_texts import setup_merge_text_subparser
7
8
  from .run_proxy import setup_proxy_subparser
8
9
  from .chat_vac import setup_vac_subparser
10
+ from ..utils.config import load_config_key
9
11
 
12
+ from ..logging import log
13
+
14
+ from rich import print
15
+ from rich.console import Console
16
+ from rich.prompt import Prompt
17
+ from rich.spinner import Spinner
18
+ from rich.panel import Panel
19
+ from rich.text import Text
20
+ console = Console()
21
+
22
+ def load_default_gcp_config():
23
+ gcp_config = load_config_key('gcp_config', 'global', kind="vacConfig")
24
+ if gcp_config:
25
+ return gcp_config.get('project_id', ''), gcp_config.get('location', 'europe-west1')
26
+ else:
27
+ return '', 'europe-west1'
10
28
 
11
29
  def main(args=None):
30
+
31
+
12
32
  """
13
33
  Entry point for the sunholo console script. This function parses command line arguments
14
34
  and invokes the appropriate functionality based on the user input.
15
35
 
16
- Example commands:
36
+ Get started:
17
37
  ```bash
18
- sunholo deploy --config_path . --gcs_bucket your-gcs-bucket --lancedb_bucket your-lancedb-bucket
38
+ sunholo --help
19
39
  ```
20
40
  """
41
+ default_project, default_region = load_default_gcp_config()
42
+
21
43
  parser = argparse.ArgumentParser(description="sunholo CLI tool for deploying GenAI VACs")
44
+ parser.add_argument('--debug', action='store_true', help='Enable debug output')
45
+ parser.add_argument('--project', default=default_project, help='GCP project to list Cloud Run services from.')
46
+ parser.add_argument('--region', default=default_region, help='Region to list Cloud Run services from.')
47
+
22
48
  subparsers = parser.add_subparsers(title='commands',
23
49
  description='Valid commands',
24
50
  help='Commands',
@@ -40,6 +66,13 @@ def main(args=None):
40
66
 
41
67
  args = parser.parse_args(args)
42
68
 
69
+ if args.debug:
70
+ log.setLevel(logging.INFO)
71
+ logging.getLogger().setLevel(logging.INFO)
72
+ else:
73
+ log.setLevel(logging.WARNING)
74
+ logging.getLogger().setLevel(logging.WARNING)
75
+
43
76
  if hasattr(args, 'func'):
44
77
  args.func(args)
45
78
  else:
@@ -3,6 +3,10 @@ import os
3
3
  import signal
4
4
  import json
5
5
 
6
+ from .sun_rich import console
7
+ from rich.table import Table
8
+ from rich import print
9
+
6
10
  PROXY_TRACKER_FILE = '.vac_proxy_tracker.json'
7
11
  DEFAULT_PORT = 8080
8
12
 
@@ -46,13 +50,13 @@ def check_gcloud():
46
50
  """
47
51
  try:
48
52
  # Check if gcloud is installed
49
- result = subprocess.run(["gcloud", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53
+ result = subprocess.run(["gcloud", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30)
50
54
  if result.returncode != 0:
51
55
  print("ERROR: gcloud is not installed or not found in PATH.")
52
56
  return False
53
57
 
54
58
  # Check if gcloud is authenticated
55
- result = subprocess.run(["gcloud", "auth", "list"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
59
+ result = subprocess.run(["gcloud", "auth", "list"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30)
56
60
  if result.returncode != 0 or "ACTIVE" not in result.stdout.decode():
57
61
  print("ERROR: gcloud is not authenticated. Please run 'gcloud auth login'.")
58
62
  return False
@@ -63,16 +67,35 @@ def check_gcloud():
63
67
  print(f"ERROR: An unexpected error occurred: {e}")
64
68
  return False
65
69
 
70
+ def is_process_running(pid):
71
+ try:
72
+ os.kill(pid, 0)
73
+ return True
74
+ except OSError:
75
+ print("WARNING: VAC Proxy lost connection")
76
+ return False
77
+
66
78
  def load_proxies():
67
79
  if os.path.exists(PROXY_TRACKER_FILE):
68
80
  with open(PROXY_TRACKER_FILE, 'r') as file:
69
81
  return json.load(file)
70
82
  return {}
71
83
 
84
+ def clean_proxy_list():
85
+ proxies = load_proxies()
86
+ updated_proxies = {k: v for k, v in proxies.items() if is_process_running(v["pid"])}
87
+ if len(proxies) != len(updated_proxies):
88
+ save_proxies(updated_proxies)
89
+
90
+ return updated_proxies
91
+
72
92
  def save_proxies(proxies):
73
93
  with open(PROXY_TRACKER_FILE, 'w') as file:
74
94
  json.dump(proxies, file, indent=4)
75
95
 
96
+
97
+
98
+
76
99
  def start_proxy(service_name, region, project, port=None):
77
100
  """
78
101
  Starts the gcloud proxy to the Cloud Run service and stores the PID.
@@ -83,7 +106,7 @@ def start_proxy(service_name, region, project, port=None):
83
106
  project (str): GCP project of the Cloud Run service.
84
107
  port (int, optional): Port to run the proxy on. If not provided, auto-assigns the next available port.
85
108
  """
86
- proxies = load_proxies()
109
+ proxies = clean_proxy_list()
87
110
 
88
111
  if service_name in proxies:
89
112
  print(f"Proxy for service {service_name} is already running on port {proxies[service_name]['port']}.")
@@ -109,7 +132,7 @@ def start_proxy(service_name, region, project, port=None):
109
132
 
110
133
  print(f"Proxy for {service_name} setup complete on port {port}")
111
134
  list_proxies()
112
-
135
+
113
136
  return f"http://127.0.0.1:{port}"
114
137
 
115
138
 
@@ -120,7 +143,7 @@ def stop_proxy(service_name):
120
143
  Args:
121
144
  service_name (str): Name of the Cloud Run service.
122
145
  """
123
- proxies = load_proxies()
146
+ proxies = clean_proxy_list()
124
147
 
125
148
  if service_name not in proxies:
126
149
  print(f"No proxy found for service: {service_name}")
@@ -139,20 +162,99 @@ def stop_proxy(service_name):
139
162
 
140
163
  list_proxies()
141
164
 
165
+ def stop_all_proxies():
166
+ """
167
+ Stops all running gcloud proxies.
168
+ """
169
+ proxies = clean_proxy_list()
170
+
171
+ for service_name, info in proxies.items():
172
+ pid = info["pid"]
173
+ try:
174
+ os.kill(pid, signal.SIGTERM)
175
+ print(f"Proxy for {service_name} stopped.")
176
+ except ProcessLookupError:
177
+ print(f"No process found with PID: {pid}")
178
+ except Exception as e:
179
+ print(f"Error stopping proxy for {service_name}: {e}")
180
+
181
+ save_proxies({})
182
+
183
+ list_proxies()
184
+
185
+ def list_cloud_run_services(project, region):
186
+ """
187
+ Lists all Cloud Run services the user has access to in a specific project and region.
188
+
189
+ Args:
190
+ project (str): The GCP project ID.
191
+ region (str): The region of the Cloud Run services.
192
+ """
193
+
194
+ # point or star?
195
+ with console.status("[bold orange]Listing Cloud Run Services[/bold orange]", spinner="star") as status:
196
+ try:
197
+ result = subprocess.run(
198
+ ["gcloud", "run", "services", "list", "--project", project, "--region", region, "--format=json"],
199
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30
200
+ )
201
+ if result.returncode != 0:
202
+ status.stop()
203
+ console.print(f"[bold red]ERROR: Unable to list Cloud Run services: {result.stderr.decode()}[/bold red]")
204
+ return
205
+
206
+ services = json.loads(result.stdout.decode())
207
+ if not services:
208
+ status.stop()
209
+ console.print("[bold red]No Cloud Run services found.[/bold red]")
210
+ return
211
+
212
+ proxies = clean_proxy_list()
213
+ status.stop()
214
+
215
+ table = Table(title="VAC Cloud Run Services")
216
+ table.add_column("Service Name")
217
+ table.add_column("Region")
218
+ table.add_column("URL")
219
+ table.add_column("Proxied")
220
+ table.add_column("Port")
221
+
222
+ for service in services:
223
+ service_name = service['metadata']['name']
224
+ service_url = service['status']['url']
225
+ if service_name in proxies:
226
+ proxied = "Yes"
227
+ proxy_port = proxies[service_name]['port']
228
+ else:
229
+ proxied = "No"
230
+ proxy_port = "-"
231
+ table.add_row(service_name, region, service_url, proxied, str(proxy_port))
232
+
233
+ console.print(table)
234
+ except Exception as e:
235
+ status.stop()
236
+ console.print(f"[bold red]ERROR: An unexpected error occurred: {e}[/bold red]")
237
+
142
238
  def list_proxies():
143
239
  """
144
240
  Lists all running proxies.
145
241
  """
146
- proxies = load_proxies()
242
+ print("Listing Proxies...")
243
+ proxies = clean_proxy_list()
147
244
  if not proxies:
148
245
  print("No proxies currently running.")
149
246
  else:
150
- print("=== VAC Proxies Running ===")
247
+ table = Table(title="VAC Proxies")
248
+ table.add_column("VAC")
249
+ table.add_column("Port")
250
+ table.add_column("PID")
251
+ table.add_column("URL")
252
+
151
253
  for service_name, info in proxies.items():
152
254
  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
-
255
+ table.add_row(service_name, str(info['port']), str(info['pid']), url)
256
+
257
+ console.print(table)
156
258
 
157
259
  def setup_proxy_subparser(subparsers):
158
260
  """
@@ -161,13 +263,12 @@ def setup_proxy_subparser(subparsers):
161
263
  Args:
162
264
  subparsers: The subparsers object from argparse.ArgumentParser().
163
265
  """
266
+
164
267
  proxy_parser = subparsers.add_parser('proxy', help='Set up or stop a proxy to the Cloud Run service.')
165
268
  proxy_subparsers = proxy_parser.add_subparsers(dest='proxy_command', required=True)
166
269
 
167
270
  start_parser = proxy_subparsers.add_parser('start', help='Start the proxy to the Cloud Run service.')
168
271
  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
272
  start_parser.add_argument('--port', type=int, help='Port to run the proxy on. Auto-assigns if not provided.')
172
273
  start_parser.set_defaults(func=lambda args: start_proxy(args.service_name, args.region, args.project, args.port))
173
274
 
@@ -178,4 +279,10 @@ def setup_proxy_subparser(subparsers):
178
279
  list_parser = proxy_subparsers.add_parser('list', help='List all running proxies.')
179
280
  list_parser.set_defaults(func=lambda args: list_proxies())
180
281
 
282
+ stop_all_parser = proxy_subparsers.add_parser('stop-all', help='Stop all running proxies.')
283
+ stop_all_parser.set_defaults(func=lambda args: stop_all_proxies())
284
+
285
+ list_services_parser = proxy_subparsers.add_parser('list-vacs', help='List all Cloud Run VAC services.')
286
+ list_services_parser.set_defaults(func=lambda args: list_cloud_run_services(args.project, args.region))
287
+
181
288
 
@@ -0,0 +1,3 @@
1
+ from rich.console import Console
2
+
3
+ console = Console()
@@ -48,7 +48,8 @@ def pick_retriever(vector_name, embeddings=None):
48
48
  log.info(f"Found vectorstore {vectorstore}")
49
49
  if embeddings is None:
50
50
  embeddings = get_embeddings(vector_name)
51
- vectorstore = pick_vectorstore(vectorstore, vector_name=vector_name, embeddings=embeddings)
51
+ read_only = value.get('readonly')
52
+ vectorstore = pick_vectorstore(vectorstore, vector_name=vector_name, embeddings=embeddings, read_only=read_only)
52
53
  k_override = value.get('k', 3)
53
54
  vs_retriever = vectorstore.as_retriever(search_kwargs=dict(k=k_override))
54
55
  retriever_list.append(vs_retriever)