sunholo 0.60.2__tar.gz → 0.60.4__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 (111) hide show
  1. {sunholo-0.60.2 → sunholo-0.60.4}/PKG-INFO +10 -6
  2. {sunholo-0.60.2 → sunholo-0.60.4}/setup.py +11 -5
  3. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/__init__.py +3 -0
  4. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/chat_vac.py +74 -35
  5. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/cli.py +21 -5
  6. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/run_proxy.py +104 -12
  7. sunholo-0.60.4/sunholo/cli/sun_rich.py +3 -0
  8. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/alloydb.py +50 -0
  9. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/gcs/download_url.py +1 -1
  10. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/logging.py +0 -3
  11. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/streaming/langserve.py +2 -2
  12. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/config.py +15 -10
  13. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo.egg-info/PKG-INFO +10 -6
  14. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo.egg-info/SOURCES.txt +1 -0
  15. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo.egg-info/requires.txt +9 -4
  16. {sunholo-0.60.2 → sunholo-0.60.4}/LICENSE.txt +0 -0
  17. {sunholo-0.60.2 → sunholo-0.60.4}/MANIFEST.in +0 -0
  18. {sunholo-0.60.2 → sunholo-0.60.4}/README.md +0 -0
  19. {sunholo-0.60.2 → sunholo-0.60.4}/setup.cfg +0 -0
  20. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/__init__.py +0 -0
  21. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/chat_history.py +0 -0
  22. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/dispatch_to_qa.py +0 -0
  23. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/fastapi/__init__.py +0 -0
  24. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/fastapi/base.py +0 -0
  25. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/fastapi/qna_routes.py +0 -0
  26. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/flask/__init__.py +0 -0
  27. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/flask/base.py +0 -0
  28. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/flask/qna_routes.py +0 -0
  29. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/langserve.py +0 -0
  30. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/pubsub.py +0 -0
  31. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/route.py +0 -0
  32. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/special_commands.py +0 -0
  33. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/agents/test_chat_history.py +0 -0
  34. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/archive/__init__.py +0 -0
  35. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/archive/archive.py +0 -0
  36. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/auth/__init__.py +0 -0
  37. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/auth/run.py +0 -0
  38. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/bots/__init__.py +0 -0
  39. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/bots/discord.py +0 -0
  40. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/bots/github_webhook.py +0 -0
  41. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/bots/webapp.py +0 -0
  42. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/__init__.py +0 -0
  43. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
  44. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/doc_handling.py +0 -0
  45. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/images.py +0 -0
  46. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/loaders.py +0 -0
  47. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/message_data.py +0 -0
  48. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/pdfs.py +0 -0
  49. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/publish.py +0 -0
  50. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/chunker/splitter.py +0 -0
  51. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/__init__.py +0 -0
  52. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/cli_init.py +0 -0
  53. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/configs.py +0 -0
  54. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/deploy.py +0 -0
  55. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/cli/merge_texts.py +0 -0
  56. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/components/__init__.py +0 -0
  57. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/components/llm.py +0 -0
  58. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/components/prompt.py +0 -0
  59. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/components/retriever.py +0 -0
  60. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/components/vectorstore.py +0 -0
  61. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/__init__.py +0 -0
  62. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/database.py +0 -0
  63. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/lancedb.py +0 -0
  64. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/sql/sb/create_function.sql +0 -0
  65. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  66. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/sql/sb/create_table.sql +0 -0
  67. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  68. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/sql/sb/return_sources.sql +0 -0
  69. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/sql/sb/setup.sql +0 -0
  70. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/static_dbs.py +0 -0
  71. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/database/uuid.py +0 -0
  72. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/embedder/__init__.py +0 -0
  73. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/embedder/embed_chunk.py +0 -0
  74. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/gcs/__init__.py +0 -0
  75. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/gcs/add_file.py +0 -0
  76. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/gcs/metadata.py +0 -0
  77. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/langfuse/__init__.py +0 -0
  78. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/langfuse/callback.py +0 -0
  79. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/langfuse/prompts.py +0 -0
  80. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/llamaindex/__init__.py +0 -0
  81. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/llamaindex/generate.py +0 -0
  82. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/llamaindex/import_files.py +0 -0
  83. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/lookup/__init__.py +0 -0
  84. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/lookup/model_lookup.yaml +0 -0
  85. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/patches/__init__.py +0 -0
  86. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/patches/langchain/__init__.py +0 -0
  87. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/patches/langchain/lancedb.py +0 -0
  88. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/patches/langchain/vertexai.py +0 -0
  89. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/pubsub/__init__.py +0 -0
  90. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/pubsub/process_pubsub.py +0 -0
  91. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/pubsub/pubsub_manager.py +0 -0
  92. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/qna/__init__.py +0 -0
  93. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/qna/parsers.py +0 -0
  94. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/qna/retry.py +0 -0
  95. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/streaming/__init__.py +0 -0
  96. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/streaming/content_buffer.py +0 -0
  97. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/streaming/streaming.py +0 -0
  98. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/summarise/__init__.py +0 -0
  99. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/summarise/summarise.py +0 -0
  100. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/__init__.py +0 -0
  101. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/big_context.py +0 -0
  102. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/config_schema.py +0 -0
  103. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/gcp.py +0 -0
  104. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/parsers.py +0 -0
  105. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/utils/user_ids.py +0 -0
  106. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/vertex/__init__.py +0 -0
  107. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo/vertex/init_vertex.py +0 -0
  108. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo.egg-info/dependency_links.txt +0 -0
  109. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo.egg-info/entry_points.txt +0 -0
  110. {sunholo-0.60.2 → sunholo-0.60.4}/sunholo.egg-info/top_level.txt +0 -0
  111. {sunholo-0.60.2 → sunholo-0.60.4}/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.2
3
+ Version: 0.60.4
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.2.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.60.4.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.2'
4
+ version = '0.60.4'
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
+
@@ -1,34 +1,44 @@
1
1
  from ..agents import send_to_qa
2
2
  from ..streaming import generate_proxy_stream
3
3
  from ..utils.user_ids import generate_user_id
4
+ from ..utils.config import load_config_key
4
5
 
5
- from .run_proxy import clean_proxy_list, start_proxy
6
+ from .run_proxy import clean_proxy_list, start_proxy, stop_proxy
6
7
 
7
8
  import uuid
8
9
 
9
- def get_service_url(service_name):
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(vac_name, project, region):
19
+ agent_name = load_config_key("agent", vac_name, kind="vacConfig")
10
20
  proxies = clean_proxy_list()
11
- if service_name in proxies:
12
- port = proxies[service_name]['port']
13
- return f"http://127.0.0.1:{port}"
21
+ if agent_name in proxies:
22
+ port = proxies[agent_name]['port']
23
+ url = f"http://127.0.0.1:{port}"
14
24
  else:
15
- print(f"No proxy found running for service: {service_name} - attempting to connect")
16
- return start_proxy(service_name)
25
+ print(f"No proxy found running for service: {agent_name} required for {vac_name} - attempting to connect")
26
+ url = start_proxy(agent_name, region, project)
17
27
 
18
- def stream_chat_session(service_name):
28
+ return url
19
29
 
20
- service_url = get_service_url(service_name)
30
+ def stream_chat_session(service_name, project, region):
31
+
32
+ service_url = get_service_url(service_name, project, region)
21
33
  user_id = generate_user_id()
22
34
  chat_history = []
23
35
  while True:
24
36
  session_id = str(uuid.uuid4())
25
- user_input = input("You: ")
37
+ user_input = Prompt.ask("[bold cyan]You[/bold cyan]")
26
38
  if user_input.lower() in ["exit", "quit"]:
27
- print("Exiting chat session.")
39
+ console.print("[bold red]Exiting chat session.[/bold red]")
28
40
  break
29
41
 
30
- chat_history.append({"role": "Human", "content": user_input})
31
-
32
42
  def stream_response():
33
43
  generate = generate_proxy_stream(
34
44
  send_to_qa,
@@ -60,24 +70,30 @@ def stream_chat_session(service_name):
60
70
 
61
71
  response_started = False
62
72
  vac_response = ""
63
- for token in stream_response():
64
- if not response_started:
65
- print(f"VAC {service_name}: ", end='', flush=True)
66
- response_started = True
67
-
68
- if isinstance(token, bytes):
69
- token = token.decode('utf-8')
70
- print(token, end='', flush=True)
71
- vac_response += token
72
73
 
73
- chat_history.append({"role": "AI", "content": vac_response})
74
+ # point or star?
75
+ with console.status("[bold orange]Thinking...[/bold orange]", spinner="star") as status:
76
+ for token in stream_response():
77
+ if not response_started:
78
+ status.stop()
79
+ console.print(f"[bold yellow]{service_name}:[/bold yellow] ", end='')
80
+ response_started = True
81
+
82
+ if isinstance(token, bytes):
83
+ token = token.decode('utf-8')
84
+ console.print(token, end='')
85
+ vac_response += token
86
+
87
+ chat_history.append({"name": "Human", "content": user_input})
88
+ chat_history.append({"name": "AI", "content": vac_response})
74
89
  response_started = False
75
- print() # For new line after streaming ends
90
+ console.print()
91
+ console.rule()
76
92
 
77
- def headless_mode(service_name, user_input, chat_history=None):
93
+ def headless_mode(service_name, user_input, project, region, chat_history=None):
78
94
  chat_history = chat_history or []
79
- chat_history.append({"role": "Human", "content": user_input})
80
- service_url = get_service_url(service_name)
95
+
96
+ service_url = get_service_url(service_name, project, region)
81
97
  user_id = generate_user_id()
82
98
  session_id = str(uuid.uuid4())
83
99
 
@@ -110,13 +126,17 @@ def headless_mode(service_name, user_input, chat_history=None):
110
126
  for part in generate():
111
127
  yield part
112
128
 
113
- print(f"VAC {service_name}: ", end='', flush=True)
129
+ vac_response = ""
130
+
114
131
  for token in stream_response():
115
132
  if isinstance(token, bytes):
116
133
  token = token.decode('utf-8')
117
134
  print(token, end='', flush=True)
135
+ vac_response += token
118
136
 
119
- chat_history.append({"role": "AI", "content": token})
137
+ if vac_response:
138
+ chat_history.append({"name": "Human", "content": user_input})
139
+ chat_history.append({"name": "AI", "content": vac_response})
120
140
  print() # For new line after streaming ends
121
141
 
122
142
  return chat_history
@@ -124,15 +144,34 @@ def headless_mode(service_name, user_input, chat_history=None):
124
144
 
125
145
  def vac_command(args):
126
146
  try:
127
- service_url = get_service_url(args.service_name)
147
+ service_url = get_service_url(args.vac_name, args.project, args.region)
128
148
  except ValueError as e:
129
- print(f"ERROR: Could not start {args.service_name} proxy URL: {str(e)}")
149
+ console.print(f"[bold red]ERROR: Could not start {args.vac_name} proxy URL: {str(e)}[/bold red]")
130
150
  return
131
- print(f"== Starting VAC chat session with {args.service_name} via proxy URL: {service_url}")
151
+
152
+ agent_name = load_config_key("agent", args.vac_name, kind="vacConfig")
153
+
132
154
  if args.headless:
133
- headless_mode(args.service_name, args.user_input, args.chat_history)
155
+ headless_mode(args.vac_name, args.user_input, args.project, args.region, args.chat_history)
156
+ stop_proxy(agent_name)
134
157
  else:
135
- stream_chat_session(args.service_name)
158
+ display_name = load_config_key("display_name", vector_name=args.vac_name, kind="vacConfig")
159
+ description = load_config_key("description", vector_name=args.vac_name, kind="vacConfig")
160
+
161
+ if agent_name == "langserve":
162
+ subtitle = f"{service_url}/{args.vac_name}/playground/"
163
+ else:
164
+ subtitle = f"{agent_name} - {service_url}/vac/{args.vac_name}"
165
+
166
+ print(
167
+ Panel(description or "Starting VAC chat session",
168
+ title=display_name or args.vac_name,
169
+ subtitle=subtitle)
170
+ )
171
+
172
+ stream_chat_session(args.vac_name, args.project, args.region)
173
+ stop_proxy(agent_name)
174
+
136
175
 
137
176
  def setup_vac_subparser(subparsers):
138
177
  """
@@ -142,7 +181,7 @@ def setup_vac_subparser(subparsers):
142
181
  subparsers: The subparsers object from argparse.ArgumentParser().
143
182
  """
144
183
  vac_parser = subparsers.add_parser('vac', help='Interact with deployed VAC services.')
145
- vac_parser.add_argument('service_name', help='Name of the VAC service.')
184
+ vac_parser.add_argument('vac_name', help='Name of the VAC service.')
146
185
  vac_parser.add_argument('user_input', help='User input for the VAC service when in headless mode.', nargs='?', default=None)
147
186
  vac_parser.add_argument('--headless', action='store_true', help='Run in headless mode.')
148
187
  vac_parser.add_argument('--chat_history', help='Chat history for headless mode (as JSON string).', default=None)
@@ -7,22 +7,38 @@ from .cli_init import setup_init_subparser
7
7
  from .merge_texts import setup_merge_text_subparser
8
8
  from .run_proxy import setup_proxy_subparser
9
9
  from .chat_vac import setup_vac_subparser
10
+ from ..utils.config import load_config_key
10
11
 
11
12
  from ..logging import log
12
13
 
14
+
15
+
16
+ def load_default_gcp_config():
17
+ gcp_config = load_config_key('gcp_config', 'global', kind="vacConfig")
18
+ if gcp_config:
19
+ return gcp_config.get('project_id', ''), gcp_config.get('location', 'europe-west1')
20
+ else:
21
+ return '', 'europe-west1'
22
+
13
23
  def main(args=None):
24
+
25
+
14
26
  """
15
27
  Entry point for the sunholo console script. This function parses command line arguments
16
28
  and invokes the appropriate functionality based on the user input.
17
29
 
18
- Example commands:
30
+ Get started:
19
31
  ```bash
20
- sunholo deploy --config_path . --gcs_bucket your-gcs-bucket --lancedb_bucket your-lancedb-bucket
32
+ sunholo --help
21
33
  ```
22
34
  """
35
+ default_project, default_region = load_default_gcp_config()
36
+
23
37
  parser = argparse.ArgumentParser(description="sunholo CLI tool for deploying GenAI VACs")
24
38
  parser.add_argument('--debug', action='store_true', help='Enable debug output')
25
-
39
+ parser.add_argument('--project', default=default_project, help='GCP project to list Cloud Run services from.')
40
+ parser.add_argument('--region', default=default_region, help='Region to list Cloud Run services from.')
41
+
26
42
  subparsers = parser.add_subparsers(title='commands',
27
43
  description='Valid commands',
28
44
  help='Commands',
@@ -45,8 +61,8 @@ def main(args=None):
45
61
  args = parser.parse_args(args)
46
62
 
47
63
  if args.debug:
48
- log.setLevel(logging.INFO)
49
- logging.getLogger().setLevel(logging.INFO)
64
+ log.setLevel(logging.DEBUG)
65
+ logging.getLogger().setLevel(logging.DEBUG)
50
66
  else:
51
67
  log.setLevel(logging.WARNING)
52
68
  logging.getLogger().setLevel(logging.WARNING)
@@ -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
- print("ERROR: gcloud is not installed or not found in PATH.")
55
+ print("[bold red]ERROR: gcloud is not installed or not found in PATH.[/bold red]")
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
@@ -89,6 +93,9 @@ def save_proxies(proxies):
89
93
  with open(PROXY_TRACKER_FILE, 'w') as file:
90
94
  json.dump(proxies, file, indent=4)
91
95
 
96
+
97
+
98
+
92
99
  def start_proxy(service_name, region, project, port=None):
93
100
  """
94
101
  Starts the gcloud proxy to the Cloud Run service and stores the PID.
@@ -155,20 +162,100 @@ def stop_proxy(service_name):
155
162
 
156
163
  list_proxies()
157
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
+
158
238
  def list_proxies():
159
239
  """
160
240
  Lists all running proxies.
161
241
  """
162
- proxies = clean_proxy_list()
242
+ with console.status("[bold orange]Listing Proxies[/bold orange]", spinner="star"):
243
+ proxies = clean_proxy_list()
244
+
163
245
  if not proxies:
164
246
  print("No proxies currently running.")
165
247
  else:
166
- print("=== VAC Proxies Running ===")
248
+ table = Table(title="VAC Proxies")
249
+ table.add_column("VAC")
250
+ table.add_column("Port")
251
+ table.add_column("PID")
252
+ table.add_column("URL")
253
+
167
254
  for service_name, info in proxies.items():
168
255
  url = f"http://127.0.0.1:{info['port']}"
169
- hyperlink = create_hyperlink(url, url)
170
- print(f"- VAC: {service_name} Port: {info['port']} PID: {info['pid']} URL: {hyperlink}")
171
-
256
+ table.add_row(service_name, str(info['port']), str(info['pid']), url)
257
+
258
+ console.print(table)
172
259
 
173
260
  def setup_proxy_subparser(subparsers):
174
261
  """
@@ -177,13 +264,12 @@ def setup_proxy_subparser(subparsers):
177
264
  Args:
178
265
  subparsers: The subparsers object from argparse.ArgumentParser().
179
266
  """
180
- proxy_parser = subparsers.add_parser('proxy', help='Set up or stop a proxy to the Cloud Run service.')
267
+
268
+ proxy_parser = subparsers.add_parser('proxy', help='Set up or stop a proxy to the VAC Cloud Run services')
181
269
  proxy_subparsers = proxy_parser.add_subparsers(dest='proxy_command', required=True)
182
270
 
183
- start_parser = proxy_subparsers.add_parser('start', help='Start the proxy to the Cloud Run service.')
271
+ start_parser = proxy_subparsers.add_parser('start', help='Start the proxy to the VAC Cloud Run service')
184
272
  start_parser.add_argument('service_name', help='Name of the Cloud Run service.')
185
- start_parser.add_argument('--region', default='europe-west1', help='Region of the Cloud Run service.')
186
- start_parser.add_argument('--project', default='multivac-internal-dev', help='GCP project of the Cloud Run service.')
187
273
  start_parser.add_argument('--port', type=int, help='Port to run the proxy on. Auto-assigns if not provided.')
188
274
  start_parser.set_defaults(func=lambda args: start_proxy(args.service_name, args.region, args.project, args.port))
189
275
 
@@ -194,4 +280,10 @@ def setup_proxy_subparser(subparsers):
194
280
  list_parser = proxy_subparsers.add_parser('list', help='List all running proxies.')
195
281
  list_parser.set_defaults(func=lambda args: list_proxies())
196
282
 
283
+ stop_all_parser = proxy_subparsers.add_parser('stop-all', help='Stop all running proxies.')
284
+ stop_all_parser.set_defaults(func=lambda args: stop_all_proxies())
285
+
286
+ list_services_parser = proxy_subparsers.add_parser('list-vacs', help='List all Cloud Run VAC services.')
287
+ list_services_parser.set_defaults(func=lambda args: list_cloud_run_services(args.project, args.region))
288
+
197
289
 
@@ -0,0 +1,3 @@
1
+ from rich.console import Console
2
+
3
+ console = Console()
@@ -171,6 +171,56 @@ class AlloyDBClient:
171
171
 
172
172
  return self.execute_sql(query)
173
173
 
174
+ def create_database(self, database_name):
175
+ self.execute_sql(f'CREATE DATABASE "{database_name}"')
176
+
177
+ def fetch_owners(self):
178
+ owners = self.execute_sql('SELECT table_schema, table_name, privilege_type FROM information_schema.table_privileges')
179
+ for row in owners:
180
+ print(f"Schema: {row[0]}, Table: {row[1]}, Privilege: {row[2]}")
181
+ return owners
182
+
183
+ def create_schema(self, schema_name="public"):
184
+ self.execute_sql(f'CREATE SCHEMA IF NOT EXISTS {schema_name};')
185
+
186
+ def grant_permissions(self, schema_name, users):
187
+ for user in users:
188
+ self.execute_sql(f'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA {schema_name} TO "{user}";')
189
+ self.execute_sql(f'GRANT USAGE, CREATE ON SCHEMA {schema_name} TO "{user}";')
190
+ self.execute_sql(f'ALTER DEFAULT PRIVILEGES IN SCHEMA {schema_name} GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO "{user}";')
191
+ self.execute_sql(f'GRANT USAGE ON SCHEMA information_schema TO "{user}";')
192
+ self.execute_sql(f'GRANT SELECT ON information_schema.columns TO "{user}";')
193
+
194
+ def create_docstore_tables(self, vector_names, users):
195
+ for vector_name in vector_names:
196
+ table_name = f"{vector_name}_docstore"
197
+ sql = f'''
198
+ CREATE TABLE IF NOT EXISTS "{table_name}"
199
+ (page_content TEXT, doc_id UUID, source TEXT, images_gsurls JSONB, chunk_metadata JSONB, langchain_metadata JSONB)
200
+ '''
201
+ self.execute_sql(sql)
202
+
203
+ for user in users:
204
+ self.execute_sql(f'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE "{table_name}" TO "{user}";')
205
+
206
+ vectorstore_id = f"{vector_name}_vectorstore_1536"
207
+ sql = f'''
208
+ CREATE TABLE IF NOT EXISTS "{vectorstore_id}" (
209
+ langchain_id UUID NOT NULL,
210
+ content TEXT NOT NULL,
211
+ embedding vector NOT NULL,
212
+ source TEXT,
213
+ langchain_metadata JSONB,
214
+ docstore_doc_id UUID,
215
+ eventTime TIMESTAMPTZ
216
+ );
217
+ '''
218
+ self.execute_sql(sql)
219
+
220
+ for user in users:
221
+ self.execute_sql(f'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE {vectorstore_id} TO "{user}";')
222
+
223
+
174
224
  alloydb_table_cache = {} # Our cache, initially empty # noqa: F841
175
225
  def create_alloydb_table(vector_name, engine, type = "vectorstore", alloydb_config=None, username=None):
176
226
  global alloydb_table_cache
@@ -29,7 +29,7 @@ if is_running_on_gcp():
29
29
 
30
30
  def refresh_credentials():
31
31
  if not is_running_on_gcp():
32
- log.info("Not running on Google Cloud so no credentials available for GCS.")
32
+ log.debug("Not running on Google Cloud so no credentials available for GCS.")
33
33
  return False
34
34
  if not gcs_credentials.token or gcs_credentials.expired or not gcs_credentials.valid:
35
35
  try:
@@ -217,9 +217,6 @@ def setup_logging(logger_name=None, log_level=logging.INFO, project_id=None):
217
217
  if logger_name is None:
218
218
  logger_name = "sunholo"
219
219
 
220
- if Client and os.environ.get('GOOGLE_CLOUD_LOGGING') != "1":
221
- print("GOOGLE_CLOUD_LOGGING != 1 but authentication with Google Cloud Logging enabled - missing env var setting?")
222
-
223
220
  if not Client and os.environ.get('GOOGLE_CLOUD_LOGGING') == "1":
224
221
  print("Found GOOGLE_CLOUD_LOGGING=1 but no GCP Client available, install via `pip install sunholo[gcp]` and/or authenticate")
225
222
 
@@ -157,8 +157,8 @@ def parse_json_data(json_data: dict):
157
157
  """
158
158
  try:
159
159
  if isinstance(json_data, dict):
160
- content = json_data.get('content')
161
- if content:
160
+ content = json_data.get('content', None)
161
+ if content is not None: # content can be '' empty string
162
162
  #log.debug(f'Yield content: {content}')
163
163
  yield content
164
164
  else:
@@ -59,12 +59,12 @@ def load_all_configs():
59
59
  config_folder = os.getenv("_CONFIG_FOLDER", os.getcwd())
60
60
  config_folder = os.path.join(config_folder, "config")
61
61
 
62
- log.info(f"Loading all configs from folder: {config_folder}")
62
+ log.debug(f"Loading all configs from folder: {config_folder}")
63
63
  current_time = datetime.now()
64
64
 
65
65
  configs_by_kind = defaultdict(dict)
66
66
  for filename in os.listdir(config_folder):
67
- log.info(f"config file: {filename}")
67
+ log.debug(f"config file: {filename}")
68
68
  if filename in ["cloudbuild.yaml", "cloud_run_urls.json"]:
69
69
  # skip these
70
70
  continue
@@ -193,7 +193,7 @@ def load_config_key(key: str, vector_name: str, kind: str=None):
193
193
  configs_by_kind = load_all_configs()
194
194
 
195
195
  if kind:
196
- log.info(f"Got kind: {kind} - applying to configs")
196
+ log.debug(f"Got kind: {kind} - applying to configs")
197
197
 
198
198
  if not configs_by_kind:
199
199
  log.warning("Did not load configs via folder")
@@ -204,18 +204,23 @@ def load_config_key(key: str, vector_name: str, kind: str=None):
204
204
  else:
205
205
  config, filename = load_config(filename)
206
206
 
207
- log.info(f"Fetching {key} for {vector_name}")
207
+ log.debug(f"Fetching '{key}' for '{vector_name}'")
208
208
  apiVersion = config.get('apiVersion')
209
209
  kind = config.get('kind')
210
210
  vac = config.get('vac')
211
211
 
212
212
  if not apiVersion or not kind:
213
- log.warning("Deprecated config file, move to config with `apiVersion` and `kind` set")
214
- vac_config = config.get(vector_name)
215
- else:
216
- log.info(f"Loaded config file {kind}/{apiVersion}")
213
+ raise ValueError("Deprecated config file, move to config with `apiVersion` and `kind` set")
214
+
215
+ log.debug(f"Loaded config file {kind}/{apiVersion}")
217
216
 
218
217
  if kind == 'vacConfig':
218
+ if vector_name == 'global':
219
+ key_value = config.get(key)
220
+ log.debug(f'vac_config global value for {key}: {key_value}')
221
+
222
+ return key_value
223
+
219
224
  vac = config.get('vac')
220
225
  if not vac:
221
226
  raise ValueError("Deprecated config file, move to config with `vac:` at top level for `vector_name`")
@@ -223,7 +228,7 @@ def load_config_key(key: str, vector_name: str, kind: str=None):
223
228
  if not vac_config:
224
229
  raise ValueError(f"No config array was found for {vector_name} in {filename}")
225
230
 
226
- log.info(f'vac_config: {vac_config} for {vector_name} - fetching "{key}"')
231
+ log.debug(f'vac_config: {vac_config} for {vector_name} - fetching "{key}"')
227
232
  key_value = vac_config.get(key)
228
233
 
229
234
  return key_value
@@ -236,7 +241,7 @@ def load_config_key(key: str, vector_name: str, kind: str=None):
236
241
  if not prompt_for_vector_name:
237
242
  raise ValueError(f"Could not find prompt for vector_name {vector_name}")
238
243
 
239
- log.info(f'prompts: {prompt_for_vector_name} for {vector_name} - fetching "{key}"')
244
+ log.debug(f'prompts: {prompt_for_vector_name} for {vector_name} - fetching "{key}"')
240
245
  key_value = prompt_for_vector_name.get(key)
241
246
 
242
247
  return key_value
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.60.2
3
+ Version: 0.60.4
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.2.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.60.4.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"
@@ -50,6 +50,7 @@ sunholo/cli/configs.py
50
50
  sunholo/cli/deploy.py
51
51
  sunholo/cli/merge_texts.py
52
52
  sunholo/cli/run_proxy.py
53
+ sunholo/cli/sun_rich.py
53
54
  sunholo/components/__init__.py
54
55
  sunholo/components/llm.py
55
56
  sunholo/components/prompt.py
@@ -1,11 +1,11 @@
1
- jsonschema
1
+ google-auth
2
2
  langchain
3
3
  langchain_experimental
4
4
  langchain-community
5
- google-auth
6
5
 
7
6
  [all]
8
7
  asyncpg
8
+ fastapi
9
9
  flask
10
10
  google-auth
11
11
  google-auth-httplib2
@@ -23,6 +23,7 @@ google-generativeai
23
23
  gunicorn
24
24
  httpcore
25
25
  httpx
26
+ jsonschema
26
27
  lancedb
27
28
  langchain
28
29
  langchain_experimental
@@ -36,14 +37,18 @@ pg8000
36
37
  pgvector
37
38
  psycopg2-binary
38
39
  pypdf
39
- fastapi
40
+ python-socketio
41
+ rich
40
42
  supabase
41
43
  tiktoken
42
- python-socketio
43
44
 
44
45
  [anthropic]
45
46
  langchain-anthropic
46
47
 
48
+ [cli]
49
+ jsonschema
50
+ rich
51
+
47
52
  [database]
48
53
  asyncpg
49
54
  supabase
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes