higpertext-cli 0.8.0__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.
- config/adapters_config.json +450 -0
- config/antigravity_agent_template.json +31 -0
- config/app_config.json +174 -0
- config/context_engine.json +33 -0
- config/environments/model_defaults.json +5 -0
- config/governance/branching_strategy.json +36 -0
- config/governance/deployment_gates.json +30 -0
- config/governance/guidelines_contract.json +54 -0
- config/governance/quality_gates.json +39 -0
- config/governance/section_rules.json +22 -0
- config/governance/security_guardrails.json +52 -0
- config/hooks/README.md +35 -0
- config/hooks/custom/test_output_limiter.json +9 -0
- config/hooks/global/session_prompt.json +9 -0
- config/htx_config.json +24 -0
- config/profile_learner.json +18 -0
- config/profiles/base_agent.json +40 -0
- config/profiles/base_auditor.json +19 -0
- config/profiles/base_developer.json +19 -0
- config/profiles/base_operator.json +16 -0
- config/profiles/global.json +33 -0
- config/profiles/software_developer.json +23 -0
- config/router_content.json +137 -0
- config/semantic_graph.json +66 -0
- config/workflows/ado_release_flow.json +38 -0
- config/workflows/docs-update.json +33 -0
- config/workflows/governance-check.yaml +26 -0
- config/workflows/guidelines-sync.json +40 -0
- config/workflows/higpertext-build.json +73 -0
- config/workflows/higpertext-plan.json +38 -0
- config/workflows/higpertext-review.json +41 -0
- config/workflows/pr-quality-check.json +56 -0
- config/workflows/quality-remediation.json +57 -0
- higpertext/__init__.py +18 -0
- higpertext/adapters/__init__.py +27 -0
- higpertext/adapters/adapter_utils.py +604 -0
- higpertext/adapters/claude_adapter/__init__.py +0 -0
- higpertext/adapters/claude_adapter/claude_adapter.py +154 -0
- higpertext/adapters/copilot_adapter/__init__.py +0 -0
- higpertext/adapters/copilot_adapter/copilot_adapter.py +231 -0
- higpertext/adapters/gemini_adapter/__init__.py +0 -0
- higpertext/adapters/gemini_adapter/gemini_adapter.py +211 -0
- higpertext/adapters/llm_formatter.py +46 -0
- higpertext/adapters/open_code_adapter/__init__.py +0 -0
- higpertext/adapters/open_code_adapter/open_code_adapter.py +480 -0
- higpertext/capabilities/capabilities_runner.py +216 -0
- higpertext/capabilities/common/agent-builder.json +54 -0
- higpertext/capabilities/common/agent-sync.json +34 -0
- higpertext/capabilities/common/code-skeletonizer.json +35 -0
- higpertext/capabilities/common/commit-report.json +42 -0
- higpertext/capabilities/common/context-assembler.json +37 -0
- higpertext/capabilities/common/context-budget-report.json +15 -0
- higpertext/capabilities/common/dep-manager.json +43 -0
- higpertext/capabilities/common/docs-sync.json +14 -0
- higpertext/capabilities/common/doctor.json +18 -0
- higpertext/capabilities/common/efficiency-meter.json +31 -0
- higpertext/capabilities/common/env-catalog.json +13 -0
- higpertext/capabilities/common/env-clean.json +14 -0
- higpertext/capabilities/common/env-logs.json +16 -0
- higpertext/capabilities/common/env-runner.json +23 -0
- higpertext/capabilities/common/env-status.json +13 -0
- higpertext/capabilities/common/env-stop.json +14 -0
- higpertext/capabilities/common/env-template.json +14 -0
- higpertext/capabilities/common/error-context-locator.json +23 -0
- higpertext/capabilities/common/eval-agent.json +33 -0
- higpertext/capabilities/common/file-map.json +17 -0
- higpertext/capabilities/common/governance-exception.json +54 -0
- higpertext/capabilities/common/graph-query.json +59 -0
- higpertext/capabilities/common/graph-rebuild.json +31 -0
- higpertext/capabilities/common/graph-visualize.json +37 -0
- higpertext/capabilities/common/grep-search.json +176 -0
- higpertext/capabilities/common/higpertext-tester.json +25 -0
- higpertext/capabilities/common/hook-health.json +19 -0
- higpertext/capabilities/common/hook-sync-check.json +19 -0
- higpertext/capabilities/common/hooks-manager.json +55 -0
- higpertext/capabilities/common/knowledge-asker.json +27 -0
- higpertext/capabilities/common/list-rules.json +27 -0
- higpertext/capabilities/common/llm-invoke.json +59 -0
- higpertext/capabilities/common/load-rules.json +37 -0
- higpertext/capabilities/common/memory-manager.json +65 -0
- higpertext/capabilities/common/quality-scan.json +21 -0
- higpertext/capabilities/common/quality-updater.json +35 -0
- higpertext/capabilities/common/rag-index.json +17 -0
- higpertext/capabilities/common/report-viewer.json +24 -0
- higpertext/capabilities/common/roadmap-report.json +37 -0
- higpertext/capabilities/common/scripts/_env_cli.py +65 -0
- higpertext/capabilities/common/scripts/agent_builder.py +60 -0
- higpertext/capabilities/common/scripts/agent_sync.py +56 -0
- higpertext/capabilities/common/scripts/ask_higpertext.py +38 -0
- higpertext/capabilities/common/scripts/code_skeletonizer.py +225 -0
- higpertext/capabilities/common/scripts/commit_report.py +134 -0
- higpertext/capabilities/common/scripts/context_assembler.py +70 -0
- higpertext/capabilities/common/scripts/context_budget_report.py +53 -0
- higpertext/capabilities/common/scripts/dep_manager.py +81 -0
- higpertext/capabilities/common/scripts/docs_sync.py +981 -0
- higpertext/capabilities/common/scripts/doctor.py +144 -0
- higpertext/capabilities/common/scripts/efficiency_meter.py +83 -0
- higpertext/capabilities/common/scripts/env_catalog.py +47 -0
- higpertext/capabilities/common/scripts/env_clean.py +30 -0
- higpertext/capabilities/common/scripts/env_logs.py +32 -0
- higpertext/capabilities/common/scripts/env_runner.py +53 -0
- higpertext/capabilities/common/scripts/env_status.py +38 -0
- higpertext/capabilities/common/scripts/env_stop.py +30 -0
- higpertext/capabilities/common/scripts/env_template.py +73 -0
- higpertext/capabilities/common/scripts/error_context_locator.py +138 -0
- higpertext/capabilities/common/scripts/eval_agent.py +80 -0
- higpertext/capabilities/common/scripts/file_map.py +95 -0
- higpertext/capabilities/common/scripts/governance_exception.py +116 -0
- higpertext/capabilities/common/scripts/graph_query.py +104 -0
- higpertext/capabilities/common/scripts/graph_rebuild.py +107 -0
- higpertext/capabilities/common/scripts/graph_visualize.py +76 -0
- higpertext/capabilities/common/scripts/grep_search.py +648 -0
- higpertext/capabilities/common/scripts/higpertext_tester.py +102 -0
- higpertext/capabilities/common/scripts/hook_health.py +149 -0
- higpertext/capabilities/common/scripts/hook_sync_check.py +134 -0
- higpertext/capabilities/common/scripts/hooks_manager.py +171 -0
- higpertext/capabilities/common/scripts/list_rules.py +175 -0
- higpertext/capabilities/common/scripts/llm_invoke.py +135 -0
- higpertext/capabilities/common/scripts/load_rules.py +379 -0
- higpertext/capabilities/common/scripts/memory_manager.py +210 -0
- higpertext/capabilities/common/scripts/presentation_engine.py +63 -0
- higpertext/capabilities/common/scripts/quality_scan.py +132 -0
- higpertext/capabilities/common/scripts/rag_index.py +39 -0
- higpertext/capabilities/common/scripts/report_viewer.py +106 -0
- higpertext/capabilities/common/scripts/roadmap_report.py +73 -0
- higpertext/capabilities/common/scripts/search_router.py +111 -0
- higpertext/capabilities/common/scripts/semantic_diff.py +166 -0
- higpertext/capabilities/common/scripts/semantic_search.py +43 -0
- higpertext/capabilities/common/scripts/session_control.py +136 -0
- higpertext/capabilities/common/scripts/smart_read.py +232 -0
- higpertext/capabilities/common/scripts/subagent_executor.py +143 -0
- higpertext/capabilities/common/scripts/sync_agents.py +353 -0
- higpertext/capabilities/common/scripts/task_decomposer.py +78 -0
- higpertext/capabilities/common/scripts/telemetry_report.py +36 -0
- higpertext/capabilities/common/search-router.json +24 -0
- higpertext/capabilities/common/semantic-diff.json +40 -0
- higpertext/capabilities/common/semantic-search.json +19 -0
- higpertext/capabilities/common/session-clean.json +20 -0
- higpertext/capabilities/common/session-start.json +44 -0
- higpertext/capabilities/common/smart-read.json +28 -0
- higpertext/capabilities/common/subagent-executor.json +25 -0
- higpertext/capabilities/common/sync-agents.json +32 -0
- higpertext/capabilities/common/task-decomposer.json +37 -0
- higpertext/capabilities/common/telemetry-report.json +23 -0
- higpertext/capabilities/git/__init__.py +0 -0
- higpertext/capabilities/git/committer.json +61 -0
- higpertext/capabilities/git/diff.json +33 -0
- higpertext/capabilities/git/ls-files.json +44 -0
- higpertext/capabilities/git/rm.json +27 -0
- higpertext/capabilities/git/scripts/__init__.py +0 -0
- higpertext/capabilities/git/scripts/commit_changes.py +1077 -0
- higpertext/capabilities/git/scripts/git_diff.py +171 -0
- higpertext/capabilities/git/scripts/git_ls_files.py +376 -0
- higpertext/capabilities/git/scripts/git_rm.py +62 -0
- higpertext/capabilities/security/k8s-auditor.json +33 -0
- higpertext/capabilities/security/scripts/k8s_auditor.py +307 -0
- higpertext/capabilities/security/scripts/secret_scanner.py +235 -0
- higpertext/capabilities/security/secret-scanner.json +32 -0
- higpertext/hooks/__init__.py +28 -0
- higpertext/hooks/_compat.py +27 -0
- higpertext/hooks/hook_tasks/__init__.py +1 -0
- higpertext/hooks/hook_tasks/_rules/__init__.py +0 -0
- higpertext/hooks/hook_tasks/_rules/bash_rules.py +635 -0
- higpertext/hooks/hook_tasks/_rules/context_engine_rule.py +79 -0
- higpertext/hooks/hook_tasks/_rules/context_rules.py +199 -0
- higpertext/hooks/hook_tasks/_rules/governance_adapter.py +72 -0
- higpertext/hooks/hook_tasks/_rules/profile_rules.json +25 -0
- higpertext/hooks/hook_tasks/_rules/quality_rules.py +86 -0
- higpertext/hooks/hook_tasks/_rules/security_rules.py +214 -0
- higpertext/hooks/hook_tasks/_rules/session_rules.py +316 -0
- higpertext/hooks/hook_tasks/_rules/telemetry_rules.py +121 -0
- higpertext/hooks/hook_tasks/audit_logger_hook.py +28 -0
- higpertext/hooks/hook_tasks/hook_bash_guard.py +101 -0
- higpertext/hooks/hook_tasks/hook_code_quality.py +48 -0
- higpertext/hooks/hook_tasks/hook_context_hint.py +46 -0
- higpertext/hooks/hook_tasks/hook_context_manager.py +44 -0
- higpertext/hooks/hook_tasks/hook_io.py +122 -0
- higpertext/hooks/hook_tasks/hook_loop_guard.py +182 -0
- higpertext/hooks/hook_tasks/hook_post_observer.py +54 -0
- higpertext/hooks/hook_tasks/hook_read_guard.py +85 -0
- higpertext/hooks/hook_tasks/hook_security_guard.py +81 -0
- higpertext/hooks/hook_tasks/hook_session_prompt.py +83 -0
- higpertext/hooks/hook_tasks/hook_session_stop.py +115 -0
- higpertext/hooks/hook_tasks/hook_utils.py +144 -0
- higpertext/hooks/hook_tasks/session_guard_hook.py +23 -0
- higpertext/hooks/hook_tasks/telemetry_utils.py +176 -0
- higpertext/hooks/hook_tasks/test_echo_hook.py +33 -0
- higpertext/hooks/hook_tasks/webhook_hook.py +54 -0
- higpertext/hooks/hook_tasks/workflow_runner_hook.py +49 -0
- higpertext/hooks/hooks_catalog.json +116 -0
- higpertext/kernel/__init__.py +63 -0
- higpertext/kernel/_compat.py +138 -0
- higpertext/kernel/app_config.py +117 -0
- higpertext/kernel/application/__init__.py +13 -0
- higpertext/kernel/application/agent_registry.py +102 -0
- higpertext/kernel/application/capability_manager.py +61 -0
- higpertext/kernel/application/commit_reporter.py +247 -0
- higpertext/kernel/application/context_builder.py +166 -0
- higpertext/kernel/application/context_engine.py +409 -0
- higpertext/kernel/application/engine.py +41 -0
- higpertext/kernel/application/env_runtime.py +174 -0
- higpertext/kernel/application/environment_manager.py +154 -0
- higpertext/kernel/application/governance.py +192 -0
- higpertext/kernel/application/hook_registry.py +102 -0
- higpertext/kernel/application/hook_renderer.py +720 -0
- higpertext/kernel/application/ports.py +49 -0
- higpertext/kernel/application/profile_learner.py +358 -0
- higpertext/kernel/application/profile_service.py +205 -0
- higpertext/kernel/application/profile_services.py +6 -0
- higpertext/kernel/application/profile_use_cases.py +93 -0
- higpertext/kernel/application/rag_service.py +75 -0
- higpertext/kernel/application/roadmap_reporter.py +178 -0
- higpertext/kernel/application/semantic_engine.py +258 -0
- higpertext/kernel/application/session_services.py +33 -0
- higpertext/kernel/application/skill_hook_compiler.py +85 -0
- higpertext/kernel/application/telemetry.py +326 -0
- higpertext/kernel/application/workflow_manager.py +176 -0
- higpertext/kernel/config_paths.py +66 -0
- higpertext/kernel/domain/__init__.py +12 -0
- higpertext/kernel/domain/agent_registry.py +23 -0
- higpertext/kernel/domain/commit_reporter.py +155 -0
- higpertext/kernel/domain/compilers.py +7 -0
- higpertext/kernel/domain/context_engine.py +319 -0
- higpertext/kernel/domain/entities.py +51 -0
- higpertext/kernel/domain/env_runtime.py +62 -0
- higpertext/kernel/domain/governance.py +198 -0
- higpertext/kernel/domain/hook_models.py +29 -0
- higpertext/kernel/domain/profile_learner.py +186 -0
- higpertext/kernel/domain/rag.py +70 -0
- higpertext/kernel/domain/repositories.py +8 -0
- higpertext/kernel/domain/roadmap_reporter.py +80 -0
- higpertext/kernel/domain/semantic_engine.py +107 -0
- higpertext/kernel/engine.py +42 -0
- higpertext/kernel/htx_resolver.py +69 -0
- higpertext/kernel/infrastructure/__init__.py +13 -0
- higpertext/kernel/infrastructure/agent_registry.py +40 -0
- higpertext/kernel/infrastructure/cache/capability_cache.py +319 -0
- higpertext/kernel/infrastructure/capability_helper.py +40 -0
- higpertext/kernel/infrastructure/cli/__init__.py +1 -0
- higpertext/kernel/infrastructure/cli/agent_commands.py +62 -0
- higpertext/kernel/infrastructure/cli/arguments.py +39 -0
- higpertext/kernel/infrastructure/cli/capability_command_builder.py +86 -0
- higpertext/kernel/infrastructure/cli/capability_task_service.py +234 -0
- higpertext/kernel/infrastructure/cli/cli_search.py +234 -0
- higpertext/kernel/infrastructure/cli/parameter_contracts.py +83 -0
- higpertext/kernel/infrastructure/cli/parser_builder.py +122 -0
- higpertext/kernel/infrastructure/cli/profile_commands.py +89 -0
- higpertext/kernel/infrastructure/cli/roadmap_commands.py +117 -0
- higpertext/kernel/infrastructure/cli/router.py +1110 -0
- higpertext/kernel/infrastructure/cli/session_commands.py +36 -0
- higpertext/kernel/infrastructure/cli/task_commands.py +23 -0
- higpertext/kernel/infrastructure/cli/task_result_reporter.py +56 -0
- higpertext/kernel/infrastructure/cli/workflow_commands.py +25 -0
- higpertext/kernel/infrastructure/compilers/__init__.py +3 -0
- higpertext/kernel/infrastructure/compilers/factory.py +27 -0
- higpertext/kernel/infrastructure/compilers/graph_compiler.py +20 -0
- higpertext/kernel/infrastructure/compilers/guide_compiler.py +50 -0
- higpertext/kernel/infrastructure/compilers/hook_compiler.py +69 -0
- higpertext/kernel/infrastructure/compilers/playbook_compiler.py +154 -0
- higpertext/kernel/infrastructure/context_engine.py +303 -0
- higpertext/kernel/infrastructure/database/local_vector_store.py +99 -0
- higpertext/kernel/infrastructure/deployment/__init__.py +1 -0
- higpertext/kernel/infrastructure/deployment/resource_deployer.py +283 -0
- higpertext/kernel/infrastructure/diagnostics/__init__.py +1 -0
- higpertext/kernel/infrastructure/diagnostics/health.py +191 -0
- higpertext/kernel/infrastructure/env_runtime.py +227 -0
- higpertext/kernel/infrastructure/execution/__init__.py +1 -0
- higpertext/kernel/infrastructure/execution/parallel.py +188 -0
- higpertext/kernel/infrastructure/execution/resilience.py +155 -0
- higpertext/kernel/infrastructure/file_repositories.py +213 -0
- higpertext/kernel/infrastructure/governance.py +198 -0
- higpertext/kernel/infrastructure/hook_config_loader.py +53 -0
- higpertext/kernel/infrastructure/hook_webhook_dispatcher.py +61 -0
- higpertext/kernel/infrastructure/hook_workflow_bridge.py +60 -0
- higpertext/kernel/infrastructure/llm/__init__.py +6 -0
- higpertext/kernel/infrastructure/llm/provider.py +46 -0
- higpertext/kernel/infrastructure/llm/providers/__init__.py +0 -0
- higpertext/kernel/infrastructure/llm/providers/anthropic_provider.py +94 -0
- higpertext/kernel/infrastructure/llm/providers/gemini_embeddings.py +74 -0
- higpertext/kernel/infrastructure/llm/providers/gemini_provider.py +101 -0
- higpertext/kernel/infrastructure/llm/providers/ollama_provider.py +110 -0
- higpertext/kernel/infrastructure/llm/providers/openai_provider.py +98 -0
- higpertext/kernel/infrastructure/llm/registry.py +81 -0
- higpertext/kernel/infrastructure/logger.py +303 -0
- higpertext/kernel/infrastructure/output_store.py +70 -0
- higpertext/kernel/infrastructure/parser/__init__.py +1 -0
- higpertext/kernel/infrastructure/parser/code_chunker.py +144 -0
- higpertext/kernel/infrastructure/parser/language/__init__.py +14 -0
- higpertext/kernel/infrastructure/parser/language/base.py +41 -0
- higpertext/kernel/infrastructure/parser/language/powershell_parser.py +35 -0
- higpertext/kernel/infrastructure/parser/language/python_parser.py +98 -0
- higpertext/kernel/infrastructure/parser/language/typescript_parser.py +91 -0
- higpertext/kernel/infrastructure/parser/semantic_graph.py +409 -0
- higpertext/kernel/infrastructure/presentation/__init__.py +1 -0
- higpertext/kernel/infrastructure/presentation/html_renderer.py +137 -0
- higpertext/kernel/infrastructure/presentation/markdown_renderer.py +84 -0
- higpertext/kernel/infrastructure/presentation/markdown_report_renderer.py +97 -0
- higpertext/kernel/infrastructure/profile_store.py +28 -0
- higpertext/kernel/infrastructure/semantic_engine.py +289 -0
- higpertext/kernel/infrastructure/telemetry_reporter.py +132 -0
- higpertext/kernel/infrastructure/validation/__init__.py +1 -0
- higpertext/kernel/infrastructure/validation/contract_validator.py +163 -0
- higpertext/kernel/pkg_resources.py +38 -0
- higpertext/kernel/session_manager.py +319 -0
- higpertext/templates/env/generic-shell.yaml +21 -0
- higpertext/templates/env/node-vitest.yaml +27 -0
- higpertext/templates/env/python-pytest.yaml +29 -0
- higpertext/templates/html/commit_body.html +20 -0
- higpertext/templates/html/commit_diff.html +4 -0
- higpertext/templates/html/commit_index.html +29 -0
- higpertext/templates/html/commit_layer.html +11 -0
- higpertext/templates/html/commit_shell.html +28 -0
- higpertext/templates/html/graph_visualize.html +86 -0
- higpertext/templates/html/roadmap_body.html +12 -0
- higpertext/templates/html/roadmap_phase.html +5 -0
- higpertext/templates/html/roadmap_shell.html +29 -0
- higpertext/templates/markdown/commit_report.md +18 -0
- higpertext/templates/markdown/efficiency_report.md +12 -0
- higpertext/templates/markdown/roadmap_report.md +25 -0
- higpertext/templates/skills/best-practices.md +7 -0
- higpertext/templates/skills/clean-code.md +8 -0
- higpertext/templates/skills/ddd-standards.md +7 -0
- higpertext/templates/skills/tdd-practices.md +7 -0
- higpertext/templates/subagents/architect.md +7 -0
- higpertext/templates/subagents/test-engineer.md +7 -0
- higpertext/templates/workflows/build.json +23 -0
- higpertext/templates/workflows/compact.json +21 -0
- higpertext/templates/workflows/plan.json +59 -0
- higpertext/templates/workflows/review.json +26 -0
- higpertext/templates/workflows/spec.json +27 -0
- higpertext_cli-0.8.0.dist-info/METADATA +35 -0
- higpertext_cli-0.8.0.dist-info/RECORD +335 -0
- higpertext_cli-0.8.0.dist-info/WHEEL +5 -0
- higpertext_cli-0.8.0.dist-info/entry_points.txt +2 -0
- higpertext_cli-0.8.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""WorkflowHookBridge — traduce workflow_hooks de hooks_config.json a hooks ejecutables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from higpertext.kernel.infrastructure.hook_config_loader import load_hooks_config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class WorkflowHookEntry:
|
|
12
|
+
id: str
|
|
13
|
+
workflow_id: str
|
|
14
|
+
event: str
|
|
15
|
+
matcher: str
|
|
16
|
+
description: str
|
|
17
|
+
enabled: bool
|
|
18
|
+
assistants: list[str]
|
|
19
|
+
profiles: list[str]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkflowHookBridge:
|
|
23
|
+
def __init__(self, project_root: Path) -> None:
|
|
24
|
+
self.project_root = project_root
|
|
25
|
+
|
|
26
|
+
def get_active_workflow_hooks(self, assistant: str, profile: str) -> list[WorkflowHookEntry]:
|
|
27
|
+
config = self._load()
|
|
28
|
+
result: list[WorkflowHookEntry] = []
|
|
29
|
+
for raw in config.get("workflow_hooks", []):
|
|
30
|
+
if not raw.get("enabled", False):
|
|
31
|
+
continue
|
|
32
|
+
allowed_a = raw.get("assistants", [])
|
|
33
|
+
if allowed_a and assistant not in allowed_a:
|
|
34
|
+
continue
|
|
35
|
+
allowed_p = raw.get("profiles", [])
|
|
36
|
+
if allowed_p and profile not in allowed_p:
|
|
37
|
+
continue
|
|
38
|
+
result.append(
|
|
39
|
+
WorkflowHookEntry(
|
|
40
|
+
id=raw["id"],
|
|
41
|
+
workflow_id=raw["workflow_id"],
|
|
42
|
+
event=raw["event"],
|
|
43
|
+
matcher=raw.get("matcher", ""),
|
|
44
|
+
description=raw.get("description", ""),
|
|
45
|
+
enabled=raw.get("enabled", False),
|
|
46
|
+
assistants=raw.get("assistants", []),
|
|
47
|
+
profiles=raw.get("profiles", []),
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
def to_hook_script_args(self, entry: WorkflowHookEntry) -> str:
|
|
53
|
+
"""Genera el comando de shell que ejecuta el workflow desde el hook."""
|
|
54
|
+
return (
|
|
55
|
+
f"python3 src/higpertext/hooks/hook_tasks/workflow_runner_hook.py "
|
|
56
|
+
f"--workflow {entry.workflow_id}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _load(self) -> dict:
|
|
60
|
+
return load_hooks_config(self.project_root)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""LLMProvider Protocol — contrato que todo provider debe implementar."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Iterator, Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class LLMResponse:
|
|
10
|
+
content: str
|
|
11
|
+
model: str
|
|
12
|
+
provider: str
|
|
13
|
+
input_tokens: int = 0
|
|
14
|
+
output_tokens: int = 0
|
|
15
|
+
metadata: dict = field(default_factory=dict)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@runtime_checkable
|
|
19
|
+
class LLMProvider(Protocol):
|
|
20
|
+
"""Contrato de provider LLM. Soporta completion y streaming."""
|
|
21
|
+
|
|
22
|
+
provider_name: str
|
|
23
|
+
|
|
24
|
+
def complete(
|
|
25
|
+
self,
|
|
26
|
+
prompt: str,
|
|
27
|
+
*,
|
|
28
|
+
system: str = "",
|
|
29
|
+
model: str = "",
|
|
30
|
+
max_tokens: int = 1024,
|
|
31
|
+
temperature: float = 0.7,
|
|
32
|
+
**kwargs,
|
|
33
|
+
) -> LLMResponse: ...
|
|
34
|
+
|
|
35
|
+
def stream(
|
|
36
|
+
self,
|
|
37
|
+
prompt: str,
|
|
38
|
+
*,
|
|
39
|
+
system: str = "",
|
|
40
|
+
model: str = "",
|
|
41
|
+
max_tokens: int = 1024,
|
|
42
|
+
temperature: float = 0.7,
|
|
43
|
+
**kwargs,
|
|
44
|
+
) -> Iterator[str]: ...
|
|
45
|
+
|
|
46
|
+
def list_models(self) -> list[str]: ...
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""AnthropicProvider — implementación LLMProvider para Anthropic Claude."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import os
|
|
5
|
+
from typing import Iterator
|
|
6
|
+
|
|
7
|
+
from higpertext.kernel.infrastructure.llm.provider import LLMProvider, LLMResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnthropicProvider:
|
|
11
|
+
provider_name = "anthropic"
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
14
|
+
self._config = config or {}
|
|
15
|
+
self._default_model = self._config.get("model", "")
|
|
16
|
+
self._client = None
|
|
17
|
+
|
|
18
|
+
def _resolve_model(self, model: str) -> str:
|
|
19
|
+
resolved_model = model or self._default_model
|
|
20
|
+
if not resolved_model:
|
|
21
|
+
raise ValueError("Modelo Anthropic no definido. Indica --model o HIGPERTEXT_LLM_MODEL.")
|
|
22
|
+
return resolved_model
|
|
23
|
+
|
|
24
|
+
def _get_client(self):
|
|
25
|
+
if self._client is None:
|
|
26
|
+
try:
|
|
27
|
+
import anthropic
|
|
28
|
+
except ImportError:
|
|
29
|
+
raise ImportError("Instala el SDK: pip install anthropic")
|
|
30
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY") or self._config.get("api_key", "")
|
|
31
|
+
if not api_key:
|
|
32
|
+
raise ValueError("ANTHROPIC_API_KEY no está definida en el entorno")
|
|
33
|
+
self._client = anthropic.Anthropic(api_key=api_key)
|
|
34
|
+
return self._client
|
|
35
|
+
|
|
36
|
+
def complete(
|
|
37
|
+
self,
|
|
38
|
+
prompt: str,
|
|
39
|
+
*,
|
|
40
|
+
system: str = "",
|
|
41
|
+
model: str = "",
|
|
42
|
+
max_tokens: int = 1024,
|
|
43
|
+
temperature: float = 0.7,
|
|
44
|
+
**kwargs,
|
|
45
|
+
) -> LLMResponse:
|
|
46
|
+
client = self._get_client()
|
|
47
|
+
resolved_model = self._resolve_model(model)
|
|
48
|
+
msg = client.messages.create(
|
|
49
|
+
model=resolved_model,
|
|
50
|
+
max_tokens=max_tokens,
|
|
51
|
+
system=system or None,
|
|
52
|
+
messages=[{"role": "user", "content": prompt}],
|
|
53
|
+
temperature=temperature,
|
|
54
|
+
)
|
|
55
|
+
content = msg.content[0].text if msg.content else ""
|
|
56
|
+
return LLMResponse(
|
|
57
|
+
content=content,
|
|
58
|
+
model=resolved_model,
|
|
59
|
+
provider=self.provider_name,
|
|
60
|
+
input_tokens=msg.usage.input_tokens,
|
|
61
|
+
output_tokens=msg.usage.output_tokens,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def stream(
|
|
65
|
+
self,
|
|
66
|
+
prompt: str,
|
|
67
|
+
*,
|
|
68
|
+
system: str = "",
|
|
69
|
+
model: str = "",
|
|
70
|
+
max_tokens: int = 1024,
|
|
71
|
+
temperature: float = 0.7,
|
|
72
|
+
**kwargs,
|
|
73
|
+
) -> Iterator[str]:
|
|
74
|
+
client = self._get_client()
|
|
75
|
+
resolved_model = self._resolve_model(model)
|
|
76
|
+
with client.messages.stream(
|
|
77
|
+
model=resolved_model,
|
|
78
|
+
max_tokens=max_tokens,
|
|
79
|
+
system=system or None,
|
|
80
|
+
messages=[{"role": "user", "content": prompt}],
|
|
81
|
+
temperature=temperature,
|
|
82
|
+
) as stream:
|
|
83
|
+
for text in stream.text_stream:
|
|
84
|
+
yield text
|
|
85
|
+
|
|
86
|
+
def list_models(self) -> list[str]:
|
|
87
|
+
return [
|
|
88
|
+
"claude-sonnet-4-6",
|
|
89
|
+
"claude-opus-4-8",
|
|
90
|
+
"claude-haiku-4-5-20251001",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
assert isinstance(AnthropicProvider(), LLMProvider)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Gemini Embedding Provider implementation using native urllib requests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from higpertext.kernel.domain.rag import IEmbeddingProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GeminiEmbeddingProvider(IEmbeddingProvider):
|
|
12
|
+
"""Generates text embeddings using Google's Gemini API."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, project_root: Path) -> None:
|
|
15
|
+
self.project_root = project_root
|
|
16
|
+
self._api_key = self._load_api_key()
|
|
17
|
+
|
|
18
|
+
def _load_api_key(self) -> str:
|
|
19
|
+
# Intentar cargar desde el entorno
|
|
20
|
+
import os
|
|
21
|
+
key = os.environ.get("GEMINI_API_KEY")
|
|
22
|
+
if key:
|
|
23
|
+
return key
|
|
24
|
+
|
|
25
|
+
# Intentar leer del environment.json
|
|
26
|
+
env_file = self.project_root / ".higpertext" / "config" / "environment.json"
|
|
27
|
+
if env_file.exists():
|
|
28
|
+
try:
|
|
29
|
+
data = json.loads(env_file.read_text(encoding="utf-8"))
|
|
30
|
+
return data.get("GEMINI_API_KEY", "")
|
|
31
|
+
except (OSError, json.JSONDecodeError):
|
|
32
|
+
pass
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
def get_embedding(self, text: str) -> list[float]:
|
|
36
|
+
if not self._api_key:
|
|
37
|
+
raise ValueError("[ERROR] No se configuró la variable GEMINI_API_KEY para RAG.")
|
|
38
|
+
|
|
39
|
+
url = f"https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key={self._api_key}"
|
|
40
|
+
payload = {
|
|
41
|
+
"model": "models/text-embedding-004",
|
|
42
|
+
"content": {
|
|
43
|
+
"parts": [{"text": text}]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
req = urllib.request.Request(
|
|
48
|
+
url,
|
|
49
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
50
|
+
headers={"Content-Type": "application/json"},
|
|
51
|
+
method="POST"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
56
|
+
res_data = json.loads(response.read().decode("utf-8"))
|
|
57
|
+
return res_data["embedding"]["values"]
|
|
58
|
+
except urllib.error.HTTPError as e:
|
|
59
|
+
err_body = e.read().decode("utf-8")
|
|
60
|
+
raise RuntimeError(f"[ERROR] API de Embeddings devolvió HTTP {e.code}: {err_body}")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
raise RuntimeError(f"[ERROR] Falló conexión a la API de Embeddings: {e}")
|
|
63
|
+
|
|
64
|
+
def get_embeddings_batch(self, texts: list[str]) -> list[list[float]]:
|
|
65
|
+
# La API de Gemini permite procesar listas si se llama adecuadamente o secuencial.
|
|
66
|
+
# Por seguridad y límites de API, lo implementamos secuencial pero mapeado.
|
|
67
|
+
embeddings: list[list[float]] = []
|
|
68
|
+
for text in texts:
|
|
69
|
+
if not text.strip():
|
|
70
|
+
# Embedding vacío por defecto
|
|
71
|
+
embeddings.append([0.0] * 768)
|
|
72
|
+
else:
|
|
73
|
+
embeddings.append(self.get_embedding(text))
|
|
74
|
+
return embeddings
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""GeminiProvider — implementación LLMProvider para Google Gemini."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import os
|
|
5
|
+
from typing import Iterator
|
|
6
|
+
|
|
7
|
+
from higpertext.kernel.infrastructure.llm.provider import LLMProvider, LLMResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeminiProvider:
|
|
11
|
+
provider_name = "gemini"
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
14
|
+
self._config = config or {}
|
|
15
|
+
self._default_model = self._config.get("model", "")
|
|
16
|
+
self._client = None
|
|
17
|
+
|
|
18
|
+
def _resolve_model(self, model: str) -> str:
|
|
19
|
+
resolved_model = model or self._default_model
|
|
20
|
+
if not resolved_model:
|
|
21
|
+
raise ValueError("Modelo Gemini no definido. Indica --model o HIGPERTEXT_LLM_MODEL.")
|
|
22
|
+
return resolved_model
|
|
23
|
+
|
|
24
|
+
def _get_client(self):
|
|
25
|
+
if self._client is None:
|
|
26
|
+
try:
|
|
27
|
+
from google import genai
|
|
28
|
+
except ImportError:
|
|
29
|
+
raise ImportError("Instala el SDK: pip install google-genai")
|
|
30
|
+
api_key = os.environ.get("GEMINI_API_KEY") or self._config.get("api_key", "")
|
|
31
|
+
if not api_key:
|
|
32
|
+
raise ValueError("GEMINI_API_KEY no está definida en el entorno")
|
|
33
|
+
self._client = genai.Client(api_key=api_key)
|
|
34
|
+
return self._client
|
|
35
|
+
|
|
36
|
+
def complete(
|
|
37
|
+
self,
|
|
38
|
+
prompt: str,
|
|
39
|
+
*,
|
|
40
|
+
system: str = "",
|
|
41
|
+
model: str = "",
|
|
42
|
+
max_tokens: int = 1024,
|
|
43
|
+
temperature: float = 0.7,
|
|
44
|
+
**kwargs,
|
|
45
|
+
) -> LLMResponse:
|
|
46
|
+
from google.genai import types
|
|
47
|
+
|
|
48
|
+
client = self._get_client()
|
|
49
|
+
resolved_model = self._resolve_model(model)
|
|
50
|
+
config = types.GenerateContentConfig(
|
|
51
|
+
max_output_tokens=max_tokens,
|
|
52
|
+
temperature=temperature,
|
|
53
|
+
system_instruction=system or None,
|
|
54
|
+
)
|
|
55
|
+
resp = client.models.generate_content(
|
|
56
|
+
model=resolved_model,
|
|
57
|
+
contents=prompt,
|
|
58
|
+
config=config,
|
|
59
|
+
)
|
|
60
|
+
content = resp.text or ""
|
|
61
|
+
usage = resp.usage_metadata
|
|
62
|
+
return LLMResponse(
|
|
63
|
+
content=content,
|
|
64
|
+
model=resolved_model,
|
|
65
|
+
provider=self.provider_name,
|
|
66
|
+
input_tokens=usage.prompt_token_count if usage else 0,
|
|
67
|
+
output_tokens=usage.candidates_token_count if usage else 0,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def stream(
|
|
71
|
+
self,
|
|
72
|
+
prompt: str,
|
|
73
|
+
*,
|
|
74
|
+
system: str = "",
|
|
75
|
+
model: str = "",
|
|
76
|
+
max_tokens: int = 1024,
|
|
77
|
+
temperature: float = 0.7,
|
|
78
|
+
**kwargs,
|
|
79
|
+
) -> Iterator[str]:
|
|
80
|
+
from google.genai import types
|
|
81
|
+
|
|
82
|
+
client = self._get_client()
|
|
83
|
+
resolved_model = self._resolve_model(model)
|
|
84
|
+
config = types.GenerateContentConfig(
|
|
85
|
+
max_output_tokens=max_tokens,
|
|
86
|
+
temperature=temperature,
|
|
87
|
+
system_instruction=system or None,
|
|
88
|
+
)
|
|
89
|
+
for chunk in client.models.generate_content_stream(
|
|
90
|
+
model=resolved_model,
|
|
91
|
+
contents=prompt,
|
|
92
|
+
config=config,
|
|
93
|
+
):
|
|
94
|
+
if chunk.text:
|
|
95
|
+
yield chunk.text
|
|
96
|
+
|
|
97
|
+
def list_models(self) -> list[str]:
|
|
98
|
+
return ["gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
assert isinstance(GeminiProvider(), LLMProvider)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""OllamaProvider — implementación LLMProvider para Ollama (Llama local)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
7
|
+
from typing import Iterator
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.infrastructure.llm.provider import LLMProvider, LLMResponse
|
|
10
|
+
from higpertext.kernel.app_config import OLLAMA_BASE_URL as _DEFAULT_BASE_URL
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OllamaProvider:
|
|
14
|
+
provider_name = "ollama"
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
17
|
+
self._config = config or {}
|
|
18
|
+
self._default_model = self._config.get("model", "")
|
|
19
|
+
self._base_url = self._config.get("base_url", _DEFAULT_BASE_URL).rstrip("/")
|
|
20
|
+
|
|
21
|
+
def _resolve_model(self, model: str) -> str:
|
|
22
|
+
resolved_model = model or self._default_model
|
|
23
|
+
if not resolved_model:
|
|
24
|
+
raise ValueError("Modelo Ollama no definido. Indica --model o HIGPERTEXT_LLM_MODEL.")
|
|
25
|
+
return resolved_model
|
|
26
|
+
|
|
27
|
+
def _post(self, endpoint: str, payload: dict) -> urllib.request.http.client.HTTPResponse:
|
|
28
|
+
url = f"{self._base_url}{endpoint}"
|
|
29
|
+
data = json.dumps(payload).encode("utf-8")
|
|
30
|
+
req = urllib.request.Request(
|
|
31
|
+
url,
|
|
32
|
+
data=data,
|
|
33
|
+
headers={"Content-Type": "application/json"},
|
|
34
|
+
method="POST",
|
|
35
|
+
)
|
|
36
|
+
return urllib.request.urlopen(req, timeout=120) # nosec B310
|
|
37
|
+
|
|
38
|
+
def complete(
|
|
39
|
+
self,
|
|
40
|
+
prompt: str,
|
|
41
|
+
*,
|
|
42
|
+
system: str = "",
|
|
43
|
+
model: str = "",
|
|
44
|
+
max_tokens: int = 1024,
|
|
45
|
+
temperature: float = 0.7,
|
|
46
|
+
**kwargs,
|
|
47
|
+
) -> LLMResponse:
|
|
48
|
+
resolved_model = self._resolve_model(model)
|
|
49
|
+
messages = []
|
|
50
|
+
if system:
|
|
51
|
+
messages.append({"role": "system", "content": system})
|
|
52
|
+
messages.append({"role": "user", "content": prompt})
|
|
53
|
+
payload = {
|
|
54
|
+
"model": resolved_model,
|
|
55
|
+
"messages": messages,
|
|
56
|
+
"stream": False,
|
|
57
|
+
"options": {"temperature": temperature, "num_predict": max_tokens},
|
|
58
|
+
}
|
|
59
|
+
with self._post("/api/chat", payload) as resp:
|
|
60
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
61
|
+
content = data.get("message", {}).get("content", "")
|
|
62
|
+
return LLMResponse(
|
|
63
|
+
content=content,
|
|
64
|
+
model=resolved_model,
|
|
65
|
+
provider=self.provider_name,
|
|
66
|
+
input_tokens=data.get("prompt_eval_count", 0),
|
|
67
|
+
output_tokens=data.get("eval_count", 0),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def stream(
|
|
71
|
+
self,
|
|
72
|
+
prompt: str,
|
|
73
|
+
*,
|
|
74
|
+
system: str = "",
|
|
75
|
+
model: str = "",
|
|
76
|
+
max_tokens: int = 1024,
|
|
77
|
+
temperature: float = 0.7,
|
|
78
|
+
**kwargs,
|
|
79
|
+
) -> Iterator[str]:
|
|
80
|
+
resolved_model = self._resolve_model(model)
|
|
81
|
+
messages = []
|
|
82
|
+
if system:
|
|
83
|
+
messages.append({"role": "system", "content": system})
|
|
84
|
+
messages.append({"role": "user", "content": prompt})
|
|
85
|
+
payload = {
|
|
86
|
+
"model": resolved_model,
|
|
87
|
+
"messages": messages,
|
|
88
|
+
"stream": True,
|
|
89
|
+
"options": {"temperature": temperature, "num_predict": max_tokens},
|
|
90
|
+
}
|
|
91
|
+
with self._post("/api/chat", payload) as resp:
|
|
92
|
+
for line in resp:
|
|
93
|
+
chunk = json.loads(line.decode("utf-8"))
|
|
94
|
+
text = chunk.get("message", {}).get("content", "")
|
|
95
|
+
if text:
|
|
96
|
+
yield text
|
|
97
|
+
if chunk.get("done"):
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
def list_models(self) -> list[str]:
|
|
101
|
+
try:
|
|
102
|
+
url = f"{self._base_url}/api/tags"
|
|
103
|
+
with urllib.request.urlopen(url, timeout=5) as resp: # nosec B310
|
|
104
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
105
|
+
return [m["name"] for m in data.get("models", [])]
|
|
106
|
+
except Exception:
|
|
107
|
+
return []
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
assert isinstance(OllamaProvider(), LLMProvider)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""OpenAIProvider — implementación LLMProvider para OpenAI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import os
|
|
5
|
+
from typing import Iterator
|
|
6
|
+
|
|
7
|
+
from higpertext.kernel.infrastructure.llm.provider import LLMProvider, LLMResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenAIProvider:
|
|
11
|
+
provider_name = "openai"
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: dict | None = None) -> None:
|
|
14
|
+
self._config = config or {}
|
|
15
|
+
self._default_model = self._config.get("model", "")
|
|
16
|
+
self._client = None
|
|
17
|
+
|
|
18
|
+
def _resolve_model(self, model: str) -> str:
|
|
19
|
+
resolved_model = model or self._default_model
|
|
20
|
+
if not resolved_model:
|
|
21
|
+
raise ValueError("Modelo OpenAI no definido. Indica --model o HIGPERTEXT_LLM_MODEL.")
|
|
22
|
+
return resolved_model
|
|
23
|
+
|
|
24
|
+
def _get_client(self):
|
|
25
|
+
if self._client is None:
|
|
26
|
+
try:
|
|
27
|
+
import openai
|
|
28
|
+
except ImportError:
|
|
29
|
+
raise ImportError("Instala el SDK: pip install openai")
|
|
30
|
+
api_key = os.environ.get("OPENAI_API_KEY") or self._config.get("api_key", "")
|
|
31
|
+
if not api_key:
|
|
32
|
+
raise ValueError("OPENAI_API_KEY no está definida en el entorno")
|
|
33
|
+
self._client = openai.OpenAI(api_key=api_key)
|
|
34
|
+
return self._client
|
|
35
|
+
|
|
36
|
+
def complete(
|
|
37
|
+
self,
|
|
38
|
+
prompt: str,
|
|
39
|
+
*,
|
|
40
|
+
system: str = "",
|
|
41
|
+
model: str = "",
|
|
42
|
+
max_tokens: int = 1024,
|
|
43
|
+
temperature: float = 0.7,
|
|
44
|
+
**kwargs,
|
|
45
|
+
) -> LLMResponse:
|
|
46
|
+
client = self._get_client()
|
|
47
|
+
resolved_model = self._resolve_model(model)
|
|
48
|
+
messages = []
|
|
49
|
+
if system:
|
|
50
|
+
messages.append({"role": "system", "content": system})
|
|
51
|
+
messages.append({"role": "user", "content": prompt})
|
|
52
|
+
resp = client.chat.completions.create(
|
|
53
|
+
model=resolved_model,
|
|
54
|
+
messages=messages,
|
|
55
|
+
max_tokens=max_tokens,
|
|
56
|
+
temperature=temperature,
|
|
57
|
+
)
|
|
58
|
+
content = resp.choices[0].message.content or ""
|
|
59
|
+
return LLMResponse(
|
|
60
|
+
content=content,
|
|
61
|
+
model=resolved_model,
|
|
62
|
+
provider=self.provider_name,
|
|
63
|
+
input_tokens=resp.usage.prompt_tokens if resp.usage else 0,
|
|
64
|
+
output_tokens=resp.usage.completion_tokens if resp.usage else 0,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def stream(
|
|
68
|
+
self,
|
|
69
|
+
prompt: str,
|
|
70
|
+
*,
|
|
71
|
+
system: str = "",
|
|
72
|
+
model: str = "",
|
|
73
|
+
max_tokens: int = 1024,
|
|
74
|
+
temperature: float = 0.7,
|
|
75
|
+
**kwargs,
|
|
76
|
+
) -> Iterator[str]:
|
|
77
|
+
client = self._get_client()
|
|
78
|
+
resolved_model = self._resolve_model(model)
|
|
79
|
+
messages = []
|
|
80
|
+
if system:
|
|
81
|
+
messages.append({"role": "system", "content": system})
|
|
82
|
+
messages.append({"role": "user", "content": prompt})
|
|
83
|
+
for chunk in client.chat.completions.create(
|
|
84
|
+
model=resolved_model,
|
|
85
|
+
messages=messages,
|
|
86
|
+
max_tokens=max_tokens,
|
|
87
|
+
temperature=temperature,
|
|
88
|
+
stream=True,
|
|
89
|
+
):
|
|
90
|
+
delta = chunk.choices[0].delta.content
|
|
91
|
+
if delta:
|
|
92
|
+
yield delta
|
|
93
|
+
|
|
94
|
+
def list_models(self) -> list[str]:
|
|
95
|
+
return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
assert isinstance(OpenAIProvider(), LLMProvider)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""LLMRegistry — resuelve e instancia el provider correcto por nombre."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .provider import LLMProvider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_PROVIDER_MODULES: dict[str, tuple[str, str]] = {
|
|
15
|
+
"anthropic": (
|
|
16
|
+
"kernel.infrastructure.llm.providers.anthropic_provider",
|
|
17
|
+
"AnthropicProvider",
|
|
18
|
+
),
|
|
19
|
+
"openai": ("kernel.infrastructure.llm.providers.openai_provider", "OpenAIProvider"),
|
|
20
|
+
"gemini": ("kernel.infrastructure.llm.providers.gemini_provider", "GeminiProvider"),
|
|
21
|
+
"ollama": ("kernel.infrastructure.llm.providers.ollama_provider", "OllamaProvider"),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LLMRegistry:
|
|
26
|
+
"""Resuelve el provider LLM desde environment.json o variable de entorno."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, project_root: Path | None = None) -> None:
|
|
29
|
+
self._root = project_root or Path.cwd()
|
|
30
|
+
self._cache: dict[str, "LLMProvider"] = {}
|
|
31
|
+
|
|
32
|
+
def _load_env_config(self) -> dict:
|
|
33
|
+
env_path = self._root / WORKSPACE_DIR_NAME / "config" / "environment.json"
|
|
34
|
+
try:
|
|
35
|
+
return json.loads(env_path.read_text(encoding="utf-8"))
|
|
36
|
+
except (OSError, json.JSONDecodeError):
|
|
37
|
+
return {}
|
|
38
|
+
|
|
39
|
+
def default_provider(self) -> str:
|
|
40
|
+
env_var = os.environ.get("HIGPERTEXT_LLM_PROVIDER")
|
|
41
|
+
if env_var:
|
|
42
|
+
return env_var.lower()
|
|
43
|
+
cfg = self._load_env_config()
|
|
44
|
+
return cfg.get("llm", {}).get("default_provider", "")
|
|
45
|
+
|
|
46
|
+
def default_model(self, provider: str) -> str:
|
|
47
|
+
env_var = os.environ.get("HIGPERTEXT_LLM_MODEL")
|
|
48
|
+
if env_var:
|
|
49
|
+
return env_var
|
|
50
|
+
cfg = self._load_env_config()
|
|
51
|
+
provider_cfg = cfg.get("llm", {}).get("providers", {}).get(provider, {})
|
|
52
|
+
return provider_cfg.get("model", "")
|
|
53
|
+
|
|
54
|
+
def provider_config(self, provider: str) -> dict:
|
|
55
|
+
cfg = self._load_env_config()
|
|
56
|
+
return cfg.get("llm", {}).get("providers", {}).get(provider, {})
|
|
57
|
+
|
|
58
|
+
def resolve(self, provider_name: str | None = None) -> "LLMProvider":
|
|
59
|
+
name = (provider_name or self.default_provider()).lower()
|
|
60
|
+
if not name:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"Provider LLM no definido. Usa --provider, HIGPERTEXT_LLM_PROVIDER "
|
|
63
|
+
"o .higpertext/config/environment.json[llm.default_provider]."
|
|
64
|
+
)
|
|
65
|
+
if name in self._cache:
|
|
66
|
+
return self._cache[name]
|
|
67
|
+
|
|
68
|
+
if name not in _PROVIDER_MODULES:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Provider '{name}' no soportado. Disponibles: {list(_PROVIDER_MODULES)}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
module_path, class_name = _PROVIDER_MODULES[name]
|
|
74
|
+
import importlib
|
|
75
|
+
|
|
76
|
+
module = importlib.import_module(module_path)
|
|
77
|
+
provider_cls = getattr(module, class_name)
|
|
78
|
+
config = self.provider_config(name)
|
|
79
|
+
instance: "LLMProvider" = provider_cls(config=config)
|
|
80
|
+
self._cache[name] = instance
|
|
81
|
+
return instance
|