ak-agentbase 0.1.0a1__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.
- agentbase/__init__.py +78 -0
- agentbase/__main__.py +3 -0
- agentbase/cli.py +73 -0
- agentbase/config.py +115 -0
- agentbase/exceptions.py +36 -0
- agentbase/models.py +33 -0
- agentbase/registry.py +88 -0
- agentbase/tracing.py +49 -0
- agentbase/types.py +65 -0
- ak_agentbase-0.1.0a1.dist-info/METADATA +313 -0
- ak_agentbase-0.1.0a1.dist-info/RECORD +14 -0
- ak_agentbase-0.1.0a1.dist-info/WHEEL +4 -0
- ak_agentbase-0.1.0a1.dist-info/entry_points.txt +2 -0
- ak_agentbase-0.1.0a1.dist-info/licenses/LICENSE +21 -0
agentbase/__init__.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
__version__ = "0.1.0a1"
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from agentbase.config import get_environment
|
|
6
|
+
from agentbase.config import load_config as _load_config
|
|
7
|
+
from agentbase.exceptions import (
|
|
8
|
+
AgentBaseError,
|
|
9
|
+
AgentNotRegisteredError,
|
|
10
|
+
ConfigValidationError,
|
|
11
|
+
ContractViolationError,
|
|
12
|
+
ModelResolutionError,
|
|
13
|
+
)
|
|
14
|
+
from agentbase.models import resolve as _resolve
|
|
15
|
+
from agentbase.registry import load_registry
|
|
16
|
+
from agentbase.types import (
|
|
17
|
+
AgentBaseConfig,
|
|
18
|
+
AgentMetadata,
|
|
19
|
+
AgentStatus,
|
|
20
|
+
ResolvedAgent,
|
|
21
|
+
TracingConfig,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"__version__",
|
|
26
|
+
"load_config",
|
|
27
|
+
"resolve",
|
|
28
|
+
"init_tracing",
|
|
29
|
+
"AgentBaseConfig",
|
|
30
|
+
"AgentMetadata",
|
|
31
|
+
"AgentStatus",
|
|
32
|
+
"ResolvedAgent",
|
|
33
|
+
"TracingConfig",
|
|
34
|
+
"AgentBaseError",
|
|
35
|
+
"AgentNotRegisteredError",
|
|
36
|
+
"ConfigValidationError",
|
|
37
|
+
"ContractViolationError",
|
|
38
|
+
"ModelResolutionError",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
_config: AgentBaseConfig | None = None
|
|
42
|
+
_registry: dict[str, AgentMetadata] | None = None
|
|
43
|
+
_environment: str | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_config(
|
|
47
|
+
config_path: str | Path | None = None,
|
|
48
|
+
registry_path: str | Path | None = None,
|
|
49
|
+
) -> AgentBaseConfig:
|
|
50
|
+
global _config, _registry, _environment
|
|
51
|
+
|
|
52
|
+
_config = _load_config(config_path)
|
|
53
|
+
_environment = get_environment(_config)
|
|
54
|
+
|
|
55
|
+
if registry_path is None:
|
|
56
|
+
base = Path(config_path).parent if config_path else Path.cwd()
|
|
57
|
+
registry_path = base / "agents.yaml"
|
|
58
|
+
|
|
59
|
+
_registry = load_registry(registry_path, _config, _environment)
|
|
60
|
+
return _config
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def resolve(agent_name: str, environment: str | None = None) -> ResolvedAgent:
|
|
64
|
+
if _config is None or _registry is None:
|
|
65
|
+
raise AgentBaseError(
|
|
66
|
+
"AgentBase is not initialized. Call agentbase.load_config() before using resolve()."
|
|
67
|
+
)
|
|
68
|
+
return _resolve(agent_name, _registry, _config, environment or _environment)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def init_tracing() -> None:
|
|
72
|
+
if _config is None:
|
|
73
|
+
raise AgentBaseError(
|
|
74
|
+
"AgentBase is not initialized. Call agentbase.load_config() before init_tracing()."
|
|
75
|
+
)
|
|
76
|
+
from agentbase.tracing import init_tracing as _init_tracing
|
|
77
|
+
|
|
78
|
+
_init_tracing(_config)
|
agentbase/__main__.py
ADDED
agentbase/cli.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from agentbase.config import _config_cache, get_environment, load_config
|
|
6
|
+
from agentbase.exceptions import AgentBaseError
|
|
7
|
+
from agentbase.registry import _registry_cache, load_registry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _validate(config_path: str, registry_path: str) -> bool:
|
|
11
|
+
"""Validate config and registry files.
|
|
12
|
+
|
|
13
|
+
Returns True if valid, False if any errors found.
|
|
14
|
+
"""
|
|
15
|
+
_config_cache.clear()
|
|
16
|
+
_registry_cache.clear()
|
|
17
|
+
|
|
18
|
+
config = None
|
|
19
|
+
errors: list[str] = []
|
|
20
|
+
|
|
21
|
+
# Step 1: Validate agentbase.yaml
|
|
22
|
+
try:
|
|
23
|
+
config = load_config(config_path)
|
|
24
|
+
print(f"✓ {config_path} — project: {config.project}")
|
|
25
|
+
except AgentBaseError as e:
|
|
26
|
+
print(f"✗ {e}")
|
|
27
|
+
errors.append(str(e))
|
|
28
|
+
|
|
29
|
+
# Step 2: Validate agents.yaml (if config loaded successfully)
|
|
30
|
+
if config is not None:
|
|
31
|
+
try:
|
|
32
|
+
environment = get_environment(config)
|
|
33
|
+
load_registry(registry_path, config, environment)
|
|
34
|
+
print(f"✓ {registry_path} — environment: {environment}")
|
|
35
|
+
except AgentBaseError as e:
|
|
36
|
+
print(f"✗ {e}")
|
|
37
|
+
errors.append(str(e))
|
|
38
|
+
|
|
39
|
+
# Step 3: Validate tracing env vars (if using LangSmith)
|
|
40
|
+
if config.tracing.enabled and config.tracing.backend == "langsmith":
|
|
41
|
+
if os.environ.get("LANGCHAIN_API_KEY"):
|
|
42
|
+
print("✓ LANGCHAIN_API_KEY present")
|
|
43
|
+
else:
|
|
44
|
+
msg = "LANGCHAIN_API_KEY is not set (required when tracing.backend=langsmith)"
|
|
45
|
+
print(f"✗ {msg}")
|
|
46
|
+
errors.append(msg)
|
|
47
|
+
|
|
48
|
+
if not errors:
|
|
49
|
+
print("✓ All checks passed")
|
|
50
|
+
return not errors
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main() -> None:
|
|
54
|
+
"""CLI entry point."""
|
|
55
|
+
parser = argparse.ArgumentParser(prog="agentbase", description="AgentBase CLI")
|
|
56
|
+
sub = parser.add_subparsers(dest="command")
|
|
57
|
+
|
|
58
|
+
val = sub.add_parser("validate", help="Validate agentbase.yaml and agents.yaml")
|
|
59
|
+
val.add_argument(
|
|
60
|
+
"config", nargs="?", default="agentbase.yaml", help="Path to agentbase.yaml"
|
|
61
|
+
)
|
|
62
|
+
val.add_argument(
|
|
63
|
+
"agents", nargs="?", default="agents.yaml", help="Path to agents.yaml"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
|
|
68
|
+
if args.command == "validate":
|
|
69
|
+
ok = _validate(args.config, args.agents)
|
|
70
|
+
sys.exit(0 if ok else 1)
|
|
71
|
+
else:
|
|
72
|
+
parser.print_help()
|
|
73
|
+
sys.exit(1)
|
agentbase/config.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from agentbase.exceptions import ConfigValidationError
|
|
9
|
+
from agentbase.types import AgentBaseConfig, AgentBaseDefaults, ModelAliasConfig, TracingConfig
|
|
10
|
+
|
|
11
|
+
_config_cache: dict[str, AgentBaseConfig] = {}
|
|
12
|
+
_ENV_VAR_RE = re.compile(r"\$\{([^}]+)\}")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _interpolate(value: Any) -> Any:
|
|
16
|
+
if isinstance(value, str):
|
|
17
|
+
|
|
18
|
+
def _sub(m: re.Match) -> str:
|
|
19
|
+
var = m.group(1)
|
|
20
|
+
if var not in os.environ:
|
|
21
|
+
raise ConfigValidationError(
|
|
22
|
+
f"Environment variable '{var}' is referenced in agentbase.yaml but not set. "
|
|
23
|
+
f"Set '{var}' in your environment before starting the application."
|
|
24
|
+
)
|
|
25
|
+
return os.environ[var]
|
|
26
|
+
|
|
27
|
+
return _ENV_VAR_RE.sub(_sub, value)
|
|
28
|
+
if isinstance(value, dict):
|
|
29
|
+
return {k: _interpolate(v) for k, v in value.items()}
|
|
30
|
+
if isinstance(value, list):
|
|
31
|
+
return [_interpolate(v) for v in value]
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_models(raw_models: dict) -> dict[str, ModelAliasConfig]:
|
|
36
|
+
result = {}
|
|
37
|
+
for alias, raw in raw_models.items():
|
|
38
|
+
if not isinstance(raw, dict):
|
|
39
|
+
raise ConfigValidationError(
|
|
40
|
+
f"Model alias '{alias}' must be a YAML mapping in agentbase.yaml."
|
|
41
|
+
)
|
|
42
|
+
alias_dict = dict(raw)
|
|
43
|
+
default_params = alias_dict.pop("default_params", {})
|
|
44
|
+
if not alias_dict:
|
|
45
|
+
raise ConfigValidationError(
|
|
46
|
+
f"Model alias '{alias}' has no environment mappings in agentbase.yaml."
|
|
47
|
+
)
|
|
48
|
+
result[alias] = ModelAliasConfig(environments=alias_dict, default_params=default_params)
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _validate_coverage(models: dict[str, ModelAliasConfig]) -> None:
|
|
53
|
+
all_envs: set[str] = set()
|
|
54
|
+
for alias_config in models.values():
|
|
55
|
+
all_envs.update(alias_config.environments.keys())
|
|
56
|
+
for alias, alias_config in models.items():
|
|
57
|
+
missing = all_envs - set(alias_config.environments)
|
|
58
|
+
if missing:
|
|
59
|
+
raise ConfigValidationError(
|
|
60
|
+
f"Model alias '{alias}' is missing mappings for environment(s): "
|
|
61
|
+
f"{', '.join(sorted(missing))}. "
|
|
62
|
+
f"Add these under 'models.{alias}' in agentbase.yaml."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_config(path: str | Path | None = None) -> AgentBaseConfig:
|
|
67
|
+
resolved = Path(path).resolve() if path else (Path.cwd() / "agentbase.yaml").resolve()
|
|
68
|
+
cache_key = str(resolved)
|
|
69
|
+
|
|
70
|
+
if cache_key in _config_cache:
|
|
71
|
+
return _config_cache[cache_key]
|
|
72
|
+
|
|
73
|
+
if not resolved.exists():
|
|
74
|
+
raise ConfigValidationError(
|
|
75
|
+
f"Config file not found: {resolved}. Create agentbase.yaml in your project root."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
with open(resolved) as f:
|
|
79
|
+
raw = yaml.safe_load(f)
|
|
80
|
+
|
|
81
|
+
if not isinstance(raw, dict):
|
|
82
|
+
raise ConfigValidationError("agentbase.yaml must be a YAML mapping at the top level.")
|
|
83
|
+
|
|
84
|
+
raw = _interpolate(raw)
|
|
85
|
+
|
|
86
|
+
for field in ("project", "models"):
|
|
87
|
+
if field not in raw:
|
|
88
|
+
raise ConfigValidationError(f"agentbase.yaml is missing required field '{field}'.")
|
|
89
|
+
|
|
90
|
+
models = _parse_models(raw["models"])
|
|
91
|
+
|
|
92
|
+
defaults_raw = raw.get("defaults", {})
|
|
93
|
+
defaults = AgentBaseDefaults(**defaults_raw) if defaults_raw else AgentBaseDefaults()
|
|
94
|
+
|
|
95
|
+
tracing_raw = raw.get("tracing", {})
|
|
96
|
+
try:
|
|
97
|
+
tracing = TracingConfig(**tracing_raw) if tracing_raw else TracingConfig()
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise ConfigValidationError(f"Invalid 'tracing' section in agentbase.yaml: {e}") from e
|
|
100
|
+
|
|
101
|
+
config = AgentBaseConfig(
|
|
102
|
+
project=raw["project"],
|
|
103
|
+
models=models,
|
|
104
|
+
defaults=defaults,
|
|
105
|
+
tracing=tracing,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
_validate_coverage(config.models)
|
|
109
|
+
|
|
110
|
+
_config_cache[cache_key] = config
|
|
111
|
+
return config
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_environment(config: AgentBaseConfig) -> str:
|
|
115
|
+
return os.environ.get("AGENTBASE_ENV", config.defaults.environment)
|
agentbase/exceptions.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class AgentBaseError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConfigValidationError(AgentBaseError):
|
|
6
|
+
def __init__(self, message: str) -> None:
|
|
7
|
+
super().__init__(message)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentNotRegisteredError(AgentBaseError):
|
|
11
|
+
def __init__(self, agent_name: str) -> None:
|
|
12
|
+
super().__init__(
|
|
13
|
+
f"Agent '{agent_name}' is not registered in agents.yaml. "
|
|
14
|
+
f"Add an entry for '{agent_name}' with all required contract fields."
|
|
15
|
+
)
|
|
16
|
+
self.agent_name = agent_name
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ContractViolationError(AgentBaseError):
|
|
20
|
+
def __init__(self, agent_name: str, missing_field: str) -> None:
|
|
21
|
+
super().__init__(
|
|
22
|
+
f"Agent '{agent_name}' is missing required contract field '{missing_field}'. "
|
|
23
|
+
f"Add '{missing_field}' to the '{agent_name}' entry in agents.yaml."
|
|
24
|
+
)
|
|
25
|
+
self.agent_name = agent_name
|
|
26
|
+
self.missing_field = missing_field
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ModelResolutionError(AgentBaseError):
|
|
30
|
+
def __init__(self, alias: str, environment: str) -> None:
|
|
31
|
+
super().__init__(
|
|
32
|
+
f"Model alias '{alias}' has no mapping for environment '{environment}'. "
|
|
33
|
+
f"Add a '{environment}' entry under 'models.{alias}' in agentbase.yaml."
|
|
34
|
+
)
|
|
35
|
+
self.alias = alias
|
|
36
|
+
self.environment = environment
|
agentbase/models.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from agentbase.config import get_environment
|
|
2
|
+
from agentbase.exceptions import ModelResolutionError
|
|
3
|
+
from agentbase.registry import get_agent
|
|
4
|
+
from agentbase.tracing import build_trace_metadata
|
|
5
|
+
from agentbase.types import AgentBaseConfig, AgentMetadata, ResolvedAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def resolve_model(agent: AgentMetadata, config: AgentBaseConfig, environment: str) -> str | None:
|
|
9
|
+
if agent.model_alias is None:
|
|
10
|
+
return None
|
|
11
|
+
alias_config = config.models[agent.model_alias]
|
|
12
|
+
if environment not in alias_config.environments:
|
|
13
|
+
raise ModelResolutionError(agent.model_alias, environment)
|
|
14
|
+
return alias_config.environments[environment]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def resolve(
|
|
18
|
+
agent_name: str,
|
|
19
|
+
registry: dict[str, AgentMetadata],
|
|
20
|
+
config: AgentBaseConfig,
|
|
21
|
+
environment: str | None = None,
|
|
22
|
+
) -> ResolvedAgent:
|
|
23
|
+
env = environment or get_environment(config)
|
|
24
|
+
agent = get_agent(agent_name, registry)
|
|
25
|
+
model_id = resolve_model(agent, config, env)
|
|
26
|
+
default_params = config.models[agent.model_alias].default_params if agent.model_alias else {}
|
|
27
|
+
trace_metadata = build_trace_metadata(agent, model_id)
|
|
28
|
+
return ResolvedAgent(
|
|
29
|
+
agent_name=agent.agent_name,
|
|
30
|
+
model_id=model_id,
|
|
31
|
+
default_params=default_params,
|
|
32
|
+
trace_metadata=trace_metadata,
|
|
33
|
+
)
|
agentbase/registry.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
from agentbase.exceptions import (
|
|
6
|
+
AgentNotRegisteredError,
|
|
7
|
+
ConfigValidationError,
|
|
8
|
+
ContractViolationError,
|
|
9
|
+
)
|
|
10
|
+
from agentbase.types import AgentBaseConfig, AgentMetadata
|
|
11
|
+
|
|
12
|
+
_CONTRACT_FIELDS = ("agent_name", "agent_version", "model_alias", "prompt_ref", "owner", "status")
|
|
13
|
+
|
|
14
|
+
_registry_cache: dict[str, dict[str, AgentMetadata]] = {}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _validate_agent(raw: dict, config: AgentBaseConfig, environment: str) -> AgentMetadata:
|
|
18
|
+
agent_name = raw.get("agent_name") or "<unknown>"
|
|
19
|
+
|
|
20
|
+
for field in _CONTRACT_FIELDS:
|
|
21
|
+
if field == "model_alias":
|
|
22
|
+
continue
|
|
23
|
+
if not raw.get(field):
|
|
24
|
+
raise ContractViolationError(agent_name, field)
|
|
25
|
+
|
|
26
|
+
model_alias = raw.get("model_alias")
|
|
27
|
+
if model_alias is not None and model_alias not in config.models:
|
|
28
|
+
raise ConfigValidationError(
|
|
29
|
+
f"Agent '{agent_name}' references model alias '{model_alias}' which is not defined "
|
|
30
|
+
f"in agentbase.yaml. Available aliases: {', '.join(sorted(config.models))}."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return AgentMetadata(
|
|
34
|
+
project=config.project,
|
|
35
|
+
environment=environment,
|
|
36
|
+
agent_name=raw["agent_name"],
|
|
37
|
+
agent_version=str(raw["agent_version"]),
|
|
38
|
+
model_alias=raw.get("model_alias"),
|
|
39
|
+
prompt_ref=raw["prompt_ref"],
|
|
40
|
+
owner=raw["owner"],
|
|
41
|
+
status=raw["status"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_registry(
|
|
46
|
+
path: str | Path,
|
|
47
|
+
config: AgentBaseConfig,
|
|
48
|
+
environment: str,
|
|
49
|
+
) -> dict[str, AgentMetadata]:
|
|
50
|
+
resolved = Path(path).resolve()
|
|
51
|
+
cache_key = f"{resolved}:{environment}"
|
|
52
|
+
|
|
53
|
+
if cache_key in _registry_cache:
|
|
54
|
+
return _registry_cache[cache_key]
|
|
55
|
+
|
|
56
|
+
if not resolved.exists():
|
|
57
|
+
raise ConfigValidationError(
|
|
58
|
+
f"Agent registry not found: {resolved}. Create agents.yaml in your project root."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
with open(resolved) as f:
|
|
62
|
+
raw = yaml.safe_load(f)
|
|
63
|
+
|
|
64
|
+
if not isinstance(raw, dict) or "agents" not in raw:
|
|
65
|
+
raise ConfigValidationError("agents.yaml must have a top-level 'agents' list.")
|
|
66
|
+
|
|
67
|
+
agents_raw = raw["agents"]
|
|
68
|
+
if not isinstance(agents_raw, list):
|
|
69
|
+
raise ConfigValidationError("'agents' in agents.yaml must be a list.")
|
|
70
|
+
|
|
71
|
+
registry: dict[str, AgentMetadata] = {}
|
|
72
|
+
for agent_raw in agents_raw:
|
|
73
|
+
metadata = _validate_agent(agent_raw, config, environment)
|
|
74
|
+
if metadata.agent_name in registry:
|
|
75
|
+
raise ConfigValidationError(
|
|
76
|
+
f"Duplicate agent name '{metadata.agent_name}' in agents.yaml. "
|
|
77
|
+
f"Each agent must have a unique name within a project."
|
|
78
|
+
)
|
|
79
|
+
registry[metadata.agent_name] = metadata
|
|
80
|
+
|
|
81
|
+
_registry_cache[cache_key] = registry
|
|
82
|
+
return registry
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_agent(name: str, registry: dict[str, AgentMetadata]) -> AgentMetadata:
|
|
86
|
+
if name not in registry:
|
|
87
|
+
raise AgentNotRegisteredError(name)
|
|
88
|
+
return registry[name]
|
agentbase/tracing.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from agentbase.exceptions import ConfigValidationError
|
|
5
|
+
from agentbase.types import AgentBaseConfig, AgentMetadata
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def init_tracing(config: AgentBaseConfig) -> None:
|
|
9
|
+
tc = config.tracing
|
|
10
|
+
if not tc.enabled or tc.backend == "none":
|
|
11
|
+
return
|
|
12
|
+
if tc.backend == "langsmith":
|
|
13
|
+
_init_langsmith(config.project)
|
|
14
|
+
else:
|
|
15
|
+
raise ConfigValidationError(
|
|
16
|
+
f"Unknown tracing backend '{tc.backend}' in agentbase.yaml. "
|
|
17
|
+
f"Supported values: 'langsmith', 'none'."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _init_langsmith(project: str) -> None:
|
|
22
|
+
if not os.environ.get("LANGCHAIN_API_KEY"):
|
|
23
|
+
raise ConfigValidationError(
|
|
24
|
+
"Missing environment variable LANGCHAIN_API_KEY "
|
|
25
|
+
"(required for LangSmith tracing). "
|
|
26
|
+
"Get a key at smith.langchain.com and set LANGCHAIN_API_KEY in your environment."
|
|
27
|
+
)
|
|
28
|
+
os.environ["LANGCHAIN_TRACING_V2"] = "true"
|
|
29
|
+
os.environ["LANGCHAIN_PROJECT"] = project
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_trace_metadata(agent: AgentMetadata, model_id: str | None) -> dict[str, Any]:
|
|
33
|
+
metadata = {
|
|
34
|
+
"project": agent.project,
|
|
35
|
+
"environment": agent.environment,
|
|
36
|
+
"agent_name": agent.agent_name,
|
|
37
|
+
"agent_version": agent.agent_version,
|
|
38
|
+
"owner": agent.owner,
|
|
39
|
+
}
|
|
40
|
+
if agent.model_alias is not None:
|
|
41
|
+
metadata["model_alias"] = agent.model_alias
|
|
42
|
+
if model_id is not None:
|
|
43
|
+
metadata["model_id"] = model_id
|
|
44
|
+
return {
|
|
45
|
+
"generation_name": agent.agent_name,
|
|
46
|
+
"trace_name": f"{agent.project}/{agent.agent_name}",
|
|
47
|
+
"tags": [agent.project, agent.environment, agent.status.value],
|
|
48
|
+
"metadata": metadata,
|
|
49
|
+
}
|
agentbase/types.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AgentStatus(StrEnum):
|
|
8
|
+
experimental = "experimental"
|
|
9
|
+
active = "active"
|
|
10
|
+
deprecated = "deprecated"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentMetadata(BaseModel):
|
|
14
|
+
project: str
|
|
15
|
+
environment: str
|
|
16
|
+
agent_name: str
|
|
17
|
+
agent_version: str
|
|
18
|
+
model_alias: str | None = None
|
|
19
|
+
prompt_ref: str
|
|
20
|
+
owner: str
|
|
21
|
+
status: AgentStatus
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModelAliasConfig(BaseModel):
|
|
25
|
+
environments: dict[str, str]
|
|
26
|
+
default_params: dict[str, Any] = {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentBaseDefaults(BaseModel):
|
|
30
|
+
environment: str = "dev"
|
|
31
|
+
model_alias: str = "smart"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TracingConfig(BaseModel):
|
|
35
|
+
backend: str = "none" # "langsmith" | "none"
|
|
36
|
+
enabled: bool = True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AgentBaseConfig(BaseModel):
|
|
40
|
+
project: str
|
|
41
|
+
models: dict[str, ModelAliasConfig]
|
|
42
|
+
defaults: AgentBaseDefaults = AgentBaseDefaults()
|
|
43
|
+
tracing: TracingConfig = TracingConfig()
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def environments(self) -> set[str]:
|
|
47
|
+
envs: set[str] = set()
|
|
48
|
+
for alias_config in self.models.values():
|
|
49
|
+
envs.update(alias_config.environments.keys())
|
|
50
|
+
return envs
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ResolvedAgent(BaseModel):
|
|
54
|
+
agent_name: str
|
|
55
|
+
model_id: str | None
|
|
56
|
+
default_params: dict[str, Any] = {}
|
|
57
|
+
trace_metadata: dict[str, Any] = {}
|
|
58
|
+
|
|
59
|
+
def langchain_config(self) -> dict[str, Any]:
|
|
60
|
+
"""RunnableConfig dict — pass as config= to any LangChain call."""
|
|
61
|
+
return {
|
|
62
|
+
"run_name": self.trace_metadata.get("generation_name", self.agent_name),
|
|
63
|
+
"tags": self.trace_metadata.get("tags", []),
|
|
64
|
+
"metadata": self.trace_metadata.get("metadata", {}),
|
|
65
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ak-agentbase
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Unified AI agent configuration, observability, and model management for multi-agent applications
|
|
5
|
+
Project-URL: Homepage, https://github.com/Shivam1904/AgentBase
|
|
6
|
+
Project-URL: Documentation, https://github.com/Shivam1904/AgentBase/blob/main/README.md
|
|
7
|
+
Project-URL: Repository, https://github.com/Shivam1904/AgentBase
|
|
8
|
+
Project-URL: Issues, https://github.com/Shivam1904/AgentBase/issues
|
|
9
|
+
Author-email: Shivam Srivastava <shivam.sriv93@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,ai,configuration,langsmith,llm,observability
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: pydantic==2.12.5
|
|
22
|
+
Requires-Dist: pyyaml==6.0.3
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build==1.2.2; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest==9.0.3; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff==0.15.12; extra == 'dev'
|
|
28
|
+
Requires-Dist: twine==6.1.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# AgentBase v0.1-alpha
|
|
32
|
+
|
|
33
|
+
**Unified AI agent configuration, observability, and model management for production multi-agent systems.**
|
|
34
|
+
|
|
35
|
+
AgentBase is a lightweight, framework-agnostic control plane that standardizes how AI-heavy products configure models, register agents, and emit observability metadata. No framework dependencies. No vendor lock-in. Just YAML + Python.
|
|
36
|
+
|
|
37
|
+
## Status
|
|
38
|
+
|
|
39
|
+
✅ **v0.1-alpha** — Core library complete. 96 tests passing. First projects integrated (Writerverse, Skill-Deck pipeline).
|
|
40
|
+
|
|
41
|
+
## What It Does
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import agentbase
|
|
45
|
+
from langchain_openai import ChatOpenAI
|
|
46
|
+
|
|
47
|
+
# Load configuration
|
|
48
|
+
agentbase.load_config("agentbase.yaml", "agents.yaml")
|
|
49
|
+
agentbase.init_tracing()
|
|
50
|
+
|
|
51
|
+
# Resolve an agent
|
|
52
|
+
resolved = agentbase.resolve("my_analyzer")
|
|
53
|
+
|
|
54
|
+
# Construct LLM with AgentBase config
|
|
55
|
+
llm = ChatOpenAI(model=resolved.model_id, **resolved.default_params)
|
|
56
|
+
|
|
57
|
+
# Enable LangSmith tracing
|
|
58
|
+
result = llm.invoke(messages, config=resolved.langchain_config())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Returns:**
|
|
62
|
+
- `model_id`: Resolved model name (e.g., `gpt-4o`, `claude-sonnet-4-20250514`)
|
|
63
|
+
- `default_params`: Model config (temperature, max_tokens, etc.)
|
|
64
|
+
- `langchain_config()`: LangSmith trace metadata (tags, generation_name, metadata)
|
|
65
|
+
|
|
66
|
+
## Core Features
|
|
67
|
+
|
|
68
|
+
- **YAML Config** — Single source of truth for all model aliases and agent definitions
|
|
69
|
+
- **Model Aliases** — `fast`, `smart`, `creative` that resolve to provider-specific IDs per environment
|
|
70
|
+
- **Agent Registry** — Centralized agent metadata (owner, version, prompt location, status)
|
|
71
|
+
- **LangSmith Integration** — Automatic trace metadata in every chain/workflow
|
|
72
|
+
- **Environment Switching** — Dev/prod model aliases in one config file
|
|
73
|
+
- **Zero Framework Dependencies** — Works with LangChain, Anthropic SDK, OpenAI SDK, etc.
|
|
74
|
+
- **CLI Validation** — `agentbase validate` checks config and resolves all agents
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install agentbase==0.1.0a1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
**1. Create config files:**
|
|
85
|
+
|
|
86
|
+
`agentbase.yaml`:
|
|
87
|
+
```yaml
|
|
88
|
+
project: my-project
|
|
89
|
+
|
|
90
|
+
models:
|
|
91
|
+
fast:
|
|
92
|
+
dev: gpt-4o-mini
|
|
93
|
+
production: gpt-4o
|
|
94
|
+
smart:
|
|
95
|
+
dev: gpt-4o
|
|
96
|
+
production: claude-sonnet-4-20250514
|
|
97
|
+
|
|
98
|
+
defaults:
|
|
99
|
+
environment: dev
|
|
100
|
+
model_alias: smart
|
|
101
|
+
|
|
102
|
+
tracing:
|
|
103
|
+
backend: langsmith
|
|
104
|
+
enabled: true
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`agents.yaml`:
|
|
108
|
+
```yaml
|
|
109
|
+
agents:
|
|
110
|
+
- agent_name: analyzer
|
|
111
|
+
model_alias: fast
|
|
112
|
+
owner: team
|
|
113
|
+
status: active
|
|
114
|
+
agent_version: "0.1.0"
|
|
115
|
+
prompt_ref: "git:backend/prompts/analyze.py"
|
|
116
|
+
description: "Analyzes input data"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**2. Set environment variables:**
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
LANGCHAIN_API_KEY=lsv2_pt_xxxxx
|
|
123
|
+
OPENAI_API_KEY=sk-xxxxx
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**3. Initialize in your app:**
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import agentbase
|
|
130
|
+
|
|
131
|
+
agentbase.load_config(config_path="agentbase.yaml", registry_path="agents.yaml")
|
|
132
|
+
agentbase.init_tracing()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**4. Use in your chains:**
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
resolved = agentbase.resolve("analyzer")
|
|
139
|
+
llm = ChatOpenAI(model=resolved.model_id, **resolved.default_params)
|
|
140
|
+
result = chain.invoke(input, config=resolved.langchain_config())
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
See [QUICKSTART.md](QUICKSTART.md) for full setup instructions.
|
|
144
|
+
|
|
145
|
+
## Architecture
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
agentbase.load_config()
|
|
149
|
+
├─ Loads agentbase.yaml (project, models, defaults, tracing)
|
|
150
|
+
├─ Loads agents.yaml (agent registry)
|
|
151
|
+
└─ Validates all required fields
|
|
152
|
+
|
|
153
|
+
agentbase.init_tracing()
|
|
154
|
+
└─ Sets LANGCHAIN_TRACING_V2 and LANGCHAIN_PROJECT env vars
|
|
155
|
+
(if LangSmith backend is enabled)
|
|
156
|
+
|
|
157
|
+
agentbase.resolve(agent_name)
|
|
158
|
+
├─ Looks up agent in registry
|
|
159
|
+
├─ Resolves model_alias → model_id (using defaults.environment)
|
|
160
|
+
├─ Returns ResolvedAgent with:
|
|
161
|
+
│ ├─ model_id (e.g., "gpt-4o")
|
|
162
|
+
│ ├─ default_params (temperature, max_tokens, etc.)
|
|
163
|
+
│ ├─ langchain_config() → LangSmith metadata
|
|
164
|
+
│ └─ trace_metadata → raw metadata dict
|
|
165
|
+
└─ (Throws ConfigValidationError if not found or invalid)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## API
|
|
169
|
+
|
|
170
|
+
### Core Functions
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
agentbase.load_config(config_path: str, registry_path: str) → None
|
|
174
|
+
# Load and validate agentbase.yaml and agents.yaml
|
|
175
|
+
# Raises ConfigValidationError if validation fails
|
|
176
|
+
|
|
177
|
+
agentbase.init_tracing() → None
|
|
178
|
+
# Set up LangSmith tracing if enabled in config
|
|
179
|
+
# Requires LANGCHAIN_API_KEY env var if using LangSmith
|
|
180
|
+
|
|
181
|
+
agentbase.resolve(agent_name: str) → ResolvedAgent
|
|
182
|
+
# Resolve agent and return config
|
|
183
|
+
# Returns ResolvedAgent with model_id, default_params, langchain_config()
|
|
184
|
+
# Raises ConfigValidationError if agent not found
|
|
185
|
+
|
|
186
|
+
agentbase.validate(config_path: str, registry_path: str) → None
|
|
187
|
+
# Validation-only mode (no tracing setup)
|
|
188
|
+
# Used by CLI and tests
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### ResolvedAgent
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
@dataclass
|
|
195
|
+
class ResolvedAgent:
|
|
196
|
+
model_id: str | None # "gpt-4o", "claude-sonnet-4-20250514", None for workflows
|
|
197
|
+
default_params: dict[str, Any] # {"temperature": 0.7, "max_tokens": 4096}
|
|
198
|
+
|
|
199
|
+
def langchain_config(self) -> dict[str, Any]:
|
|
200
|
+
# Returns LangSmith-compatible config for chain.invoke(config=...)
|
|
201
|
+
# Fields: generation_name, trace_name, tags, metadata
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## CLI
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Validate config (exit 0 if valid, 1 if errors)
|
|
208
|
+
agentbase validate agentbase.yaml agents.yaml
|
|
209
|
+
|
|
210
|
+
# Resolve an agent (pretty-print)
|
|
211
|
+
agentbase resolve agent_name --config agentbase.yaml --registry agents.yaml
|
|
212
|
+
|
|
213
|
+
# Version
|
|
214
|
+
agentbase --version
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Configuration Reference
|
|
218
|
+
|
|
219
|
+
### agentbase.yaml
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
project: <string> # Project name (used in LangSmith traces)
|
|
223
|
+
|
|
224
|
+
models:
|
|
225
|
+
<alias>: # e.g., "fast", "smart", "creative"
|
|
226
|
+
dev: <model_id> # e.g., "gpt-4o-mini"
|
|
227
|
+
production: <model_id> # e.g., "gpt-4o"
|
|
228
|
+
|
|
229
|
+
defaults:
|
|
230
|
+
environment: <env> # "dev" or "production"
|
|
231
|
+
model_alias: <alias> # Default model alias
|
|
232
|
+
[other defaults...]
|
|
233
|
+
|
|
234
|
+
tracing:
|
|
235
|
+
backend: langsmith | none # "langsmith" or "none"
|
|
236
|
+
enabled: true | false # Enable/disable tracing
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### agents.yaml
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
agents:
|
|
243
|
+
- agent_name: <string> # Unique agent identifier
|
|
244
|
+
model_alias: <string> # References models.* in agentbase.yaml
|
|
245
|
+
owner: <string> # Team or person responsible
|
|
246
|
+
status: active | inactive # Status
|
|
247
|
+
agent_version: <semver> # e.g., "0.1.0"
|
|
248
|
+
prompt_ref: <string> # Reference to prompt (e.g., "git:path/to/file.py")
|
|
249
|
+
description: <string> # Human-readable description
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Environment Variables
|
|
253
|
+
|
|
254
|
+
| Variable | Required | Purpose |
|
|
255
|
+
|----------|----------|---------|
|
|
256
|
+
| `LANGCHAIN_API_KEY` | If tracing enabled | LangSmith API key |
|
|
257
|
+
| `OPENAI_API_KEY` | If using gpt-* models | OpenAI API key |
|
|
258
|
+
| `ANTHROPIC_API_KEY` | If using claude-* models | Anthropic API key |
|
|
259
|
+
|
|
260
|
+
## Why AgentBase?
|
|
261
|
+
|
|
262
|
+
**Before AgentBase:**
|
|
263
|
+
- Each project had its own config system (get_ai_config, hardcoded model names)
|
|
264
|
+
- LLM setup was duplicated across chains
|
|
265
|
+
- No unified observability or cost tracking
|
|
266
|
+
- Difficult to switch models for A/B testing
|
|
267
|
+
|
|
268
|
+
**With AgentBase:**
|
|
269
|
+
- One config file per project, YAML-based
|
|
270
|
+
- Agents registered once, resolved anywhere
|
|
271
|
+
- Automatic LangSmith integration
|
|
272
|
+
- Model switching via `defaults.environment`
|
|
273
|
+
- Cost visibility per agent per environment
|
|
274
|
+
|
|
275
|
+
## For Projects Using AgentBase
|
|
276
|
+
|
|
277
|
+
- **Writerverse** — 35 agents + 7 LangGraph workflows (scene generation, consistency analysis, chat, supervisor)
|
|
278
|
+
- **Skill-Deck** — Q&A system with dynamic model routing
|
|
279
|
+
- **Shivam Portfolio** — Proof-of-concept (first integration)
|
|
280
|
+
|
|
281
|
+
See [QUICKSTART.md](QUICKSTART.md) for project integration steps.
|
|
282
|
+
|
|
283
|
+
## For Publishing
|
|
284
|
+
|
|
285
|
+
See [PUBLISH.md](PUBLISH.md) for detailed steps to publish to PyPI.
|
|
286
|
+
|
|
287
|
+
## Development
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# Install dev dependencies
|
|
291
|
+
pip install -e .[dev]
|
|
292
|
+
|
|
293
|
+
# Run tests
|
|
294
|
+
pytest tests/ -v
|
|
295
|
+
|
|
296
|
+
# Lint
|
|
297
|
+
ruff check agentbase/ tests/
|
|
298
|
+
|
|
299
|
+
# Format
|
|
300
|
+
ruff format agentbase/ tests/
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT — see [LICENSE](LICENSE)
|
|
306
|
+
|
|
307
|
+
## Author
|
|
308
|
+
|
|
309
|
+
Shivam Srivastava — [GitHub](https://github.com/Shivam1904)
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
**Questions?** Open an issue on [GitHub](https://github.com/Shivam1904/AgentBase/issues)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
agentbase/__init__.py,sha256=ioI1ZEwDA3Q4DvbBAIjyJs1gjHJ_MKy30z3zH_seS7Y,2115
|
|
2
|
+
agentbase/__main__.py,sha256=YJ2L3vBKgUG0jAY6Sh4nfwiaFceLOU2k5zgbUp-zdlU,39
|
|
3
|
+
agentbase/cli.py,sha256=-WN4nxdm-g3aZzP7Bv-wWTVroWzt22hn77GPgmunBHI,2337
|
|
4
|
+
agentbase/config.py,sha256=SAh2A_c9WGCqSh-h4bN3E3ouKdoIntb0xSPVz4ou-es,3916
|
|
5
|
+
agentbase/exceptions.py,sha256=yLNJA2kRbea49ES_nT7vjy-Knze3WJB0s6Bl7DwOOVo,1279
|
|
6
|
+
agentbase/models.py,sha256=atK9HNGM25c4JkvPrNxw3aLgFXg29RrvVerK9B300xg,1275
|
|
7
|
+
agentbase/registry.py,sha256=7Xpf_g13_bPzefP7QKHWk0oQOT33l5oW32ATf4v2hTQ,2885
|
|
8
|
+
agentbase/tracing.py,sha256=XcOXdh6DwKbOeSxyQE8C3B3bALH7fzbisuUJA5GmTEU,1676
|
|
9
|
+
agentbase/types.py,sha256=EBM6Z3l7JS23jiHYANbsmUf5E5uEjfjdx3zDNVGUUPM,1649
|
|
10
|
+
ak_agentbase-0.1.0a1.dist-info/METADATA,sha256=I6MhNCrpPcGjirVUBIJ3tGB6YKRI8NzrZGfvEL_BfYY,9179
|
|
11
|
+
ak_agentbase-0.1.0a1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
12
|
+
ak_agentbase-0.1.0a1.dist-info/entry_points.txt,sha256=ikSlYfVSxSUSwFN0ZbMFMioyuC2mviQCm-ZaZwFPPOE,49
|
|
13
|
+
ak_agentbase-0.1.0a1.dist-info/licenses/LICENSE,sha256=f3ihmqRSYLpC8ZEWx7Ns0gvqWJnMPCMpS_iKz0ngNhc,1074
|
|
14
|
+
ak_agentbase-0.1.0a1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shivam Srivastava
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|