arclith-cli 0.18.0__tar.gz → 0.19.0__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.
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/PKG-INFO +2 -2
- arclith_cli-0.19.0/arclith_cli/__init__.py +1 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/capabilities.py +7 -1
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/ai.py +89 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/persistence.py +129 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/init_project.py +1 -1
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/main.py +1 -1
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/pyproject.toml +2 -2
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/e2e_manual.sh +1 -1
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_add_adapter.py +7 -7
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_core_scaffold.py +1 -1
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_embedding_capability.py +125 -3
- arclith_cli-0.19.0/tests/test_vector_store_capability.py +166 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/uv.lock +5 -3
- arclith_cli-0.18.0/arclith_cli/__init__.py +0 -1
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/.gitignore +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/LICENSE +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/Makefile +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/README.md +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/adapter_config.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/adapter_generator.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/adapter_parameters.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/adapter_rendering.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/adapter_selection.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/adapter_templates.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/add_adapter.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/capability_models.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/__init__.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/core.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/observability.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/security.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/catalogs/transports.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/core_scaffold.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/entity_scanner.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/export_config.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/new_project.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/project_paths.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/recipe.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/recipe_cli.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/recipe_models.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/rename.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/runtime_templates.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/scaffold.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/arclith_cli/updater.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/__init__.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_capabilities.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_e2e_scaffold.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_project_paths.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_recipe.py +0 -0
- {arclith_cli-0.18.0 → arclith_cli-0.19.0}/tests/test_rename.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: arclith-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: CLI scaffolding tool for arclith — hexagonal architecture framework
|
|
5
5
|
Author-email: Killian KOPP <killiankopp@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -186,7 +186,7 @@ License: Apache License
|
|
|
186
186
|
License-File: LICENSE
|
|
187
187
|
Keywords: arclith,cli,ddd,hexagonal-architecture,scaffold
|
|
188
188
|
Requires-Python: >=3.13
|
|
189
|
-
Requires-Dist: arclith>=0.
|
|
189
|
+
Requires-Dist: arclith>=0.22.0
|
|
190
190
|
Requires-Dist: httpx>=0.27.0
|
|
191
191
|
Requires-Dist: rich>=13.0.0
|
|
192
192
|
Requires-Dist: typer>=0.15.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.19.0"
|
|
@@ -24,7 +24,11 @@ from arclith_cli.catalogs.core import (
|
|
|
24
24
|
SECRETS_CAPABILITY,
|
|
25
25
|
)
|
|
26
26
|
from arclith_cli.catalogs.observability import OBSERVABILITY_CAPABILITY
|
|
27
|
-
from arclith_cli.catalogs.persistence import
|
|
27
|
+
from arclith_cli.catalogs.persistence import (
|
|
28
|
+
REPOSITORY_CAPABILITY,
|
|
29
|
+
STORAGE_CAPABILITY,
|
|
30
|
+
VECTOR_STORE_CAPABILITY,
|
|
31
|
+
)
|
|
28
32
|
from arclith_cli.catalogs.security import (
|
|
29
33
|
AUTH_CAPABILITY,
|
|
30
34
|
LICENSE_CAPABILITY,
|
|
@@ -68,6 +72,7 @@ __all__ = [
|
|
|
68
72
|
"STORAGE_CAPABILITY",
|
|
69
73
|
"SecretMappingSpec",
|
|
70
74
|
"TENANT_CAPABILITY",
|
|
75
|
+
"VECTOR_STORE_CAPABILITY",
|
|
71
76
|
"capability_catalog_as_dict",
|
|
72
77
|
"capability_names",
|
|
73
78
|
"get_capability",
|
|
@@ -77,6 +82,7 @@ __all__ = [
|
|
|
77
82
|
CAPABILITY_CATALOG = (
|
|
78
83
|
REPOSITORY_CAPABILITY,
|
|
79
84
|
STORAGE_CAPABILITY,
|
|
85
|
+
VECTOR_STORE_CAPABILITY,
|
|
80
86
|
CACHE_CAPABILITY,
|
|
81
87
|
LOGGER_CAPABILITY,
|
|
82
88
|
SECRETS_CAPABILITY,
|
|
@@ -260,6 +260,95 @@ multitenant: {multitenant}
|
|
|
260
260
|
dependency_extra="embedding",
|
|
261
261
|
entity_scoped=False,
|
|
262
262
|
),
|
|
263
|
+
AdapterSpec(
|
|
264
|
+
name="openai",
|
|
265
|
+
capability="embedding",
|
|
266
|
+
layer="outbound",
|
|
267
|
+
description="Embeddings via l'API OpenAI officielle et un secret externe.",
|
|
268
|
+
config_path="config/adapters/outbound/embedding.yaml",
|
|
269
|
+
config_template="""\
|
|
270
|
+
adapter: openai
|
|
271
|
+
base_url: "{base_url}"
|
|
272
|
+
api_key: null
|
|
273
|
+
model_name: "{model_name}"
|
|
274
|
+
dimensions: {dimensions}
|
|
275
|
+
batch_size: {batch_size}
|
|
276
|
+
timeout: {timeout}
|
|
277
|
+
encoding_format: {encoding_format}
|
|
278
|
+
normalize: {normalize}
|
|
279
|
+
multitenant: {multitenant}
|
|
280
|
+
""",
|
|
281
|
+
env_path=".env",
|
|
282
|
+
env_template="""\
|
|
283
|
+
OPENAI_API_KEY={api_key}
|
|
284
|
+
""",
|
|
285
|
+
secret_mappings=(
|
|
286
|
+
SecretMappingSpec(
|
|
287
|
+
field_path="adapters.embedding.api_key",
|
|
288
|
+
secret_key="OPENAI_API_KEY",
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
parameters=(
|
|
292
|
+
ParameterSpec(
|
|
293
|
+
name="base_url",
|
|
294
|
+
kind="string",
|
|
295
|
+
prompt="Endpoint officiel OpenAI avec préfixe API",
|
|
296
|
+
default="https://api.openai.com/v1",
|
|
297
|
+
),
|
|
298
|
+
ParameterSpec(
|
|
299
|
+
name="api_key",
|
|
300
|
+
kind="string",
|
|
301
|
+
prompt="OPENAI_API_KEY (laisser vide pour un secret externe)",
|
|
302
|
+
default="",
|
|
303
|
+
secret=True,
|
|
304
|
+
),
|
|
305
|
+
ParameterSpec(
|
|
306
|
+
name="model_name",
|
|
307
|
+
kind="string",
|
|
308
|
+
prompt="ID du modèle d'embedding OpenAI accessible au projet",
|
|
309
|
+
default="remplacer-par-model-id-openai-embedding",
|
|
310
|
+
),
|
|
311
|
+
ParameterSpec(
|
|
312
|
+
name="dimensions",
|
|
313
|
+
kind="string",
|
|
314
|
+
prompt="Dimension optionnelle des vecteurs (null pour le défaut modèle)",
|
|
315
|
+
default="null",
|
|
316
|
+
),
|
|
317
|
+
ParameterSpec(
|
|
318
|
+
name="batch_size",
|
|
319
|
+
kind="string",
|
|
320
|
+
prompt="Taille maximale des sous-batches",
|
|
321
|
+
default="64",
|
|
322
|
+
),
|
|
323
|
+
ParameterSpec(
|
|
324
|
+
name="timeout",
|
|
325
|
+
kind="string",
|
|
326
|
+
prompt="Timeout HTTP en secondes",
|
|
327
|
+
default="30.0",
|
|
328
|
+
),
|
|
329
|
+
ParameterSpec(
|
|
330
|
+
name="encoding_format",
|
|
331
|
+
kind="string",
|
|
332
|
+
prompt="Format des vecteurs retournés",
|
|
333
|
+
default="float",
|
|
334
|
+
choices=("float",),
|
|
335
|
+
),
|
|
336
|
+
ParameterSpec(
|
|
337
|
+
name="normalize",
|
|
338
|
+
kind="boolean",
|
|
339
|
+
prompt="Normaliser les vecteurs en norme L2",
|
|
340
|
+
default=False,
|
|
341
|
+
),
|
|
342
|
+
ParameterSpec(
|
|
343
|
+
name="multitenant",
|
|
344
|
+
kind="boolean",
|
|
345
|
+
prompt="Réserver la résolution de contexte par tenant",
|
|
346
|
+
default=False,
|
|
347
|
+
),
|
|
348
|
+
),
|
|
349
|
+
dependency_extra="embedding",
|
|
350
|
+
entity_scoped=False,
|
|
351
|
+
),
|
|
263
352
|
),
|
|
264
353
|
)
|
|
265
354
|
|
|
@@ -444,3 +444,132 @@ multitenant: {multitenant}
|
|
|
444
444
|
),
|
|
445
445
|
),
|
|
446
446
|
)
|
|
447
|
+
|
|
448
|
+
VECTOR_STORE_CAPABILITY = CapabilitySpec(
|
|
449
|
+
name="vector-store",
|
|
450
|
+
layer="outbound",
|
|
451
|
+
description="Indexation et recherche dense derrière un port provider-neutral.",
|
|
452
|
+
activation_config_key=None,
|
|
453
|
+
adapters=(
|
|
454
|
+
AdapterSpec(
|
|
455
|
+
name="memory",
|
|
456
|
+
capability="vector-store",
|
|
457
|
+
layer="outbound",
|
|
458
|
+
description="Recherche exacte déterministe sans dépendance pour tests et POC.",
|
|
459
|
+
config_path="config/adapters/outbound/vector_store.yaml",
|
|
460
|
+
config_template="""\
|
|
461
|
+
adapter: memory
|
|
462
|
+
collection_name: "{collection_name}"
|
|
463
|
+
vector_size: {vector_size}
|
|
464
|
+
distance: {distance}
|
|
465
|
+
multitenant: {multitenant}
|
|
466
|
+
""",
|
|
467
|
+
parameters=(
|
|
468
|
+
ParameterSpec(
|
|
469
|
+
name="collection_name",
|
|
470
|
+
kind="string",
|
|
471
|
+
prompt="Nom logique de la collection",
|
|
472
|
+
default="default",
|
|
473
|
+
),
|
|
474
|
+
ParameterSpec(
|
|
475
|
+
name="vector_size",
|
|
476
|
+
kind="string",
|
|
477
|
+
prompt="Dimension obligatoire des vecteurs",
|
|
478
|
+
default="1536",
|
|
479
|
+
),
|
|
480
|
+
ParameterSpec(
|
|
481
|
+
name="distance",
|
|
482
|
+
kind="string",
|
|
483
|
+
prompt="Métrique de similarité",
|
|
484
|
+
default="cosine",
|
|
485
|
+
choices=("cosine", "dot", "euclid"),
|
|
486
|
+
),
|
|
487
|
+
ParameterSpec(
|
|
488
|
+
name="multitenant",
|
|
489
|
+
kind="boolean",
|
|
490
|
+
prompt="Réserver la résolution de contexte par tenant",
|
|
491
|
+
default=False,
|
|
492
|
+
),
|
|
493
|
+
),
|
|
494
|
+
entity_scoped=False,
|
|
495
|
+
),
|
|
496
|
+
AdapterSpec(
|
|
497
|
+
name="qdrant",
|
|
498
|
+
capability="vector-store",
|
|
499
|
+
layer="outbound",
|
|
500
|
+
description="Index vectoriel dense Qdrant via le client Python async officiel.",
|
|
501
|
+
config_path="config/adapters/outbound/vector_store.yaml",
|
|
502
|
+
config_template="""\
|
|
503
|
+
adapter: qdrant
|
|
504
|
+
url: "{url}"
|
|
505
|
+
api_key: null
|
|
506
|
+
collection_name: "{collection_name}"
|
|
507
|
+
vector_size: {vector_size}
|
|
508
|
+
distance: {distance}
|
|
509
|
+
prefer_grpc: {prefer_grpc}
|
|
510
|
+
timeout: {timeout}
|
|
511
|
+
create_collection: {create_collection}
|
|
512
|
+
multitenant: {multitenant}
|
|
513
|
+
""",
|
|
514
|
+
secret_mappings=(
|
|
515
|
+
SecretMappingSpec(
|
|
516
|
+
field_path="adapters.vector_store.api_key",
|
|
517
|
+
secret_key="QDRANT_API_KEY",
|
|
518
|
+
),
|
|
519
|
+
),
|
|
520
|
+
parameters=(
|
|
521
|
+
ParameterSpec(
|
|
522
|
+
name="url",
|
|
523
|
+
kind="string",
|
|
524
|
+
prompt="Endpoint HTTP Qdrant sans credentials",
|
|
525
|
+
default="http://localhost:6333",
|
|
526
|
+
),
|
|
527
|
+
ParameterSpec(
|
|
528
|
+
name="collection_name",
|
|
529
|
+
kind="string",
|
|
530
|
+
prompt="Nom de la collection Qdrant",
|
|
531
|
+
default="default",
|
|
532
|
+
),
|
|
533
|
+
ParameterSpec(
|
|
534
|
+
name="vector_size",
|
|
535
|
+
kind="string",
|
|
536
|
+
prompt="Dimension obligatoire des vecteurs",
|
|
537
|
+
default="1536",
|
|
538
|
+
),
|
|
539
|
+
ParameterSpec(
|
|
540
|
+
name="distance",
|
|
541
|
+
kind="string",
|
|
542
|
+
prompt="Métrique de similarité",
|
|
543
|
+
default="cosine",
|
|
544
|
+
choices=("cosine", "dot", "euclid"),
|
|
545
|
+
),
|
|
546
|
+
ParameterSpec(
|
|
547
|
+
name="prefer_grpc",
|
|
548
|
+
kind="boolean",
|
|
549
|
+
prompt="Préférer le transport gRPC",
|
|
550
|
+
default=False,
|
|
551
|
+
),
|
|
552
|
+
ParameterSpec(
|
|
553
|
+
name="timeout",
|
|
554
|
+
kind="string",
|
|
555
|
+
prompt="Timeout client en secondes",
|
|
556
|
+
default="5.0",
|
|
557
|
+
),
|
|
558
|
+
ParameterSpec(
|
|
559
|
+
name="create_collection",
|
|
560
|
+
kind="boolean",
|
|
561
|
+
prompt="Créer la collection si elle manque",
|
|
562
|
+
default=True,
|
|
563
|
+
),
|
|
564
|
+
ParameterSpec(
|
|
565
|
+
name="multitenant",
|
|
566
|
+
kind="boolean",
|
|
567
|
+
prompt="Résoudre url, api_key et collection par tenant",
|
|
568
|
+
default=False,
|
|
569
|
+
),
|
|
570
|
+
),
|
|
571
|
+
dependency_extra="qdrant",
|
|
572
|
+
entity_scoped=False,
|
|
573
|
+
),
|
|
574
|
+
),
|
|
575
|
+
)
|
|
@@ -164,7 +164,7 @@ def add_adapter(
|
|
|
164
164
|
"--capability",
|
|
165
165
|
help=(
|
|
166
166
|
"Capacité cible: repository, cache, logger, secrets, api, mcp, probe, http, "
|
|
167
|
-
"command-bus, runtime, auth, tenant, license, llm, embedding, agent, "
|
|
167
|
+
"command-bus, runtime, auth, tenant, license, llm, embedding, vector-store, agent, "
|
|
168
168
|
"agent-persistence ou observability"
|
|
169
169
|
),
|
|
170
170
|
),
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "arclith-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.19.0"
|
|
8
8
|
description = "CLI scaffolding tool for arclith — hexagonal architecture framework"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -17,7 +17,7 @@ dependencies = [
|
|
|
17
17
|
"typer>=0.15.0",
|
|
18
18
|
"rich>=13.0.0",
|
|
19
19
|
"httpx>=0.27.0",
|
|
20
|
-
"arclith>=0.
|
|
20
|
+
"arclith>=0.22.0",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[tool.uv.sources]
|
|
@@ -39,7 +39,7 @@ if grep -q '\[tool\.uv\.sources\]' pyproject.toml; then
|
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
41
|
ARCLITH_VERSION=$(grep 'arclith\[' pyproject.toml | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
42
|
-
MIN_ARCLITH_VERSION="0.
|
|
42
|
+
MIN_ARCLITH_VERSION="0.22.0"
|
|
43
43
|
if version_lt "$ARCLITH_VERSION" "$MIN_ARCLITH_VERSION"; then
|
|
44
44
|
echo -e "${RED}✗ FAIL: arclith version < $MIN_ARCLITH_VERSION (found: $ARCLITH_VERSION)${NC}"
|
|
45
45
|
exit 1
|
|
@@ -520,7 +520,7 @@ def test_add_langsmith_adds_dependency_extra_idempotently(tmp_path: Path) -> Non
|
|
|
520
520
|
project_dir = _minimal_project(tmp_path)
|
|
521
521
|
pyproject = project_dir / "pyproject.toml"
|
|
522
522
|
pyproject.write_text(
|
|
523
|
-
'[project]\ndependencies = ["arclith[fastapi,mcp]>=0.
|
|
523
|
+
'[project]\ndependencies = ["arclith[fastapi,mcp]>=0.22.0"]\n',
|
|
524
524
|
encoding="utf-8",
|
|
525
525
|
)
|
|
526
526
|
|
|
@@ -533,7 +533,7 @@ def test_add_langsmith_adds_dependency_extra_idempotently(tmp_path: Path) -> Non
|
|
|
533
533
|
yes=True,
|
|
534
534
|
)
|
|
535
535
|
|
|
536
|
-
assert "arclith[fastapi,mcp,langsmith]>=0.
|
|
536
|
+
assert "arclith[fastapi,mcp,langsmith]>=0.22.0" in pyproject.read_text()
|
|
537
537
|
|
|
538
538
|
|
|
539
539
|
def test_add_opentelemetry_adds_dependency_extra_idempotently(
|
|
@@ -542,7 +542,7 @@ def test_add_opentelemetry_adds_dependency_extra_idempotently(
|
|
|
542
542
|
project_dir = _minimal_project(tmp_path)
|
|
543
543
|
pyproject = project_dir / "pyproject.toml"
|
|
544
544
|
pyproject.write_text(
|
|
545
|
-
'[project]\ndependencies = ["arclith[fastapi,mcp]>=0.
|
|
545
|
+
'[project]\ndependencies = ["arclith[fastapi,mcp]>=0.22.0"]\n',
|
|
546
546
|
encoding="utf-8",
|
|
547
547
|
)
|
|
548
548
|
|
|
@@ -555,7 +555,7 @@ def test_add_opentelemetry_adds_dependency_extra_idempotently(
|
|
|
555
555
|
yes=True,
|
|
556
556
|
)
|
|
557
557
|
|
|
558
|
-
assert "arclith[fastapi,mcp,opentelemetry]>=0.
|
|
558
|
+
assert "arclith[fastapi,mcp,opentelemetry]>=0.22.0" in pyproject.read_text()
|
|
559
559
|
|
|
560
560
|
|
|
561
561
|
@pytest.mark.parametrize(
|
|
@@ -1701,7 +1701,7 @@ def test_add_langgraph_persistence_enriches_config_and_dependency_without_overwr
|
|
|
1701
1701
|
(project_dir / "pyproject.toml").write_text(
|
|
1702
1702
|
"""[project]
|
|
1703
1703
|
name = "demo-service"
|
|
1704
|
-
dependencies = ["arclith[fastapi,mcp]>=0.
|
|
1704
|
+
dependencies = ["arclith[fastapi,mcp]>=0.22.0"]
|
|
1705
1705
|
""",
|
|
1706
1706
|
encoding="utf-8",
|
|
1707
1707
|
)
|
|
@@ -1753,7 +1753,7 @@ dependencies = ["arclith[fastapi,mcp]>=0.21.0"]
|
|
|
1753
1753
|
)
|
|
1754
1754
|
pyproject = (project_dir / "pyproject.toml").read_text(encoding="utf-8")
|
|
1755
1755
|
assert (
|
|
1756
|
-
"arclith[fastapi,mcp,langgraph,langgraph-persistence-mongodb]>=0.
|
|
1756
|
+
"arclith[fastapi,mcp,langgraph,langgraph-persistence-mongodb]>=0.22.0"
|
|
1757
1757
|
in pyproject
|
|
1758
1758
|
)
|
|
1759
1759
|
|
|
@@ -1775,7 +1775,7 @@ def test_add_langgraph_persistence_requires_agent_capability(
|
|
|
1775
1775
|
) -> None:
|
|
1776
1776
|
project_dir = _minimal_project(tmp_path)
|
|
1777
1777
|
(project_dir / "pyproject.toml").write_text(
|
|
1778
|
-
'[project]\ndependencies = ["arclith>=0.
|
|
1778
|
+
'[project]\ndependencies = ["arclith>=0.22.0"]\n', encoding="utf-8"
|
|
1779
1779
|
)
|
|
1780
1780
|
|
|
1781
1781
|
with pytest.raises(typer.Exit):
|
|
@@ -52,7 +52,7 @@ def test_init_project_creates_minimal_src_layout_without_entity(tmp_path: Path)
|
|
|
52
52
|
"observability:\n"
|
|
53
53
|
" enabled: []\n"
|
|
54
54
|
)
|
|
55
|
-
assert "arclith[fastapi,mcp]>=0.
|
|
55
|
+
assert "arclith[fastapi,mcp]>=0.22.0" in (generated / "pyproject.toml").read_text(encoding="utf-8")
|
|
56
56
|
assert (package_root / "domain" / "models" / "__init__.py").exists()
|
|
57
57
|
assert (package_root / "domain" / "ports" / "inbound" / "__init__.py").exists()
|
|
58
58
|
assert (package_root / "domain" / "ports" / "outbound" / "__init__.py").exists()
|
|
@@ -15,7 +15,7 @@ def _minimal_project(tmp_path: Path) -> Path:
|
|
|
15
15
|
)
|
|
16
16
|
(project_dir / "pyproject.toml").write_text(
|
|
17
17
|
'[project]\nname = "demo-service"\nversion = "0.1.0"\n'
|
|
18
|
-
'dependencies = ["arclith>=0.
|
|
18
|
+
'dependencies = ["arclith>=0.22.0"]\n',
|
|
19
19
|
encoding="utf-8",
|
|
20
20
|
)
|
|
21
21
|
return project_dir
|
|
@@ -27,7 +27,11 @@ def test_embedding_capability_catalog_declares_deterministic_adapter() -> None:
|
|
|
27
27
|
assert capability is not None
|
|
28
28
|
assert capability.layer == "outbound"
|
|
29
29
|
assert capability.activation_config_key is None
|
|
30
|
-
assert capability.adapter_names() == (
|
|
30
|
+
assert capability.adapter_names() == (
|
|
31
|
+
"deterministic",
|
|
32
|
+
"openai-compatible",
|
|
33
|
+
"openai",
|
|
34
|
+
)
|
|
31
35
|
deterministic = capability.get_adapter("deterministic")
|
|
32
36
|
assert deterministic is not None
|
|
33
37
|
assert deterministic.entity_scoped is False
|
|
@@ -53,6 +57,29 @@ def test_embedding_capability_catalog_declares_deterministic_adapter() -> None:
|
|
|
53
57
|
"normalize",
|
|
54
58
|
"multitenant",
|
|
55
59
|
]
|
|
60
|
+
openai = capability.get_adapter("openai")
|
|
61
|
+
assert openai is not None
|
|
62
|
+
assert openai.entity_scoped is False
|
|
63
|
+
assert openai.dependency_extra == "embedding"
|
|
64
|
+
assert openai.secret_mappings[0].field_path == "adapters.embedding.api_key"
|
|
65
|
+
assert openai.secret_mappings[0].secret_key == "OPENAI_API_KEY"
|
|
66
|
+
assert [parameter.name for parameter in openai.parameters] == [
|
|
67
|
+
"base_url",
|
|
68
|
+
"api_key",
|
|
69
|
+
"model_name",
|
|
70
|
+
"dimensions",
|
|
71
|
+
"batch_size",
|
|
72
|
+
"timeout",
|
|
73
|
+
"encoding_format",
|
|
74
|
+
"normalize",
|
|
75
|
+
"multitenant",
|
|
76
|
+
]
|
|
77
|
+
assert (
|
|
78
|
+
next(
|
|
79
|
+
parameter for parameter in openai.parameters if parameter.name == "api_key"
|
|
80
|
+
).secret
|
|
81
|
+
is True
|
|
82
|
+
)
|
|
56
83
|
|
|
57
84
|
|
|
58
85
|
def test_embedding_capability_is_exposed_in_json_catalog() -> None:
|
|
@@ -65,6 +92,20 @@ def test_embedding_capability_is_exposed_in_json_catalog() -> None:
|
|
|
65
92
|
assert [adapter["name"] for adapter in embedding["adapters"]] == [
|
|
66
93
|
"deterministic",
|
|
67
94
|
"openai-compatible",
|
|
95
|
+
"openai",
|
|
96
|
+
]
|
|
97
|
+
openai = embedding["adapters"][2]
|
|
98
|
+
api_key = next(
|
|
99
|
+
parameter
|
|
100
|
+
for parameter in openai["parameters"]
|
|
101
|
+
if parameter["name"] == "api_key"
|
|
102
|
+
)
|
|
103
|
+
assert api_key["secret"] is True
|
|
104
|
+
assert openai["secret_mappings"] == [
|
|
105
|
+
{
|
|
106
|
+
"field_path": "adapters.embedding.api_key",
|
|
107
|
+
"secret_key": "OPENAI_API_KEY",
|
|
108
|
+
}
|
|
68
109
|
]
|
|
69
110
|
|
|
70
111
|
|
|
@@ -156,6 +197,87 @@ def test_add_openai_compatible_embedding_generates_loadable_config_idempotently(
|
|
|
156
197
|
assert app.config.adapters.embedding is not None
|
|
157
198
|
assert app.config.adapters.embedding.model_name == "nomic-embed-text"
|
|
158
199
|
assert isinstance(app.embedding(), OpenAICompatibleEmbeddingAdapter)
|
|
159
|
-
assert "arclith[embedding]>=0.
|
|
200
|
+
assert "arclith[embedding]>=0.22.0" in (project_dir / "pyproject.toml").read_text(
|
|
160
201
|
encoding="utf-8"
|
|
161
202
|
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_add_openai_embedding_generates_safe_loadable_config_idempotently(
|
|
206
|
+
tmp_path: Path,
|
|
207
|
+
monkeypatch,
|
|
208
|
+
) -> None:
|
|
209
|
+
project_dir = _minimal_project(tmp_path)
|
|
210
|
+
(project_dir / ".env").write_text("EXISTING=value\n", encoding="utf-8")
|
|
211
|
+
params = {
|
|
212
|
+
"model_name": "configured-embedding-model",
|
|
213
|
+
"dimensions": "1536",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for _ in range(2):
|
|
217
|
+
add_adapter_cmd(
|
|
218
|
+
project_dir=project_dir,
|
|
219
|
+
capability_name="embedding",
|
|
220
|
+
adapter="openai",
|
|
221
|
+
adapter_params=params,
|
|
222
|
+
yes=True,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
config_path = project_dir / "config/adapters/outbound/embedding.yaml"
|
|
226
|
+
config_text = config_path.read_text(encoding="utf-8")
|
|
227
|
+
secrets_text = (project_dir / "config/secrets.yaml").read_text(encoding="utf-8")
|
|
228
|
+
env_text = (project_dir / ".env").read_text(encoding="utf-8")
|
|
229
|
+
|
|
230
|
+
assert config_text == (
|
|
231
|
+
"adapter: openai\n"
|
|
232
|
+
'base_url: "https://api.openai.com/v1"\n'
|
|
233
|
+
"api_key: null\n"
|
|
234
|
+
'model_name: "configured-embedding-model"\n'
|
|
235
|
+
"dimensions: 1536\n"
|
|
236
|
+
"batch_size: 64\n"
|
|
237
|
+
"timeout: 30.0\n"
|
|
238
|
+
"encoding_format: float\n"
|
|
239
|
+
"normalize: false\n"
|
|
240
|
+
"multitenant: false\n"
|
|
241
|
+
)
|
|
242
|
+
assert "adapters.embedding.api_key: OPENAI_API_KEY" in secrets_text
|
|
243
|
+
assert secrets_text.count("adapters.embedding.api_key") == 1
|
|
244
|
+
assert env_text == "EXISTING=value\n"
|
|
245
|
+
assert "sk-" not in config_text + secrets_text + env_text
|
|
246
|
+
assert ".env" in (project_dir / ".gitignore").read_text(encoding="utf-8")
|
|
247
|
+
assert "arclith[embedding]>=0.22.0" in (project_dir / "pyproject.toml").read_text(
|
|
248
|
+
encoding="utf-8"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
|
|
252
|
+
from arclith import Arclith
|
|
253
|
+
from arclith.adapters.outbound.openai import OpenAIEmbeddingAdapter
|
|
254
|
+
|
|
255
|
+
app = Arclith(project_dir / "config")
|
|
256
|
+
|
|
257
|
+
assert app.config.adapters.embedding is not None
|
|
258
|
+
assert app.config.adapters.embedding.api_key == "test-key"
|
|
259
|
+
assert type(app.embedding()) is OpenAIEmbeddingAdapter
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_add_openai_embedding_omits_fake_key_and_keeps_dimensions_optional(
|
|
263
|
+
tmp_path: Path,
|
|
264
|
+
) -> None:
|
|
265
|
+
project_dir = _minimal_project(tmp_path)
|
|
266
|
+
|
|
267
|
+
add_adapter_cmd(
|
|
268
|
+
project_dir=project_dir,
|
|
269
|
+
capability_name="embedding",
|
|
270
|
+
adapter="openai",
|
|
271
|
+
adapter_params={"model_name": "configured-embedding-model"},
|
|
272
|
+
yes=True,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
config_text = (project_dir / "config/adapters/outbound/embedding.yaml").read_text(
|
|
276
|
+
encoding="utf-8"
|
|
277
|
+
)
|
|
278
|
+
env_text = (project_dir / ".env").read_text(encoding="utf-8")
|
|
279
|
+
|
|
280
|
+
assert "dimensions: null" in config_text
|
|
281
|
+
assert "api_key: null" in config_text
|
|
282
|
+
assert "OPENAI_API_KEY=" not in env_text
|
|
283
|
+
assert "sk-" not in config_text + env_text
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from arclith_cli.add_adapter import add_adapter_cmd
|
|
4
|
+
from arclith_cli.capabilities import capability_catalog_as_dict, get_capability
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _minimal_project(tmp_path: Path) -> Path:
|
|
8
|
+
project_dir = tmp_path / "demo-service"
|
|
9
|
+
(project_dir / "src" / "demo_service" / "domain" / "models").mkdir(parents=True)
|
|
10
|
+
config_dir = project_dir / "config" / "adapters"
|
|
11
|
+
config_dir.mkdir(parents=True)
|
|
12
|
+
(config_dir / "adapters.yaml").write_text(
|
|
13
|
+
"logger: console\nrepository: memory\nobservability:\n enabled: []\n",
|
|
14
|
+
encoding="utf-8",
|
|
15
|
+
)
|
|
16
|
+
(project_dir / "pyproject.toml").write_text(
|
|
17
|
+
'[project]\nname = "demo-service"\nversion = "0.1.0"\n'
|
|
18
|
+
'dependencies = ["arclith>=0.22.0"]\n',
|
|
19
|
+
encoding="utf-8",
|
|
20
|
+
)
|
|
21
|
+
return project_dir
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_vector_store_capability_catalog_declares_memory_adapter() -> None:
|
|
25
|
+
capability = get_capability("vector-store")
|
|
26
|
+
|
|
27
|
+
assert capability is not None
|
|
28
|
+
assert capability.layer == "outbound"
|
|
29
|
+
assert capability.activation_config_key is None
|
|
30
|
+
assert capability.adapter_names() == ("memory", "qdrant")
|
|
31
|
+
memory = capability.get_adapter("memory")
|
|
32
|
+
assert memory is not None
|
|
33
|
+
assert memory.entity_scoped is False
|
|
34
|
+
assert memory.dependency_extra is None
|
|
35
|
+
assert memory.config_path == "config/adapters/outbound/vector_store.yaml"
|
|
36
|
+
assert [parameter.name for parameter in memory.parameters] == [
|
|
37
|
+
"collection_name",
|
|
38
|
+
"vector_size",
|
|
39
|
+
"distance",
|
|
40
|
+
"multitenant",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_vector_store_capability_is_exposed_in_json_catalog() -> None:
|
|
45
|
+
payload_by_name = {
|
|
46
|
+
capability["name"]: capability for capability in capability_catalog_as_dict()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
vector_store = payload_by_name["vector-store"]
|
|
50
|
+
assert vector_store["activation_config_key"] is None
|
|
51
|
+
assert [adapter["name"] for adapter in vector_store["adapters"]] == [
|
|
52
|
+
"memory",
|
|
53
|
+
"qdrant",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_vector_store_capability_declares_qdrant_adapter() -> None:
|
|
58
|
+
capability = get_capability("vector-store")
|
|
59
|
+
|
|
60
|
+
assert capability is not None
|
|
61
|
+
qdrant = capability.get_adapter("qdrant")
|
|
62
|
+
assert qdrant is not None
|
|
63
|
+
assert qdrant.entity_scoped is False
|
|
64
|
+
assert qdrant.dependency_extra == "qdrant"
|
|
65
|
+
assert qdrant.config_path == "config/adapters/outbound/vector_store.yaml"
|
|
66
|
+
assert [mapping.field_path for mapping in qdrant.secret_mappings] == [
|
|
67
|
+
"adapters.vector_store.api_key",
|
|
68
|
+
]
|
|
69
|
+
assert [mapping.secret_key for mapping in qdrant.secret_mappings] == [
|
|
70
|
+
"QDRANT_API_KEY",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_add_memory_vector_store_generates_loadable_config_idempotently(
|
|
75
|
+
tmp_path: Path,
|
|
76
|
+
) -> None:
|
|
77
|
+
project_dir = _minimal_project(tmp_path)
|
|
78
|
+
params = {
|
|
79
|
+
"collection_name": "documents",
|
|
80
|
+
"vector_size": "3",
|
|
81
|
+
"distance": "cosine",
|
|
82
|
+
"multitenant": "false",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for _ in range(2):
|
|
86
|
+
add_adapter_cmd(
|
|
87
|
+
project_dir=project_dir,
|
|
88
|
+
capability_name="vector-store",
|
|
89
|
+
adapter="memory",
|
|
90
|
+
adapter_params=params,
|
|
91
|
+
yes=True,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
from arclith import Arclith, MemoryVectorStore
|
|
95
|
+
|
|
96
|
+
config_path = project_dir / "config/adapters/outbound/vector_store.yaml"
|
|
97
|
+
app = Arclith(project_dir / "config")
|
|
98
|
+
|
|
99
|
+
assert config_path.read_text(encoding="utf-8") == (
|
|
100
|
+
"adapter: memory\n"
|
|
101
|
+
'collection_name: "documents"\n'
|
|
102
|
+
"vector_size: 3\n"
|
|
103
|
+
"distance: cosine\n"
|
|
104
|
+
"multitenant: false\n"
|
|
105
|
+
)
|
|
106
|
+
assert app.config.adapters.vector_store is not None
|
|
107
|
+
assert app.config.adapters.vector_store.vector_size == 3
|
|
108
|
+
assert isinstance(app.vector_store(), MemoryVectorStore)
|
|
109
|
+
assert "vector_store:" not in (
|
|
110
|
+
project_dir / "config/adapters/adapters.yaml"
|
|
111
|
+
).read_text(encoding="utf-8")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_add_qdrant_vector_store_generates_loadable_config_and_secrets(
|
|
115
|
+
tmp_path: Path,
|
|
116
|
+
monkeypatch,
|
|
117
|
+
) -> None:
|
|
118
|
+
project_dir = _minimal_project(tmp_path)
|
|
119
|
+
params = {
|
|
120
|
+
"url": "http://localhost:6333",
|
|
121
|
+
"collection_name": "documents",
|
|
122
|
+
"vector_size": "1536",
|
|
123
|
+
"distance": "cosine",
|
|
124
|
+
"prefer_grpc": "false",
|
|
125
|
+
"timeout": "5.0",
|
|
126
|
+
"create_collection": "true",
|
|
127
|
+
"multitenant": "false",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for _ in range(2):
|
|
131
|
+
add_adapter_cmd(
|
|
132
|
+
project_dir=project_dir,
|
|
133
|
+
capability_name="vector-store",
|
|
134
|
+
adapter="qdrant",
|
|
135
|
+
adapter_params=params,
|
|
136
|
+
yes=True,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
config_text = (
|
|
140
|
+
project_dir / "config/adapters/outbound/vector_store.yaml"
|
|
141
|
+
).read_text(encoding="utf-8")
|
|
142
|
+
secrets_text = (project_dir / "config/secrets.yaml").read_text(encoding="utf-8")
|
|
143
|
+
|
|
144
|
+
assert config_text == (
|
|
145
|
+
"adapter: qdrant\n"
|
|
146
|
+
'url: "http://localhost:6333"\n'
|
|
147
|
+
"api_key: null\n"
|
|
148
|
+
'collection_name: "documents"\n'
|
|
149
|
+
"vector_size: 1536\n"
|
|
150
|
+
"distance: cosine\n"
|
|
151
|
+
"prefer_grpc: false\n"
|
|
152
|
+
"timeout: 5.0\n"
|
|
153
|
+
"create_collection: true\n"
|
|
154
|
+
"multitenant: false\n"
|
|
155
|
+
)
|
|
156
|
+
assert secrets_text.count("adapters.vector_store.api_key") == 1
|
|
157
|
+
assert "adapters.vector_store.api_key: QDRANT_API_KEY" in secrets_text
|
|
158
|
+
assert "adapters.vector_store.url" not in secrets_text
|
|
159
|
+
|
|
160
|
+
from arclith import Arclith, QdrantVectorStore
|
|
161
|
+
|
|
162
|
+
monkeypatch.setenv("QDRANT_API_KEY", "test-key")
|
|
163
|
+
app = Arclith(project_dir / "config")
|
|
164
|
+
assert isinstance(app.vector_store(), QdrantVectorStore)
|
|
165
|
+
assert app.config.adapters.vector_store is not None
|
|
166
|
+
assert app.config.adapters.vector_store.api_key == "test-key"
|
|
@@ -46,7 +46,7 @@ wheels = [
|
|
|
46
46
|
|
|
47
47
|
[[package]]
|
|
48
48
|
name = "arclith"
|
|
49
|
-
version = "0.
|
|
49
|
+
version = "0.22.0"
|
|
50
50
|
source = { editable = "../" }
|
|
51
51
|
dependencies = [
|
|
52
52
|
{ name = "fastapi" },
|
|
@@ -130,6 +130,8 @@ requires-dist = [
|
|
|
130
130
|
{ name = "pytz", marker = "extra == 'all'", specifier = "==2024.1" },
|
|
131
131
|
{ name = "pytz", marker = "extra == 'duckdb'", specifier = "==2024.1" },
|
|
132
132
|
{ name = "pyyaml", specifier = "==6.0.3" },
|
|
133
|
+
{ name = "qdrant-client", marker = "extra == 'all'", specifier = "==1.19.0" },
|
|
134
|
+
{ name = "qdrant-client", marker = "extra == 'qdrant'", specifier = "==1.19.0" },
|
|
133
135
|
{ name = "redis", marker = "extra == 'all'", specifier = ">=5.0.0" },
|
|
134
136
|
{ name = "redis", marker = "extra == 'cache'", specifier = ">=5.0.0" },
|
|
135
137
|
{ name = "redis", marker = "extra == 'langgraph-runtime'", specifier = ">=5.0.0" },
|
|
@@ -141,7 +143,7 @@ requires-dist = [
|
|
|
141
143
|
{ name = "uvicorn", marker = "extra == 'fastapi'", specifier = "==0.41.0" },
|
|
142
144
|
{ name = "uvicorn", marker = "extra == 'langgraph-runtime'", specifier = "==0.41.0" },
|
|
143
145
|
]
|
|
144
|
-
provides-extras = ["mongodb", "duckdb", "mariadb", "postgresql", "fastapi", "mcp", "vault", "cache", "auth", "embedding", "rabbitmq", "langgraph", "langgraph-runtime", "langgraph-persistence-sqlite", "langgraph-persistence-postgresql", "langgraph-persistence-mongodb", "langgraph-persistence-redis", "langsmith", "opentelemetry", "all", "azure-blob", "s3", "gcs"]
|
|
146
|
+
provides-extras = ["mongodb", "duckdb", "mariadb", "postgresql", "fastapi", "mcp", "vault", "cache", "auth", "embedding", "rabbitmq", "langgraph", "langgraph-runtime", "langgraph-persistence-sqlite", "langgraph-persistence-postgresql", "langgraph-persistence-mongodb", "langgraph-persistence-redis", "langsmith", "opentelemetry", "all", "azure-blob", "s3", "gcs", "qdrant"]
|
|
145
147
|
|
|
146
148
|
[package.metadata.requires-dev]
|
|
147
149
|
dev = [
|
|
@@ -163,7 +165,7 @@ docs = [
|
|
|
163
165
|
|
|
164
166
|
[[package]]
|
|
165
167
|
name = "arclith-cli"
|
|
166
|
-
version = "0.
|
|
168
|
+
version = "0.19.0"
|
|
167
169
|
source = { editable = "." }
|
|
168
170
|
dependencies = [
|
|
169
171
|
{ name = "arclith" },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.18.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|