volknode 0.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. volknode/__init__.py +0 -0
  2. volknode/main.py +214 -0
  3. volknode/src/core/__init__.py +0 -0
  4. volknode/src/core/result.py +26 -0
  5. volknode/src/core/validation.py +20 -0
  6. volknode/src/csr/__init__.py +0 -0
  7. volknode/src/csr/csr_service.py +47 -0
  8. volknode/src/csr/key_pair_generator.py +34 -0
  9. volknode/src/dependency_resolver.py +33 -0
  10. volknode/src/dependency_resolver_test.py +56 -0
  11. volknode/src/domain/__init__.py +0 -0
  12. volknode/src/domain/csr_credentials.py +7 -0
  13. volknode/src/domain/llm_model.py +12 -0
  14. volknode/src/domain/llm_node_certificate.py +6 -0
  15. volknode/src/domain/nexus_certificate.py +6 -0
  16. volknode/src/domain/running_vllm_config.py +7 -0
  17. volknode/src/http_client/.gitignore +23 -0
  18. volknode/src/http_client/README.md +124 -0
  19. volknode/src/http_client/ai_suite_client/__init__.py +8 -0
  20. volknode/src/http_client/ai_suite_client/api/__init__.py +1 -0
  21. volknode/src/http_client/ai_suite_client/api/default/__init__.py +1 -0
  22. volknode/src/http_client/ai_suite_client/api/default/change_password.py +197 -0
  23. volknode/src/http_client/ai_suite_client/api/default/converse.py +178 -0
  24. volknode/src/http_client/ai_suite_client/api/default/create_personal_access_token.py +193 -0
  25. volknode/src/http_client/ai_suite_client/api/default/create_user.py +207 -0
  26. volknode/src/http_client/ai_suite_client/api/default/delay_converse_indefinitely_for_prompt.py +127 -0
  27. volknode/src/http_client/ai_suite_client/api/default/delete_conversation.py +185 -0
  28. volknode/src/http_client/ai_suite_client/api/default/delete_user.py +189 -0
  29. volknode/src/http_client/ai_suite_client/api/default/dislike_message.py +182 -0
  30. volknode/src/http_client/ai_suite_client/api/default/edit_sent_message.py +196 -0
  31. volknode/src/http_client/ai_suite_client/api/default/get_app_context.py +150 -0
  32. volknode/src/http_client/ai_suite_client/api/default/get_conversation_by_id.py +182 -0
  33. volknode/src/http_client/ai_suite_client/api/default/get_conversations.py +190 -0
  34. volknode/src/http_client/ai_suite_client/api/default/get_my_profile.py +158 -0
  35. volknode/src/http_client/ai_suite_client/api/default/is_live.py +141 -0
  36. volknode/src/http_client/ai_suite_client/api/default/is_ready.py +141 -0
  37. volknode/src/http_client/ai_suite_client/api/default/like_message.py +182 -0
  38. volknode/src/http_client/ai_suite_client/api/default/list_personal_access_tokens.py +158 -0
  39. volknode/src/http_client/ai_suite_client/api/default/login_with_credentials.py +193 -0
  40. volknode/src/http_client/ai_suite_client/api/default/register_llm_node.py +208 -0
  41. volknode/src/http_client/ai_suite_client/api/default/rename_conversation.py +206 -0
  42. volknode/src/http_client/ai_suite_client/api/default/reset_app.py +107 -0
  43. volknode/src/http_client/ai_suite_client/api/default/retry_ai_response.py +175 -0
  44. volknode/src/http_client/ai_suite_client/api/default/revoke_personal_access_token.py +196 -0
  45. volknode/src/http_client/ai_suite_client/api/default/stop_indefinite_converse_answer_for_prompt.py +127 -0
  46. volknode/src/http_client/ai_suite_client/api/default/view_llm_nodes.py +201 -0
  47. volknode/src/http_client/ai_suite_client/api/default/view_users.py +190 -0
  48. volknode/src/http_client/ai_suite_client/client.py +271 -0
  49. volknode/src/http_client/ai_suite_client/errors.py +14 -0
  50. volknode/src/http_client/ai_suite_client/models/__init__.py +109 -0
  51. volknode/src/http_client/ai_suite_client/models/ai_assistant_started_typing_event.py +76 -0
  52. volknode/src/http_client/ai_suite_client/models/ai_retry_started_event.py +76 -0
  53. volknode/src/http_client/ai_suite_client/models/app_context_response.py +122 -0
  54. volknode/src/http_client/ai_suite_client/models/available_model.py +92 -0
  55. volknode/src/http_client/ai_suite_client/models/business_constraints.py +76 -0
  56. volknode/src/http_client/ai_suite_client/models/change_password_request.py +92 -0
  57. volknode/src/http_client/ai_suite_client/models/conversation.py +84 -0
  58. volknode/src/http_client/ai_suite_client/models/conversation_with_messages.py +110 -0
  59. volknode/src/http_client/ai_suite_client/models/conversations_response.py +118 -0
  60. volknode/src/http_client/ai_suite_client/models/converse_request.py +86 -0
  61. volknode/src/http_client/ai_suite_client/models/converse_response_server_event.py +182 -0
  62. volknode/src/http_client/ai_suite_client/models/converse_response_server_event_event_type.py +12 -0
  63. volknode/src/http_client/ai_suite_client/models/create_personal_access_token_request.py +101 -0
  64. volknode/src/http_client/ai_suite_client/models/create_user_request.py +120 -0
  65. volknode/src/http_client/ai_suite_client/models/delay_converse_indefinitely_for_prompt_request.py +76 -0
  66. volknode/src/http_client/ai_suite_client/models/edit_sent_message_request.py +76 -0
  67. volknode/src/http_client/ai_suite_client/models/edit_sent_message_response_server_event.py +166 -0
  68. volknode/src/http_client/ai_suite_client/models/edit_sent_message_response_server_event_event_type.py +11 -0
  69. volknode/src/http_client/ai_suite_client/models/error.py +101 -0
  70. volknode/src/http_client/ai_suite_client/models/error_event.py +88 -0
  71. volknode/src/http_client/ai_suite_client/models/list_personal_access_tokens_response.py +94 -0
  72. volknode/src/http_client/ai_suite_client/models/logged_in_user.py +100 -0
  73. volknode/src/http_client/ai_suite_client/models/login_with_credentials_request.py +84 -0
  74. volknode/src/http_client/ai_suite_client/models/message.py +120 -0
  75. volknode/src/http_client/ai_suite_client/models/message_author.py +8 -0
  76. volknode/src/http_client/ai_suite_client/models/message_edited_event.py +76 -0
  77. volknode/src/http_client/ai_suite_client/models/my_profile_response.py +122 -0
  78. volknode/src/http_client/ai_suite_client/models/new_conversation_started_event.py +76 -0
  79. volknode/src/http_client/ai_suite_client/models/no_available_models_event.py +84 -0
  80. volknode/src/http_client/ai_suite_client/models/pagination.py +84 -0
  81. volknode/src/http_client/ai_suite_client/models/permissions.py +92 -0
  82. volknode/src/http_client/ai_suite_client/models/personal_access_token_created_successfully.py +92 -0
  83. volknode/src/http_client/ai_suite_client/models/personal_access_token_list_item.py +120 -0
  84. volknode/src/http_client/ai_suite_client/models/prompted_message_saved_event.py +76 -0
  85. volknode/src/http_client/ai_suite_client/models/register_llm_node_request.py +124 -0
  86. volknode/src/http_client/ai_suite_client/models/register_llm_node_response.py +100 -0
  87. volknode/src/http_client/ai_suite_client/models/rename_conversation_request.py +76 -0
  88. volknode/src/http_client/ai_suite_client/models/reply_chunk.py +100 -0
  89. volknode/src/http_client/ai_suite_client/models/reply_chunk_content.py +88 -0
  90. volknode/src/http_client/ai_suite_client/models/reply_chunk_content_content_type.py +7 -0
  91. volknode/src/http_client/ai_suite_client/models/retry_ai_response_response_server_event.py +166 -0
  92. volknode/src/http_client/ai_suite_client/models/retry_ai_response_response_server_event_event_type.py +11 -0
  93. volknode/src/http_client/ai_suite_client/models/stop_indefinite_converse_answer_for_prompt_request.py +76 -0
  94. volknode/src/http_client/ai_suite_client/models/successful_login_response.py +84 -0
  95. volknode/src/http_client/ai_suite_client/models/user_created_successfully.py +108 -0
  96. volknode/src/http_client/ai_suite_client/models/user_resource_operations.py +84 -0
  97. volknode/src/http_client/ai_suite_client/models/user_role.py +88 -0
  98. volknode/src/http_client/ai_suite_client/models/user_role_name.py +8 -0
  99. volknode/src/http_client/ai_suite_client/models/view_llm_nodes_response.py +108 -0
  100. volknode/src/http_client/ai_suite_client/models/view_one_llm_node.py +100 -0
  101. volknode/src/http_client/ai_suite_client/models/view_one_user.py +132 -0
  102. volknode/src/http_client/ai_suite_client/models/view_users_response.py +108 -0
  103. volknode/src/http_client/ai_suite_client/py.typed +1 -0
  104. volknode/src/http_client/ai_suite_client/types.py +53 -0
  105. volknode/src/http_client/pyproject.toml +26 -0
  106. volknode/src/llm/__init__.py +16 -0
  107. volknode/src/llm/allowed_models.py +35 -0
  108. volknode/src/llm/fake_llm_runner.py +32 -0
  109. volknode/src/llm/fake_vllm_server.py +154 -0
  110. volknode/src/llm/llm_integration_test.py +331 -0
  111. volknode/src/llm/llm_runner.py +15 -0
  112. volknode/src/llm/pulled_model_info.py +9 -0
  113. volknode/src/llm/real_llm_runner.py +84 -0
  114. volknode/src/mtls/__init__.py +0 -0
  115. volknode/src/mtls/mtls_client.py +37 -0
  116. volknode/src/mtls/mtls_reverse_proxy.py +76 -0
  117. volknode/src/nexus/__init__.py +0 -0
  118. volknode/src/nexus/create_personal_access_token_test.py +77 -0
  119. volknode/src/nexus/fake_certificate_authority.py +94 -0
  120. volknode/src/nexus/fake_nexus.py +105 -0
  121. volknode/src/nexus/login_integration_test.py +67 -0
  122. volknode/src/nexus/nexus.py +22 -0
  123. volknode/src/nexus/real_nexus.py +48 -0
  124. volknode/src/nexus/register_llm_node_test.py +420 -0
  125. volknode/src/repository/__init__.py +7 -0
  126. volknode/src/repository/csr_credentials_repository.py +44 -0
  127. volknode/src/repository/llm_model_repository.py +70 -0
  128. volknode/src/repository/llm_node_certificate_repository.py +43 -0
  129. volknode/src/repository/nexus_certificate_repository.py +43 -0
  130. volknode/src/repository/running_vllm_config_repository.py +44 -0
  131. volknode/src/usecases/pull_model/__init__.py +0 -0
  132. volknode/src/usecases/pull_model/input.py +21 -0
  133. volknode/src/usecases/pull_model/interactor.py +20 -0
  134. volknode/src/usecases/pull_model/interactor_test.py +77 -0
  135. volknode/src/usecases/register_llm_node/__init__.py +0 -0
  136. volknode/src/usecases/register_llm_node/input.py +52 -0
  137. volknode/src/usecases/register_llm_node/interactor.py +62 -0
  138. volknode/src/usecases/register_llm_node/interactor_test.py +285 -0
  139. volknode/src/usecases/run_llm_node/__init__.py +0 -0
  140. volknode/src/usecases/run_llm_node/input.py +22 -0
  141. volknode/src/usecases/run_llm_node/interactor.py +33 -0
  142. volknode/src/usecases/run_llm_node/interactor_test.py +119 -0
  143. volknode/src/usecases/serve_llm/__init__.py +1 -0
  144. volknode/src/usecases/serve_llm/input.py +38 -0
  145. volknode/src/usecases/serve_llm/input_test.py +42 -0
  146. volknode/src/usecases/serve_llm/interactor.py +45 -0
  147. volknode/src/usecases/serve_llm/interactor_test.py +174 -0
  148. volknode-0.2.5.dist-info/METADATA +11 -0
  149. volknode-0.2.5.dist-info/RECORD +151 -0
  150. volknode-0.2.5.dist-info/WHEEL +4 -0
  151. volknode-0.2.5.dist-info/entry_points.txt +2 -0
volknode/__init__.py ADDED
File without changes
volknode/main.py ADDED
@@ -0,0 +1,214 @@
1
+ import os
2
+ import pwd
3
+ from importlib.metadata import version
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from volknode.src.dependency_resolver import DependencyResolver
9
+ from volknode.src.repository import CsrCredentialsRepository, LlmModelRepository, LlmNodeCertificateRepository, NexusCertificateRepository, RunningVllmConfigRepository
10
+ from volknode.src.usecases.pull_model.input import PullModelInput
11
+ from volknode.src.usecases.pull_model.interactor import PullModelInteractor
12
+ from volknode.src.usecases.register_llm_node.input import RegisterLlmNodeInput
13
+ from volknode.src.usecases.register_llm_node.interactor import RegisterLlmNodeInteractor
14
+ from volknode.src.usecases.run_llm_node.input import RunLlmNodeInput
15
+ from volknode.src.usecases.run_llm_node.interactor import RunLlmNodeInteractor
16
+ from volknode.src.usecases.serve_llm.input import ServeLlmInput
17
+ from volknode.src.usecases.serve_llm.interactor import ServeLlmInteractor
18
+
19
+
20
+ def get_user_home() -> Path:
21
+ """Get actual user's home directory, ignoring HOME env var."""
22
+ return Path(pwd.getpwuid(os.getuid()).pw_dir)
23
+
24
+ def _get_db_path() -> Path:
25
+ db_dir = get_user_home() / ".volknode"
26
+ db_dir.mkdir(parents=True, exist_ok=True)
27
+ return db_dir / "volknode.db"
28
+
29
+
30
+ app = typer.Typer()
31
+
32
+
33
+ def version_callback(value: bool):
34
+ if value:
35
+ print(f"volknode {version('volknode')}")
36
+ raise typer.Exit()
37
+
38
+
39
+ @app.callback()
40
+ def callback(
41
+ version: bool = typer.Option(
42
+ None, "--version", "-v", callback=version_callback, is_eager=True,
43
+ help="Show version and exit."
44
+ ),
45
+ ):
46
+ """Volknode CLI application."""
47
+ pass
48
+
49
+ @app.command("pull")
50
+ def pull_command(
51
+ model_name: str = typer.Argument(..., help="The name of the model to pull."),
52
+ ):
53
+ """Pull a model from Hugging Face."""
54
+ user_home = get_user_home()
55
+ os.environ["HOME"] = str(user_home)
56
+
57
+ input_data, validation = PullModelInput.create(model_name)
58
+ if validation.has_errors():
59
+ for err in validation.errors():
60
+ print(f"Error: {err}")
61
+ raise typer.Exit(1)
62
+
63
+ db_path = _get_db_path()
64
+ llm = DependencyResolver.from_env().llm_runner(cache_dir=user_home / ".cache")
65
+ llm_model_repo = LlmModelRepository(db_path)
66
+
67
+ interactor = PullModelInteractor(llm, llm_model_repo)
68
+
69
+ print(f"Pulling model: {model_name}")
70
+
71
+ try:
72
+ interactor.execute(input_data)
73
+ print(f"Successfully pulled model: {model_name}")
74
+ except Exception as e:
75
+ print(f"Error pulling model: {e}")
76
+ raise typer.Exit(1)
77
+
78
+ @app.command("run")
79
+ def run_command(
80
+ model: str = typer.Option(..., help="Model to serve"),
81
+ ):
82
+ """Run vLLM server with the specified model."""
83
+ user_home = get_user_home()
84
+ os.environ["HOME"] = str(user_home)
85
+
86
+ input_data, validation = RunLlmNodeInput.create(model)
87
+ if validation.has_errors():
88
+ for err in validation.errors():
89
+ print(f"Error: {err}")
90
+ raise typer.Exit(1)
91
+
92
+ db_path = _get_db_path()
93
+ llm = DependencyResolver.from_env().llm_runner(cache_dir=user_home / ".cache")
94
+ llm_model_repo = LlmModelRepository(db_path)
95
+ running_vllm_config_repo = RunningVllmConfigRepository(db_path)
96
+
97
+ interactor = RunLlmNodeInteractor(llm, llm_model_repo, running_vllm_config_repo)
98
+
99
+ print(f"Starting vLLM server with model: {model}")
100
+
101
+ try:
102
+ interactor.execute(input_data)
103
+ print("vLLM server is ready.")
104
+ import signal
105
+ signal.pause()
106
+ except KeyboardInterrupt:
107
+ print("\nShutting down vLLM server...")
108
+ llm.stop()
109
+ except RuntimeError as e:
110
+ print(f"Error: {e}")
111
+ raise typer.Exit(1)
112
+
113
+
114
+ @app.command("register")
115
+ def register_command(
116
+ nexus_url: str = typer.Option(..., "--nexus-url", help="The URL of the nexus server."),
117
+ url: str = typer.Option(..., "--url", help="The URL that the nexus can reach this LLM node with."),
118
+ personal_access_token: str = typer.Option(..., "--personal-access-token", help="Personal access token for authentication."),
119
+ llm_model_alias: str = typer.Option(..., "--llm-model-alias", help="The organization friendly alias for the LLM model."),
120
+ trust_insecure_remote_app_url: bool = typer.Option(False, "--trust-insecure-url", help="Skip TLS certificate verification for the nexus URL (e.g. self-signed certificates)."),
121
+ ):
122
+ """Register this LLM node with the nexus."""
123
+ input_data, validation = RegisterLlmNodeInput.create(url, personal_access_token, llm_model_alias)
124
+
125
+ if validation.has_errors():
126
+ for err in validation.errors():
127
+ print(f"Error: {err}")
128
+ raise typer.Exit(1)
129
+
130
+
131
+ db_path = _get_db_path()
132
+ nexus = DependencyResolver.from_env().nexus(nexus_url=nexus_url, verify_ssl=not trust_insecure_remote_app_url)
133
+ llm_model_repo = LlmModelRepository(db_path)
134
+ csr_credentials_repo = CsrCredentialsRepository(db_path)
135
+ llm_node_certificate_repo = LlmNodeCertificateRepository(db_path)
136
+ nexus_certificate_repo = NexusCertificateRepository(db_path)
137
+
138
+ interactor = RegisterLlmNodeInteractor(
139
+ nexus=nexus,
140
+ llm_model_repository=llm_model_repo,
141
+ csr_credentials_repository=csr_credentials_repo,
142
+ llm_node_certificate_repository=llm_node_certificate_repo,
143
+ nexus_certificate_repository=nexus_certificate_repo,
144
+ )
145
+
146
+ print(f"Registering LLM node with model alias '{input_data.llm_model_alias}' with nexus at {nexus_url}")
147
+
148
+ try:
149
+ interactor.execute(input_data)
150
+ print("Registration successful.")
151
+ except RuntimeError as e:
152
+ print(f"Error: {e}")
153
+ raise typer.Exit(1)
154
+
155
+
156
+ @app.command("serve")
157
+ def serve_command(
158
+ port: int = typer.Option(..., "--port", help="Port for the secure server that will serve LLM requests."),
159
+ with_test_cert_pem: str = typer.Option(None, "--with-test-cert-pem", hidden=True),
160
+ with_test_priv_key_pem: str = typer.Option(None, "--with-test-priv-key-pem", hidden=True),
161
+ with_test_ca_cert_pem: str = typer.Option(None, "--with-test-ca-cert-pem", hidden=True),
162
+ ):
163
+ """Start the secure server to expose the LLM to the nexus."""
164
+ from volknode.src.mtls.mtls_reverse_proxy import MtlsReverseProxy
165
+
166
+ input_data, validation = ServeLlmInput.create(
167
+ port,
168
+ test_cert_pem=with_test_cert_pem,
169
+ test_priv_key_pem=with_test_priv_key_pem,
170
+ test_ca_cert_pem=with_test_ca_cert_pem,
171
+ )
172
+ if validation.has_errors():
173
+ for err in validation.errors():
174
+ print(f"Error: {err}")
175
+ raise typer.Exit(1)
176
+
177
+ db_path = _get_db_path()
178
+ csr_credentials_repo = CsrCredentialsRepository(db_path)
179
+ llm_node_certificate_repo = LlmNodeCertificateRepository(db_path)
180
+ nexus_certificate_repo = NexusCertificateRepository(db_path)
181
+
182
+ if input_data.test_cert_pem is not None:
183
+ from volknode.src.domain.csr_credentials import CsrCredentials
184
+ from volknode.src.domain.llm_node_certificate import LlmNodeCertificate
185
+ from volknode.src.domain.nexus_certificate import NexusCertificate
186
+
187
+ csr_credentials_repo.save(CsrCredentials(private_key_pem=input_data.test_priv_key_pem, csr_pem=""))
188
+ llm_node_certificate_repo.save(LlmNodeCertificate(certificate_pem=input_data.test_cert_pem))
189
+ nexus_certificate_repo.save(NexusCertificate(certificate_pem=input_data.test_ca_cert_pem))
190
+
191
+ interactor = ServeLlmInteractor(
192
+ csr_credentials_repository=csr_credentials_repo,
193
+ llm_node_certificate_repository=llm_node_certificate_repo,
194
+ nexus_certificate_repository=nexus_certificate_repo,
195
+ running_vllm_config_repository=RunningVllmConfigRepository(db_path),
196
+ mtls_reverse_proxy=MtlsReverseProxy(),
197
+ )
198
+
199
+ print(f"Starting secure server on port {port}")
200
+
201
+ try:
202
+ interactor.execute(input_data)
203
+ print("Secure server is running.")
204
+ import signal
205
+ signal.pause()
206
+ except KeyboardInterrupt:
207
+ print("\nShutting down secure server...")
208
+ except RuntimeError as e:
209
+ print(f"Error: {e}")
210
+ raise typer.Exit(1)
211
+
212
+
213
+ if __name__ == "__main__":
214
+ app()
File without changes
@@ -0,0 +1,26 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypeVar, Generic
3
+
4
+ T = TypeVar("T")
5
+
6
+
7
+ @dataclass
8
+ class Result(Generic[T]):
9
+ value: T | None = None
10
+ error: str | None = None
11
+
12
+ @property
13
+ def is_ok(self) -> bool:
14
+ return self.error is None
15
+
16
+ @property
17
+ def is_error(self) -> bool:
18
+ return self.error is not None
19
+
20
+ @staticmethod
21
+ def ok(value: T) -> "Result[T]":
22
+ return Result(value=value)
23
+
24
+ @staticmethod
25
+ def err(error: str) -> "Result[T]":
26
+ return Result(error=error)
@@ -0,0 +1,20 @@
1
+ class Validation:
2
+ def __init__(self):
3
+ self._error_list: list[str] = []
4
+
5
+ def add_error(self, err: str) -> None:
6
+ self._error_list.append(err)
7
+
8
+ def merge_with(self, other: "Validation") -> None:
9
+ self._error_list.extend(other._error_list)
10
+
11
+ def errors(self) -> list[str]:
12
+ return self._error_list
13
+
14
+ def error(self) -> str | None:
15
+ if not self._error_list:
16
+ return None
17
+ return "\n".join(self._error_list)
18
+
19
+ def has_errors(self) -> bool:
20
+ return len(self._error_list) > 0
File without changes
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cryptography import x509
4
+ from cryptography.hazmat.primitives import hashes, serialization
5
+ from cryptography.x509 import ObjectIdentifier
6
+
7
+ from volknode.src.csr.key_pair_generator import PrivateKey
8
+
9
+ _OID_LLM_MODEL_ALIAS = ObjectIdentifier("1.3.6.1.4.1.99999.1.1")
10
+ _OID_LLM_NODE_MODEL_NAME = ObjectIdentifier("1.3.6.1.4.1.99999.1.2")
11
+ _OID_LLM_NODE_MODEL_QUANTIZATION = ObjectIdentifier("1.3.6.1.4.1.99999.1.4")
12
+ _OID_LLM_NODE_MODEL_CONTEXT_WINDOW = ObjectIdentifier("1.3.6.1.4.1.99999.1.5")
13
+ _OID_LLM_NODE_LLM_NODE_URL = ObjectIdentifier("1.3.6.1.4.1.99999.1.10")
14
+
15
+
16
+ @dataclass
17
+ class LlmNodeCsr:
18
+ model_alias: str
19
+ model_name: str
20
+ model_quantization: str
21
+ model_context_window: int
22
+ llm_node_url: str
23
+
24
+
25
+ class CsrService:
26
+ def generate_csr(self, private_key: PrivateKey, llm_node_csr: LlmNodeCsr) -> str:
27
+ """Returns PEM-encoded CSR"""
28
+ rsa_private_key = serialization.load_pem_private_key(
29
+ private_key.pem.encode("utf-8"),
30
+ password=None,
31
+ )
32
+
33
+ subject = x509.Name([
34
+ x509.NameAttribute(_OID_LLM_MODEL_ALIAS, llm_node_csr.model_alias),
35
+ x509.NameAttribute(_OID_LLM_NODE_MODEL_NAME, llm_node_csr.model_name),
36
+ x509.NameAttribute(_OID_LLM_NODE_MODEL_QUANTIZATION, llm_node_csr.model_quantization),
37
+ x509.NameAttribute(_OID_LLM_NODE_MODEL_CONTEXT_WINDOW, str(llm_node_csr.model_context_window)),
38
+ x509.NameAttribute(_OID_LLM_NODE_LLM_NODE_URL, llm_node_csr.llm_node_url),
39
+ ])
40
+
41
+ csr = (
42
+ x509.CertificateSigningRequestBuilder()
43
+ .subject_name(subject)
44
+ .sign(rsa_private_key, hashes.SHA256())
45
+ )
46
+
47
+ return csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
@@ -0,0 +1,34 @@
1
+ from cryptography.hazmat.primitives import serialization
2
+ from cryptography.hazmat.primitives.asymmetric import rsa
3
+
4
+
5
+ class PublicKey:
6
+ def __init__(self, pem: str):
7
+ self.pem = pem
8
+
9
+
10
+ class PrivateKey:
11
+ def __init__(self, pem: str, public_key: PublicKey):
12
+ self.pem = pem
13
+ self.public_key = public_key
14
+
15
+
16
+ class KeyPairGenerator:
17
+ def generate(self) -> PrivateKey:
18
+ private_key = rsa.generate_private_key(
19
+ public_exponent=65537,
20
+ key_size=2048,
21
+ )
22
+
23
+ private_key_pem = private_key.private_bytes(
24
+ encoding=serialization.Encoding.PEM,
25
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
26
+ encryption_algorithm=serialization.NoEncryption(),
27
+ ).decode("utf-8")
28
+
29
+ public_key_pem = private_key.public_key().public_bytes(
30
+ encoding=serialization.Encoding.PEM,
31
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
32
+ ).decode("utf-8")
33
+
34
+ return PrivateKey(private_key_pem, PublicKey(public_key_pem))
@@ -0,0 +1,33 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from volknode.src.llm.llm_runner import LlmRunner
5
+ from volknode.src.nexus.nexus import Nexus
6
+
7
+
8
+ class DependencyResolver:
9
+ def __init__(self, *, use_fake_llm: bool = False, use_fake_nexus: bool = False):
10
+ self._use_fake_llm = use_fake_llm
11
+ self._use_fake_nexus = use_fake_nexus
12
+
13
+ @staticmethod
14
+ def from_env() -> "DependencyResolver":
15
+ truthy = {"1", "true", "yes"}
16
+ return DependencyResolver(
17
+ use_fake_llm=os.environ.get("USE_FAKE_LLM", "").lower() in truthy,
18
+ use_fake_nexus=os.environ.get("USE_FAKE_NEXUS", "").lower() in truthy,
19
+ )
20
+
21
+ def llm_runner(self, cache_dir: Path) -> LlmRunner:
22
+ if self._use_fake_llm:
23
+ from volknode.src.llm.fake_llm_runner import FakeLlmRunner
24
+ return FakeLlmRunner()
25
+ from volknode.src.llm.real_llm_runner import RealLlmRunner
26
+ return RealLlmRunner(cache_dir)
27
+
28
+ def nexus(self, nexus_url: str, verify_ssl: bool = True) -> Nexus:
29
+ if self._use_fake_nexus:
30
+ from volknode.src.nexus.fake_nexus import FakeNexus
31
+ return FakeNexus()
32
+ from volknode.src.nexus.real_nexus import RealNexus
33
+ return RealNexus(nexus_url, verify_ssl=verify_ssl)
@@ -0,0 +1,56 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from volknode.src.dependency_resolver import DependencyResolver
5
+ from volknode.src.llm.fake_llm_runner import FakeLlmRunner
6
+ from volknode.src.llm.real_llm_runner import RealLlmRunner
7
+ from volknode.src.nexus.fake_nexus import FakeNexus
8
+ from volknode.src.nexus.real_nexus import RealNexus
9
+
10
+
11
+ class TestDependencyResolver:
12
+ def test_default_returns_real_llm_runner(self, tmp_path: Path):
13
+ resolver = DependencyResolver()
14
+ result = resolver.llm_runner(cache_dir=tmp_path)
15
+ assert isinstance(result, RealLlmRunner)
16
+
17
+ def test_default_returns_real_nexus(self):
18
+ resolver = DependencyResolver()
19
+ result = resolver.nexus(nexus_url="http://localhost:8080")
20
+ assert isinstance(result, RealNexus)
21
+
22
+ def test_use_fake_llm_returns_fake_llm_runner(self, tmp_path: Path):
23
+ resolver = DependencyResolver(use_fake_llm=True)
24
+ result = resolver.llm_runner(cache_dir=tmp_path)
25
+ assert isinstance(result, FakeLlmRunner)
26
+
27
+ def test_use_fake_nexus_returns_fake_nexus(self):
28
+ resolver = DependencyResolver(use_fake_nexus=True)
29
+ result = resolver.nexus(nexus_url="http://localhost:8080")
30
+ assert isinstance(result, FakeNexus)
31
+
32
+ def test_from_env_defaults_to_real_when_vars_unset(self, tmp_path: Path, monkeypatch):
33
+ monkeypatch.delenv("USE_FAKE_LLM", raising=False)
34
+ monkeypatch.delenv("USE_FAKE_NEXUS", raising=False)
35
+ resolver = DependencyResolver.from_env()
36
+ assert isinstance(resolver.llm_runner(cache_dir=tmp_path), RealLlmRunner)
37
+ assert isinstance(resolver.nexus(nexus_url="http://localhost:8080"), RealNexus)
38
+
39
+ def test_from_env_reads_use_fake_llm(self, tmp_path: Path, monkeypatch):
40
+ monkeypatch.setenv("USE_FAKE_LLM", "true")
41
+ monkeypatch.delenv("USE_FAKE_NEXUS", raising=False)
42
+ resolver = DependencyResolver.from_env()
43
+ assert isinstance(resolver.llm_runner(cache_dir=tmp_path), FakeLlmRunner)
44
+
45
+ def test_from_env_reads_use_fake_nexus(self, monkeypatch):
46
+ monkeypatch.delenv("USE_FAKE_LLM", raising=False)
47
+ monkeypatch.setenv("USE_FAKE_NEXUS", "1")
48
+ resolver = DependencyResolver.from_env()
49
+ assert isinstance(resolver.nexus(nexus_url="http://localhost:8080"), FakeNexus)
50
+
51
+ def test_from_env_accepts_yes(self, tmp_path: Path, monkeypatch):
52
+ monkeypatch.setenv("USE_FAKE_LLM", "yes")
53
+ monkeypatch.setenv("USE_FAKE_NEXUS", "YES")
54
+ resolver = DependencyResolver.from_env()
55
+ assert isinstance(resolver.llm_runner(cache_dir=tmp_path), FakeLlmRunner)
56
+ assert isinstance(resolver.nexus(nexus_url="http://localhost:8080"), FakeNexus)
File without changes
@@ -0,0 +1,7 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class CsrCredentials:
6
+ private_key_pem: str
7
+ csr_pem: str
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class LlmModel:
6
+ name: str
7
+ context_window: int
8
+ quantization: str
9
+ running: bool = False
10
+
11
+ def mark_as_running(self) -> None:
12
+ self.running = True
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class LlmNodeCertificate:
6
+ certificate_pem: str
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class NexusCertificate:
6
+ certificate_pem: str
@@ -0,0 +1,7 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class RunningVllmConfig:
6
+ host: str
7
+ port: int
@@ -0,0 +1,23 @@
1
+ __pycache__/
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ .pytest_cache/
6
+
7
+ # pyenv
8
+ .python-version
9
+
10
+ # Environments
11
+ .env
12
+ .venv
13
+
14
+ # mypy
15
+ .mypy_cache/
16
+ .dmypy.json
17
+ dmypy.json
18
+
19
+ # JetBrains
20
+ .idea/
21
+
22
+ /coverage.xml
23
+ /.coverage
@@ -0,0 +1,124 @@
1
+ # ai-suite-client
2
+ A client library for accessing Ai Suite
3
+
4
+ ## Usage
5
+ First, create a client:
6
+
7
+ ```python
8
+ from ai_suite_client import Client
9
+
10
+ client = Client(base_url="https://api.example.com")
11
+ ```
12
+
13
+ If the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead:
14
+
15
+ ```python
16
+ from ai_suite_client import AuthenticatedClient
17
+
18
+ client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
19
+ ```
20
+
21
+ Now call your endpoint and use your models:
22
+
23
+ ```python
24
+ from ai_suite_client.models import MyDataModel
25
+ from ai_suite_client.api.my_tag import get_my_data_model
26
+ from ai_suite_client.types import Response
27
+
28
+ with client as client:
29
+ my_data: MyDataModel = get_my_data_model.sync(client=client)
30
+ # or if you need more info (e.g. status_code)
31
+ response: Response[MyDataModel] = get_my_data_model.sync_detailed(client=client)
32
+ ```
33
+
34
+ Or do the same thing with an async version:
35
+
36
+ ```python
37
+ from ai_suite_client.models import MyDataModel
38
+ from ai_suite_client.api.my_tag import get_my_data_model
39
+ from ai_suite_client.types import Response
40
+
41
+ async with client as client:
42
+ my_data: MyDataModel = await get_my_data_model.asyncio(client=client)
43
+ response: Response[MyDataModel] = await get_my_data_model.asyncio_detailed(client=client)
44
+ ```
45
+
46
+ By default, when you're calling an HTTPS API it will attempt to verify that SSL is working correctly. Using certificate verification is highly recommended most of the time, but sometimes you may need to authenticate to a server (especially an internal server) using a custom certificate bundle.
47
+
48
+ ```python
49
+ client = AuthenticatedClient(
50
+ base_url="https://internal_api.example.com",
51
+ token="SuperSecretToken",
52
+ verify_ssl="/path/to/certificate_bundle.pem",
53
+ )
54
+ ```
55
+
56
+ You can also disable certificate validation altogether, but beware that **this is a security risk**.
57
+
58
+ ```python
59
+ client = AuthenticatedClient(
60
+ base_url="https://internal_api.example.com",
61
+ token="SuperSecretToken",
62
+ verify_ssl=False
63
+ )
64
+ ```
65
+
66
+ Things to know:
67
+ 1. Every path/method combo becomes a Python module with four functions:
68
+ 1. `sync`: Blocking request that returns parsed data (if successful) or `None`
69
+ 1. `sync_detailed`: Blocking request that always returns a `Request`, optionally with `parsed` set if the request was successful.
70
+ 1. `asyncio`: Like `sync` but async instead of blocking
71
+ 1. `asyncio_detailed`: Like `sync_detailed` but async instead of blocking
72
+
73
+ 1. All path/query params, and bodies become method arguments.
74
+ 1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above)
75
+ 1. Any endpoint which did not have a tag will be in `ai_suite_client.api.default`
76
+
77
+ ## Advanced customizations
78
+
79
+ There are more settings on the generated `Client` class which let you control more runtime behavior, check out the docstring on that class for more info. You can also customize the underlying `httpx.Client` or `httpx.AsyncClient` (depending on your use-case):
80
+
81
+ ```python
82
+ from ai_suite_client import Client
83
+
84
+ def log_request(request):
85
+ print(f"Request event hook: {request.method} {request.url} - Waiting for response")
86
+
87
+ def log_response(response):
88
+ request = response.request
89
+ print(f"Response event hook: {request.method} {request.url} - Status {response.status_code}")
90
+
91
+ client = Client(
92
+ base_url="https://api.example.com",
93
+ httpx_args={"event_hooks": {"request": [log_request], "response": [log_response]}},
94
+ )
95
+
96
+ # Or get the underlying httpx client to modify directly with client.get_httpx_client() or client.get_async_httpx_client()
97
+ ```
98
+
99
+ You can even set the httpx client directly, but beware that this will override any existing settings (e.g., base_url):
100
+
101
+ ```python
102
+ import httpx
103
+ from ai_suite_client import Client
104
+
105
+ client = Client(
106
+ base_url="https://api.example.com",
107
+ )
108
+ # Note that base_url needs to be re-set, as would any shared cookies, headers, etc.
109
+ client.set_httpx_client(httpx.Client(base_url="https://api.example.com", proxies="http://localhost:8030"))
110
+ ```
111
+
112
+ ## Building / publishing this package
113
+ This project uses [Poetry](https://python-poetry.org/) to manage dependencies and packaging. Here are the basics:
114
+ 1. Update the metadata in pyproject.toml (e.g. authors, version)
115
+ 1. If you're using a private repository, configure it with Poetry
116
+ 1. `poetry config repositories.<your-repository-name> <url-to-your-repository>`
117
+ 1. `poetry config http-basic.<your-repository-name> <username> <password>`
118
+ 1. Publish the client with `poetry publish --build -r <your-repository-name>` or, if for public PyPI, just `poetry publish --build`
119
+
120
+ If you want to install this client into another project without publishing it (e.g. for development) then:
121
+ 1. If that project **is using Poetry**, you can simply do `poetry add <path-to-this-client>` from that project
122
+ 1. If that project is not using Poetry:
123
+ 1. Build a wheel with `poetry build -f wheel`
124
+ 1. Install that wheel from the other project `pip install <path-to-wheel>`
@@ -0,0 +1,8 @@
1
+
2
+ """ A client library for accessing Ai Suite """
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """ Contains methods for accessing the API """
@@ -0,0 +1 @@
1
+ """ Contains endpoint functions for accessing the API """