test_instance.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """
  2. Tests for Agent Instance mechanism.
  3. Tests cover:
  4. 1. _deep_merge() — dict merging semantics
  5. 2. create_instance() — directory and file creation
  6. 3. load_instance() — merge agent template + instance overrides
  7. 4. load_instance_from_dirs() — two-dir loading
  8. 5. Multiple instances from same template
  9. """
  10. import json
  11. import os
  12. import shutil
  13. import tempfile
  14. import pytest
  15. import yaml
  16. from agentpaas.engine.instance import (
  17. _deep_merge,
  18. create_instance,
  19. load_instance,
  20. load_instance_from_dirs,
  21. )
  22. @pytest.fixture
  23. def tmpdir():
  24. d = tempfile.mkdtemp(prefix="test_instance_")
  25. yield d
  26. shutil.rmtree(d, ignore_errors=True)
  27. def _write_yaml(path, data):
  28. os.makedirs(os.path.dirname(path), exist_ok=True)
  29. with open(path, "w") as f:
  30. yaml.dump(data, f, allow_unicode=True)
  31. # ============================================================
  32. # 1. _deep_merge
  33. # ============================================================
  34. class TestDeepMerge:
  35. def test_simple_override(self):
  36. base = {"a": 1, "b": 2}
  37. over = {"b": 3, "c": 4}
  38. assert _deep_merge(base, over) == {"a": 1, "b": 3, "c": 4}
  39. def test_nested_merge(self):
  40. base = {"model": {"name": "qwen", "temperature": 0.3}}
  41. over = {"model": {"name": "gpt-4"}}
  42. result = _deep_merge(base, over)
  43. assert result["model"]["name"] == "gpt-4"
  44. assert result["model"]["temperature"] == 0.3 # preserved
  45. def test_deep_nested(self):
  46. base = {"a": {"b": {"c": 1, "d": 2}}}
  47. over = {"a": {"b": {"c": 99}}}
  48. result = _deep_merge(base, over)
  49. assert result["a"]["b"]["c"] == 99
  50. assert result["a"]["b"]["d"] == 2
  51. def test_no_mutation(self):
  52. base = {"x": {"y": 1}}
  53. over = {"x": {"y": 2}}
  54. result = _deep_merge(base, over)
  55. assert base["x"]["y"] == 1 # base unchanged
  56. # ============================================================
  57. # 2. create_instance
  58. # ============================================================
  59. class TestCreateInstance:
  60. def test_creates_directories(self, tmpdir):
  61. inst_dir = os.path.join(tmpdir, "maritime")
  62. create_instance(inst_dir, "qaagent67wiki", name="Maritime Wiki")
  63. assert os.path.isdir(os.path.join(inst_dir, "wiki"))
  64. assert os.path.isdir(os.path.join(inst_dir, "knowledge", "raw"))
  65. assert os.path.isdir(os.path.join(inst_dir, "knowledge", "processed"))
  66. assert os.path.isdir(os.path.join(inst_dir, "workspace"))
  67. def test_creates_instance_yml(self, tmpdir):
  68. inst_dir = os.path.join(tmpdir, "maritime")
  69. path = create_instance(inst_dir, "qaagent67wiki", name="Maritime Wiki")
  70. assert os.path.isfile(path)
  71. with open(path) as f:
  72. cfg = yaml.safe_load(f)
  73. assert cfg["agent"] == "qaagent67wiki"
  74. assert cfg["name"] == "Maritime Wiki"
  75. assert "knowledge" in cfg
  76. assert "wiki" in cfg
  77. def test_auto_paths(self, tmpdir):
  78. inst_dir = os.path.join(tmpdir, "finance")
  79. create_instance(inst_dir, "qaagent67wiki")
  80. with open(os.path.join(inst_dir, "instance.yml")) as f:
  81. cfg = yaml.safe_load(f)
  82. # Paths should point to instance_dir
  83. assert inst_dir in cfg["knowledge"]["baseDir"]
  84. assert inst_dir in cfg["wiki"]["dir"]
  85. # ============================================================
  86. # 3. load_instance
  87. # ============================================================
  88. class TestLoadInstance:
  89. def test_merge_template_and_instance(self, tmpdir):
  90. # Create agent template
  91. agent_dir = os.path.join(tmpdir, "agentexample", "myagent")
  92. _write_yaml(os.path.join(agent_dir, "agent-config.yml"), {
  93. "type": "react",
  94. "systemPrompt": "You are helpful",
  95. "model": {"name": "qwen", "temperature": 0.3},
  96. "knowledge": {"baseDir": "./knowledge"},
  97. })
  98. # Create instance
  99. inst_dir = os.path.join(tmpdir, "instances", "domain1")
  100. os.makedirs(inst_dir, exist_ok=True)
  101. _write_yaml(os.path.join(inst_dir, "instance.yml"), {
  102. "agent": agent_dir,
  103. "name": "Domain 1",
  104. "knowledge": {"baseDir": "/data/domain1/knowledge"},
  105. "model": {"name": "gpt-4"},
  106. })
  107. config = load_instance(os.path.join(inst_dir, "instance.yml"))
  108. # Template fields preserved
  109. assert config["type"] == "react"
  110. assert config["systemPrompt"] == "You are helpful"
  111. # Instance overrides applied
  112. assert config["knowledge"]["baseDir"] == "/data/domain1/knowledge"
  113. assert config["model"]["name"] == "gpt-4"
  114. # Nested merge: temperature preserved from template
  115. assert config["model"]["temperature"] == 0.3
  116. # Metadata injected
  117. assert config["_agent_dir"] == agent_dir
  118. assert config["_instance_dir"] == inst_dir
  119. assert config["_instance_name"] == "Domain 1"
  120. # ============================================================
  121. # 4. load_instance_from_dirs
  122. # ============================================================
  123. class TestLoadInstanceFromDirs:
  124. def test_with_instance_yml(self, tmpdir):
  125. agent_dir = os.path.join(tmpdir, "agent")
  126. inst_dir = os.path.join(tmpdir, "instance")
  127. _write_yaml(os.path.join(agent_dir, "agent-config.yml"), {
  128. "type": "simple",
  129. "systemPrompt": "base",
  130. "knowledge": {"baseDir": "./kb"},
  131. })
  132. os.makedirs(inst_dir, exist_ok=True)
  133. _write_yaml(os.path.join(inst_dir, "instance.yml"), {
  134. "agent": agent_dir,
  135. "knowledge": {"baseDir": "/data/prod/kb"},
  136. })
  137. config = load_instance_from_dirs(agent_dir, inst_dir)
  138. assert config["knowledge"]["baseDir"] == "/data/prod/kb"
  139. assert config["systemPrompt"] == "base"
  140. def test_without_instance_yml(self, tmpdir):
  141. agent_dir = os.path.join(tmpdir, "agent")
  142. inst_dir = os.path.join(tmpdir, "instance")
  143. _write_yaml(os.path.join(agent_dir, "agent-config.yml"), {
  144. "type": "simple",
  145. "knowledge": {"baseDir": "./default"},
  146. })
  147. os.makedirs(inst_dir, exist_ok=True)
  148. config = load_instance_from_dirs(agent_dir, inst_dir)
  149. assert config["knowledge"]["baseDir"] == "./default"
  150. assert config["_instance_dir"] == os.path.abspath(inst_dir)
  151. # ============================================================
  152. # 5. Multiple Instances from Same Template
  153. # ============================================================
  154. class TestMultipleInstances:
  155. def test_two_instances_share_template(self, tmpdir):
  156. agent_dir = os.path.join(tmpdir, "template")
  157. _write_yaml(os.path.join(agent_dir, "agent-config.yml"), {
  158. "type": "react",
  159. "systemPrompt": "Wiki agent",
  160. "knowledge": {"baseDir": "./default"},
  161. })
  162. # Instance A: maritime
  163. inst_a = os.path.join(tmpdir, "maritime")
  164. os.makedirs(inst_a, exist_ok=True)
  165. _write_yaml(os.path.join(inst_a, "instance.yml"), {
  166. "agent": agent_dir,
  167. "name": "Maritime",
  168. "knowledge": {"baseDir": "/data/maritime"},
  169. })
  170. # Instance B: medical
  171. inst_b = os.path.join(tmpdir, "medical")
  172. os.makedirs(inst_b, exist_ok=True)
  173. _write_yaml(os.path.join(inst_b, "instance.yml"), {
  174. "agent": agent_dir,
  175. "name": "Medical",
  176. "knowledge": {"baseDir": "/data/medical"},
  177. })
  178. cfg_a = load_instance(os.path.join(inst_a, "instance.yml"))
  179. cfg_b = load_instance(os.path.join(inst_b, "instance.yml"))
  180. # Same template
  181. assert cfg_a["type"] == cfg_b["type"] == "react"
  182. assert cfg_a["systemPrompt"] == cfg_b["systemPrompt"]
  183. # Different data dirs
  184. assert cfg_a["knowledge"]["baseDir"] == "/data/maritime"
  185. assert cfg_b["knowledge"]["baseDir"] == "/data/medical"
  186. assert cfg_a["_instance_name"] == "Maritime"
  187. assert cfg_b["_instance_name"] == "Medical"