multiagentos 0.4.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.
- agents/__init__.py +1 -0
- agents/catalog.py +52 -0
- agents/registry.py +11 -0
- core/__init__.py +38 -0
- core/artifacts.py +47 -0
- core/chat_agent_bridge.py +280 -0
- core/chat_agent_registry.py +61 -0
- core/chat_agent_router.py +85 -0
- core/chat_session.py +62 -0
- core/contracts/__init__.py +22 -0
- core/contracts/agent.py +21 -0
- core/contracts/ai.py +25 -0
- core/contracts/capability.py +44 -0
- core/contracts/chat_agent.py +34 -0
- core/contracts/checkpoint.py +78 -0
- core/contracts/discovery.py +30 -0
- core/contracts/execution.py +28 -0
- core/contracts/github.py +74 -0
- core/contracts/handoff.py +56 -0
- core/contracts/health.py +206 -0
- core/contracts/human_review.py +20 -0
- core/contracts/ide.py +113 -0
- core/contracts/mcp.py +76 -0
- core/contracts/model_runtime.py +25 -0
- core/contracts/planning.py +25 -0
- core/contracts/profile.py +66 -0
- core/contracts/quota.py +62 -0
- core/contracts/repository.py +34 -0
- core/contracts/resume.py +55 -0
- core/contracts/runtime.py +19 -0
- core/contracts/vyrelon_runtime.py +118 -0
- core/contracts/work_unit.py +48 -0
- core/delegation.py +33 -0
- core/handoff.py +68 -0
- core/lifecycle.py +85 -0
- core/multi_agent_workflow.py +574 -0
- core/orchestrator.py +51 -0
- core/planning.py +22 -0
- core/registry.py +44 -0
- core/routing.py +195 -0
- core/state.py +155 -0
- installer/__init__.py +1 -0
- installer/init.py +71 -0
- integrations/__init__.py +0 -0
- integrations/github/__init__.py +3 -0
- integrations/github/gateway.py +164 -0
- integrations/openai/__init__.py +1 -0
- integrations/openai/chat_agent.py +147 -0
- multiagentos/__init__.py +1 -0
- multiagentos/cli.py +759 -0
- multiagentos-0.4.0.dist-info/METADATA +7 -0
- multiagentos-0.4.0.dist-info/RECORD +122 -0
- multiagentos-0.4.0.dist-info/WHEEL +5 -0
- multiagentos-0.4.0.dist-info/entry_points.txt +2 -0
- multiagentos-0.4.0.dist-info/top_level.txt +7 -0
- profiles/__init__.py +1 -0
- profiles/common.py +11 -0
- profiles/detector.py +67 -0
- profiles/registry.py +46 -0
- profiles/resolver.py +63 -0
- runtime/__init__.py +5 -0
- runtime/agent/__init__.py +1 -0
- runtime/agent/ide.py +74 -0
- runtime/agent/model.py +197 -0
- runtime/agent/model_backed.py +49 -0
- runtime/agent/process.py +21 -0
- runtime/builtin_tools.py +57 -0
- runtime/capability.py +143 -0
- runtime/chat_adapter_registry.py +43 -0
- runtime/chat_config.py +57 -0
- runtime/chat_execution.py +52 -0
- runtime/config/__init__.py +1 -0
- runtime/config/loader.py +67 -0
- runtime/discovery.py +334 -0
- runtime/execution_config.py +71 -0
- runtime/execution_registry.py +50 -0
- runtime/git.py +84 -0
- runtime/github.py +56 -0
- runtime/github_probe.py +23 -0
- runtime/health.py +14 -0
- runtime/ide/__init__.py +6 -0
- runtime/ide/bridge.py +198 -0
- runtime/ide/registry.py +24 -0
- runtime/ide/runtime.py +35 -0
- runtime/local/__init__.py +18 -0
- runtime/local/filesystem.py +58 -0
- runtime/local/patch.py +79 -0
- runtime/local/path_security.py +31 -0
- runtime/local/permissions.py +20 -0
- runtime/local/process.py +56 -0
- runtime/local/shell.py +97 -0
- runtime/mcp/__init__.py +7 -0
- runtime/mcp/client.py +120 -0
- runtime/mcp/config.py +36 -0
- runtime/mcp/policy.py +57 -0
- runtime/mcp/proxy.py +30 -0
- runtime/mcp/server.py +137 -0
- runtime/mcp/session.py +19 -0
- runtime/model/__init__.py +5 -0
- runtime/model/ai_runtime.py +165 -0
- runtime/model/cli.py +49 -0
- runtime/model/config.py +98 -0
- runtime/model/credentials.py +42 -0
- runtime/model/factory.py +82 -0
- runtime/model/http.py +78 -0
- runtime/model/invoker.py +16 -0
- runtime/model/materializer.py +101 -0
- runtime/model/native.py +244 -0
- runtime/model/providers.py +55 -0
- runtime/model/registry.py +26 -0
- runtime/model/runtime_loader.py +51 -0
- runtime/model_control.py +136 -0
- runtime/multi_agent.py +142 -0
- runtime/policy.py +28 -0
- runtime/process.py +41 -0
- runtime/quota.py +259 -0
- runtime/repository.py +103 -0
- runtime/repository_tools.py +62 -0
- runtime/status.py +87 -0
- runtime/tool_calling.py +134 -0
- runtime/validation.py +121 -0
- runtime/vyrelon.py +1557 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Agent executor backed by the provider-neutral model runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from core.contracts.agent import AgentContract
|
|
8
|
+
from core.contracts.ai import ModelSpec
|
|
9
|
+
from core.contracts.model_runtime import ModelRequest, ModelResponse
|
|
10
|
+
from core.contracts.work_unit import WorkUnit
|
|
11
|
+
from core.registry import AIRegistry
|
|
12
|
+
from runtime.model.invoker import ModelInvoker
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ModelBackedAgentExecutor:
|
|
16
|
+
"""Turn an assigned model into an executable VYRELON Agent."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, ai_registry: AIRegistry, model_invoker: ModelInvoker) -> None:
|
|
19
|
+
self.ai_registry = ai_registry
|
|
20
|
+
self.model_invoker = model_invoker
|
|
21
|
+
|
|
22
|
+
def execute(
|
|
23
|
+
self, *, agent: AgentContract, model_id: str, work_unit: WorkUnit
|
|
24
|
+
) -> ModelResponse:
|
|
25
|
+
model = self.ai_registry.model(model_id)
|
|
26
|
+
request = ModelRequest(
|
|
27
|
+
system=self._system_prompt(agent),
|
|
28
|
+
prompt=self._work_prompt(work_unit),
|
|
29
|
+
metadata={
|
|
30
|
+
"agent_id": agent.id,
|
|
31
|
+
"role": agent.role,
|
|
32
|
+
"work_unit_id": work_unit.id,
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
return self.model_invoker.generate(model, request)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _system_prompt(agent: AgentContract) -> str:
|
|
39
|
+
capabilities = ", ".join(sorted(agent.capabilities)) or "general development"
|
|
40
|
+
return (
|
|
41
|
+
f"You are the VYRELON {agent.role} agent. "
|
|
42
|
+
f"Your capabilities are: {capabilities}. "
|
|
43
|
+
"Work only within the objective and permissions provided by the orchestrator."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _work_prompt(work_unit: WorkUnit) -> str:
|
|
48
|
+
inputs = json.dumps(work_unit.inputs, ensure_ascii=False, sort_keys=True, default=str)
|
|
49
|
+
return f"Objective: {work_unit.objective}\nInputs: {inputs}"
|
runtime/agent/process.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Process-backed AgentExecutor for the VYRELON lifecycle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from core.contracts.agent import AgentContract
|
|
6
|
+
from core.contracts.work_unit import WorkUnit
|
|
7
|
+
from runtime.process import ProcessResult, ProcessRuntime
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProcessAgentExecutor:
|
|
11
|
+
"""Execute an agent's work through the local process runtime."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, command: list[str]) -> None:
|
|
14
|
+
self.command = list(command)
|
|
15
|
+
|
|
16
|
+
def execute(
|
|
17
|
+
self, *, agent: AgentContract, model_id: str, work_unit: WorkUnit
|
|
18
|
+
) -> ProcessResult:
|
|
19
|
+
work_unit.metadata["executor_agent"] = agent.id
|
|
20
|
+
work_unit.metadata["model_id"] = model_id
|
|
21
|
+
return ProcessRuntime().run(self.command, cwd=work_unit.metadata.get("cwd"))
|
runtime/builtin_tools.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Built-in VYRELON tool bindings."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from core.contracts.vyrelon_runtime import ToolRequest, ToolSideEffect, ToolSpec
|
|
6
|
+
from runtime.local.filesystem import FilesystemRuntime
|
|
7
|
+
from runtime.local.patch import PatchRuntime
|
|
8
|
+
from runtime.local.shell import PersistentShellRuntime
|
|
9
|
+
from runtime.tool_calling import ToolRuntime
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class BuiltinToolBindings:
|
|
13
|
+
project_root: str
|
|
14
|
+
runtime: ToolRuntime
|
|
15
|
+
filesystem: FilesystemRuntime | None = None
|
|
16
|
+
patch: PatchRuntime | None = None
|
|
17
|
+
shell: PersistentShellRuntime | None = None
|
|
18
|
+
|
|
19
|
+
def __post_init__(self) -> None:
|
|
20
|
+
self.filesystem = self.filesystem or FilesystemRuntime(policy=self.runtime.policy)
|
|
21
|
+
self.patch = self.patch or PatchRuntime(policy=self.runtime.policy)
|
|
22
|
+
self.shell = self.shell or PersistentShellRuntime(self.project_root, policy=self.runtime.policy)
|
|
23
|
+
self._register()
|
|
24
|
+
|
|
25
|
+
def _register(self) -> None:
|
|
26
|
+
self.runtime.register(ToolSpec("filesystem.read","Read a text file inside the project root.",ToolSideEffect.READ,input_schema={"type":"object","required":["path"]}),self._read)
|
|
27
|
+
self.runtime.register(ToolSpec("filesystem.write","Write text to a file inside the project root.",ToolSideEffect.WRITE,frozenset({"filesystem.write"}),{"type":"object","required":["path","content"]}),self._write)
|
|
28
|
+
self.runtime.register(ToolSpec("patch.apply","Check and apply a unified Git patch to the project.",ToolSideEffect.WRITE,frozenset({"filesystem.write"}),{"type":"object","required":["patch"]}),self._patch)
|
|
29
|
+
self.runtime.register(ToolSpec("shell.run","Run a command in the project root.",ToolSideEffect.EXECUTE,frozenset({"process"}),{"type":"object","required":["command"]}),self._shell)
|
|
30
|
+
|
|
31
|
+
def _path(self, request: ToolRequest) -> str:
|
|
32
|
+
value=request.arguments.get("path")
|
|
33
|
+
if not isinstance(value,str) or not value: raise ValueError("path must be a non-empty string")
|
|
34
|
+
return str(Path(self.project_root)/value)
|
|
35
|
+
|
|
36
|
+
def _approved(self, request: ToolRequest) -> bool:
|
|
37
|
+
return request.metadata.get("approved") is True
|
|
38
|
+
|
|
39
|
+
def _read(self, request: ToolRequest) -> object:
|
|
40
|
+
return self.filesystem.read_text(self._path(request))
|
|
41
|
+
|
|
42
|
+
def _write(self, request: ToolRequest) -> object:
|
|
43
|
+
content=request.arguments.get("content")
|
|
44
|
+
if not isinstance(content,str): raise ValueError("content must be a string")
|
|
45
|
+
return str(self.filesystem.write_text(self._path(request),content,approved=self._approved(request)))
|
|
46
|
+
|
|
47
|
+
def _patch(self, request: ToolRequest) -> object:
|
|
48
|
+
patch=request.arguments.get("patch")
|
|
49
|
+
if not isinstance(patch,str) or not patch.strip(): raise ValueError("patch must be a non-empty string")
|
|
50
|
+
result=self.patch.apply(self.project_root,patch,approved=self._approved(request))
|
|
51
|
+
return {"returncode":result.returncode,"stdout":result.stdout,"stderr":result.stderr}
|
|
52
|
+
|
|
53
|
+
def _shell(self, request: ToolRequest) -> object:
|
|
54
|
+
command=request.arguments.get("command")
|
|
55
|
+
if not isinstance(command,str) or not command.strip(): raise ValueError("command must be a non-empty string")
|
|
56
|
+
result=self.shell.run(command)
|
|
57
|
+
return {"returncode":result.returncode,"stdout":result.stdout,"stderr":result.stderr}
|
runtime/capability.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Persistent model capability registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from core.contracts.ai import ModelSpec
|
|
9
|
+
from core.contracts.capability import (
|
|
10
|
+
CapabilityConfidence,
|
|
11
|
+
CapabilityMatch,
|
|
12
|
+
ModelCapabilityProfile,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CapabilityStore:
|
|
17
|
+
"""Project-scoped JSON persistence for normalized model capabilities."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, root: Path) -> None:
|
|
20
|
+
self.root = Path(root)
|
|
21
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
def path(self, model_id: str) -> Path:
|
|
24
|
+
safe = model_id.replace("/", "__").replace(":", "__")
|
|
25
|
+
return self.root / f"{safe}.json"
|
|
26
|
+
|
|
27
|
+
def exists(self, model_id: str) -> bool:
|
|
28
|
+
return self.path(model_id).exists()
|
|
29
|
+
|
|
30
|
+
def save(self, profile: ModelCapabilityProfile) -> None:
|
|
31
|
+
self.path(profile.model_id).write_text(
|
|
32
|
+
json.dumps(
|
|
33
|
+
{
|
|
34
|
+
"model_id": profile.model_id,
|
|
35
|
+
"provider_id": profile.provider_id,
|
|
36
|
+
"capabilities": sorted(profile.capabilities),
|
|
37
|
+
"confidence": profile.confidence.value,
|
|
38
|
+
"source": profile.source,
|
|
39
|
+
"metadata": profile.metadata,
|
|
40
|
+
},
|
|
41
|
+
indent=2,
|
|
42
|
+
default=str,
|
|
43
|
+
),
|
|
44
|
+
encoding="utf-8",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def load(self, model_id: str) -> ModelCapabilityProfile:
|
|
48
|
+
data = json.loads(self.path(model_id).read_text(encoding="utf-8"))
|
|
49
|
+
return ModelCapabilityProfile(
|
|
50
|
+
model_id=str(data["model_id"]),
|
|
51
|
+
provider_id=str(data["provider_id"]),
|
|
52
|
+
capabilities=frozenset(str(item) for item in data.get("capabilities", [])),
|
|
53
|
+
confidence=CapabilityConfidence(str(data.get("confidence", "unknown"))),
|
|
54
|
+
source=str(data.get("source", "unknown")),
|
|
55
|
+
metadata=dict(data.get("metadata", {})),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CapabilityRegistry:
|
|
60
|
+
"""Resolve and persist model capability profiles."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, store: CapabilityStore) -> None:
|
|
63
|
+
self.store = store
|
|
64
|
+
|
|
65
|
+
def profile(self, model: ModelSpec) -> ModelCapabilityProfile:
|
|
66
|
+
if self.store.exists(model.id):
|
|
67
|
+
stored = self.store.load(model.id)
|
|
68
|
+
if stored.provider_id == model.provider_id:
|
|
69
|
+
return stored
|
|
70
|
+
capabilities = frozenset(str(item) for item in model.capabilities)
|
|
71
|
+
profile = ModelCapabilityProfile(
|
|
72
|
+
model_id=model.id,
|
|
73
|
+
provider_id=model.provider_id,
|
|
74
|
+
capabilities=capabilities,
|
|
75
|
+
confidence=CapabilityConfidence.DECLARED,
|
|
76
|
+
source="model_spec",
|
|
77
|
+
)
|
|
78
|
+
self.store.save(profile)
|
|
79
|
+
return profile
|
|
80
|
+
|
|
81
|
+
def merge(self, profile: ModelCapabilityProfile) -> ModelCapabilityProfile:
|
|
82
|
+
"""Merge a discovered or observed profile with the persisted profile."""
|
|
83
|
+
if self.store.exists(profile.model_id):
|
|
84
|
+
current = self.store.load(profile.model_id)
|
|
85
|
+
if current.provider_id != profile.provider_id:
|
|
86
|
+
raise ValueError(f"Capability provider mismatch for {profile.model_id}")
|
|
87
|
+
confidence = (
|
|
88
|
+
CapabilityConfidence.OBSERVED
|
|
89
|
+
if CapabilityConfidence.OBSERVED in {current.confidence, profile.confidence}
|
|
90
|
+
else CapabilityConfidence.DECLARED
|
|
91
|
+
if CapabilityConfidence.DECLARED in {current.confidence, profile.confidence}
|
|
92
|
+
else CapabilityConfidence.UNKNOWN
|
|
93
|
+
)
|
|
94
|
+
metadata = dict(current.metadata)
|
|
95
|
+
metadata.update(profile.metadata)
|
|
96
|
+
merged = ModelCapabilityProfile(
|
|
97
|
+
model_id=profile.model_id,
|
|
98
|
+
provider_id=profile.provider_id,
|
|
99
|
+
capabilities=frozenset(current.capabilities | profile.capabilities),
|
|
100
|
+
confidence=confidence,
|
|
101
|
+
source=f"{current.source}+{profile.source}",
|
|
102
|
+
metadata=metadata,
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
merged = profile
|
|
106
|
+
self.store.save(merged)
|
|
107
|
+
return merged
|
|
108
|
+
|
|
109
|
+
def observe_response(self, model: ModelSpec, metadata: dict[str, object]) -> ModelCapabilityProfile:
|
|
110
|
+
"""Record capabilities directly evidenced by a successful model response."""
|
|
111
|
+
observed = {"chat"}
|
|
112
|
+
if "tool_calls" in metadata or metadata.get("tools_used") is True:
|
|
113
|
+
observed.add("tools")
|
|
114
|
+
if metadata.get("structured_output") is True:
|
|
115
|
+
observed.add("structured_output")
|
|
116
|
+
return self.merge(ModelCapabilityProfile(
|
|
117
|
+
model_id=model.id,
|
|
118
|
+
provider_id=model.provider_id,
|
|
119
|
+
capabilities=frozenset(observed),
|
|
120
|
+
confidence=CapabilityConfidence.OBSERVED,
|
|
121
|
+
source="runtime_observation",
|
|
122
|
+
metadata={"observed_capabilities": sorted(observed)},
|
|
123
|
+
))
|
|
124
|
+
|
|
125
|
+
def match(self, model: ModelSpec, requirements: frozenset[str]) -> CapabilityMatch:
|
|
126
|
+
profile = self.profile(model)
|
|
127
|
+
matched = profile.capabilities & requirements
|
|
128
|
+
missing = requirements - profile.capabilities
|
|
129
|
+
return CapabilityMatch(
|
|
130
|
+
model_id=model.id,
|
|
131
|
+
required=requirements,
|
|
132
|
+
matched=matched,
|
|
133
|
+
missing=missing,
|
|
134
|
+
score=profile.match_score(requirements),
|
|
135
|
+
confidence=profile.confidence,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def matches(
|
|
139
|
+
self,
|
|
140
|
+
models: list[ModelSpec],
|
|
141
|
+
requirements: frozenset[str],
|
|
142
|
+
) -> dict[str, CapabilityMatch]:
|
|
143
|
+
return {model.id: self.match(model, requirements) for model in models}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Resolve project Chat Agent contracts to provider adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from core.chat_agent_registry import ChatAgentRegistry
|
|
8
|
+
from core.contracts.chat_agent import ChatAgentProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ChatAdapterRegistry:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self._factories: dict[ChatAgentProvider, Any] = {}
|
|
14
|
+
|
|
15
|
+
def register(self, provider: ChatAgentProvider, factory: Any) -> None:
|
|
16
|
+
self._factories[provider] = factory
|
|
17
|
+
|
|
18
|
+
def create(self, provider: ChatAgentProvider, *, model: str | None = None) -> Any:
|
|
19
|
+
try:
|
|
20
|
+
factory = self._factories[provider]
|
|
21
|
+
except KeyError as exc:
|
|
22
|
+
raise LookupError(f"no Chat Agent adapter registered for {provider.value}") from exc
|
|
23
|
+
return factory(model=model)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def default_chat_adapters() -> ChatAdapterRegistry:
|
|
27
|
+
registry = ChatAdapterRegistry()
|
|
28
|
+
|
|
29
|
+
def openai_factory(*, model=None):
|
|
30
|
+
from integrations.openai.chat_agent import OpenAIChatAgentAdapter
|
|
31
|
+
return OpenAIChatAgentAdapter(model=model)
|
|
32
|
+
|
|
33
|
+
registry.register(ChatAgentProvider.CHATGPT, openai_factory)
|
|
34
|
+
return registry
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def resolve_project_chat_adapter(
|
|
38
|
+
project_agent,
|
|
39
|
+
model: str | None = None,
|
|
40
|
+
registry: ChatAdapterRegistry | None = None,
|
|
41
|
+
):
|
|
42
|
+
adapters = registry or default_chat_adapters()
|
|
43
|
+
return adapters.create(project_agent.provider, model=model)
|
runtime/chat_config.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Project-scoped conversational Chat Agent configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ChatConfig:
|
|
12
|
+
version: int
|
|
13
|
+
agent_id: str
|
|
14
|
+
model: str | None = None
|
|
15
|
+
|
|
16
|
+
def validate(self) -> None:
|
|
17
|
+
if self.version != 1:
|
|
18
|
+
raise ValueError(f"unsupported chat config version: {self.version}")
|
|
19
|
+
if not self.agent_id.strip():
|
|
20
|
+
raise ValueError("chat agent id must not be empty")
|
|
21
|
+
if self.model is not None and not self.model.strip():
|
|
22
|
+
raise ValueError("chat model must not be empty when provided")
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, object]:
|
|
25
|
+
self.validate()
|
|
26
|
+
return {"version": self.version, "agent_id": self.agent_id, "model": self.model}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
DEFAULT_CHAT_CONFIG = ChatConfig(version=1, agent_id="chatgpt", model=None)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_chat_config(project_root: Path) -> ChatConfig:
|
|
33
|
+
path = project_root / ".multiagentos" / "chat.json"
|
|
34
|
+
if not path.is_file():
|
|
35
|
+
raise FileNotFoundError(f"VYRELON chat config not found: {path}")
|
|
36
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
37
|
+
if not isinstance(data, dict):
|
|
38
|
+
raise ValueError("chat config must be a JSON object")
|
|
39
|
+
config = ChatConfig(
|
|
40
|
+
version=int(data.get("version", 0)),
|
|
41
|
+
agent_id=str(data.get("agent_id", "")),
|
|
42
|
+
model=data.get("model"),
|
|
43
|
+
)
|
|
44
|
+
config.validate()
|
|
45
|
+
return config
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def write_default_chat_config(project_root: Path) -> Path:
|
|
49
|
+
target = project_root / ".multiagentos"
|
|
50
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
path = target / "chat.json"
|
|
52
|
+
if not path.exists():
|
|
53
|
+
path.write_text(
|
|
54
|
+
json.dumps(DEFAULT_CHAT_CONFIG.to_dict(), indent=2) + "\n",
|
|
55
|
+
encoding="utf-8",
|
|
56
|
+
)
|
|
57
|
+
return path
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""End-to-end project Chat Agent execution facade."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from core.chat_agent_bridge import ChatAgentExecutionResult, ChatAgentRequest
|
|
8
|
+
from core.contracts.agent import AgentContract
|
|
9
|
+
from core.contracts.ai import ModelSpec
|
|
10
|
+
from core.contracts.execution import AgentExecutor, ResultReviewer, ResultVerifier
|
|
11
|
+
from runtime.vyrelon import VYRELONRuntime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def execute_project_chat_request(
|
|
15
|
+
project_root: Path,
|
|
16
|
+
request: ChatAgentRequest,
|
|
17
|
+
*,
|
|
18
|
+
agent: AgentContract,
|
|
19
|
+
models: list[ModelSpec],
|
|
20
|
+
executor: AgentExecutor,
|
|
21
|
+
verifier: ResultVerifier | None = None,
|
|
22
|
+
reviewer: ResultReviewer | None = None,
|
|
23
|
+
chat_agent_id: str | None = None,
|
|
24
|
+
preferred_model_ids: list[str] | None = None,
|
|
25
|
+
routing_strategy: str = "pool",
|
|
26
|
+
) -> ChatAgentExecutionResult:
|
|
27
|
+
"""Resolve the configured Chat Agent and execute through VYRELON.
|
|
28
|
+
|
|
29
|
+
The configured conversational provider supplies intent/plan; the supplied
|
|
30
|
+
VYRELON Agent/Model contracts own execution.
|
|
31
|
+
"""
|
|
32
|
+
runtime = VYRELONRuntime()
|
|
33
|
+
configured_agent, configured_model = runtime.project_chat_agent(project_root)
|
|
34
|
+
adapter = runtime.project_chat_adapter(project_root)
|
|
35
|
+
|
|
36
|
+
selected_chat_agent_id = chat_agent_id or configured_agent.id
|
|
37
|
+
if configured_model and preferred_model_ids is None:
|
|
38
|
+
preferred_model_ids = [configured_model]
|
|
39
|
+
|
|
40
|
+
return runtime.execute_chat_request(
|
|
41
|
+
request=request,
|
|
42
|
+
adapter=adapter,
|
|
43
|
+
agent=agent,
|
|
44
|
+
models=models,
|
|
45
|
+
executor=executor,
|
|
46
|
+
chat_agent_id=selected_chat_agent_id,
|
|
47
|
+
preferred_model_ids=preferred_model_ids,
|
|
48
|
+
verifier=verifier,
|
|
49
|
+
reviewer=reviewer,
|
|
50
|
+
routing_strategy=routing_strategy,
|
|
51
|
+
project_root=project_root,
|
|
52
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""VYRELON runtime configuration."""
|
runtime/config/loader.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""JSON configuration loader for AI providers and agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from core.contracts.agent import AgentContract
|
|
9
|
+
from core.contracts.ai import AIProvider, ModelSpec
|
|
10
|
+
from core.registry import AIRegistry, AgentRegistry
|
|
11
|
+
from core.routing import RoutingStrategy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RuntimeConfig:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
agents: AgentRegistry,
|
|
18
|
+
ai: AIRegistry,
|
|
19
|
+
routing: dict[str, RoutingStrategy],
|
|
20
|
+
) -> None:
|
|
21
|
+
self.agents = agents
|
|
22
|
+
self.ai = ai
|
|
23
|
+
self.routing = routing
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConfigLoader:
|
|
27
|
+
def load(self, path: Path) -> RuntimeConfig:
|
|
28
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
29
|
+
ai_registry = AIRegistry()
|
|
30
|
+
for provider_data in data.get("ai", {}).get("providers", []):
|
|
31
|
+
models = tuple(
|
|
32
|
+
ModelSpec(
|
|
33
|
+
id=model["id"],
|
|
34
|
+
provider_id=provider_data["id"],
|
|
35
|
+
capabilities=frozenset(model.get("capabilities", [])),
|
|
36
|
+
metadata=dict(model.get("metadata", {})),
|
|
37
|
+
)
|
|
38
|
+
for model in provider_data.get("models", [])
|
|
39
|
+
)
|
|
40
|
+
ai_registry.register(
|
|
41
|
+
AIProvider(
|
|
42
|
+
id=provider_data["id"],
|
|
43
|
+
kind=provider_data.get("type", "generic"),
|
|
44
|
+
models=models,
|
|
45
|
+
metadata=dict(provider_data.get("metadata", {})),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
agent_registry = AgentRegistry()
|
|
50
|
+
routing = {}
|
|
51
|
+
for agent_data in data.get("agents", []):
|
|
52
|
+
agent = AgentContract(
|
|
53
|
+
id=agent_data["id"],
|
|
54
|
+
role=agent_data.get("role", agent_data["id"]),
|
|
55
|
+
capabilities=frozenset(agent_data.get("capabilities", [])),
|
|
56
|
+
tools=frozenset(agent_data.get("tools", [])),
|
|
57
|
+
permissions=frozenset(agent_data.get("permissions", [])),
|
|
58
|
+
model_ids=tuple(agent_data.get("models", [])),
|
|
59
|
+
metadata=dict(agent_data.get("metadata", {})),
|
|
60
|
+
kind=agent_data.get("type", "custom"),
|
|
61
|
+
)
|
|
62
|
+
agent_registry.register(agent)
|
|
63
|
+
routing[agent.id] = RoutingStrategy(
|
|
64
|
+
agent_data.get("strategy", RoutingStrategy.POOL.value)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return RuntimeConfig(agent_registry, ai_registry, routing)
|