config.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. """
  2. Unified config loader for LambdaRAG scripts.
  3. All scripts import paths from here instead of hardcoding.
  4. Usage:
  5. from config import cfg
  6. print(cfg.raw_dir) # Path to raw files
  7. print(cfg.processed_dir) # Path to processed text
  8. print(cfg.wiki_dir) # Path to wiki
  9. print(cfg.base_dir) # Knowledge base directory
  10. """
  11. import os
  12. import sys
  13. from pathlib import Path
  14. import yaml
  15. class Config:
  16. """Loads paths from agent-config.yml + environment variables."""
  17. def __init__(self):
  18. self._cfg = {}
  19. self._load()
  20. def _find_agent_config(self):
  21. """Search for agent-config.yml in standard locations"""
  22. candidates = [
  23. os.environ.get('AGENT_CONFIG', ''),
  24. # Relative to this script: scripts/ -> parent = agent dir
  25. str(Path(__file__).parent.parent / 'agent-config.yml'),
  26. # Docker paths
  27. '/app/agentexample/qaagent67lambda/agent-config.yml',
  28. '/app/agentexample/qaagent67/agent-config.yml',
  29. ]
  30. for p in candidates:
  31. if p and Path(p).is_file():
  32. return Path(p)
  33. return None
  34. def _find_instance_config(self):
  35. """Search for instance.yml.
  36. Resolution order:
  37. 1. INSTANCE_CONFIG env var
  38. 2. {baseDir}/instance.yml (after agent-config.yml is loaded)
  39. 3. instance.yml in the directory containing this config.py file
  40. (lets build scripts auto-detect domain when run from a domain dir)
  41. """
  42. here = Path(__file__).parent
  43. base_dir = (self._cfg.get("knowledge", {}) or {}).get("baseDir", "")
  44. candidates = [
  45. os.environ.get("INSTANCE_CONFIG", ""),
  46. str(Path(base_dir) / "instance.yml") if base_dir else "",
  47. str(here / "instance.yml"),
  48. ]
  49. for p in candidates:
  50. if p and Path(p).is_file():
  51. return Path(p)
  52. return None
  53. def _load(self):
  54. config_path = self._find_agent_config()
  55. if config_path:
  56. with open(config_path) as f:
  57. self._cfg = yaml.safe_load(f) or {}
  58. # Load instance.yml (overrides agent-config.yml)
  59. instance_path = self._find_instance_config()
  60. if instance_path and Path(instance_path).is_file():
  61. with open(instance_path) as f:
  62. instance = yaml.safe_load(f) or {}
  63. # Deep merge: instance overrides agent config
  64. self._cfg = self._deep_merge(self._cfg, instance)
  65. def _deep_merge(self, base, override):
  66. import copy
  67. result = copy.deepcopy(base)
  68. for k, v in override.items():
  69. if k in result and isinstance(result[k], dict) and isinstance(v, dict):
  70. result[k] = self._deep_merge(result[k], v)
  71. else:
  72. result[k] = copy.deepcopy(v)
  73. return result
  74. # ── Knowledge paths ──
  75. @property
  76. def base_dir(self):
  77. return Path(
  78. self._cfg.get('knowledge', {}).get('baseDir', '')
  79. or os.environ.get('KNOWLEDGE_DIR', '')
  80. or str(Path(__file__).parent.parent / 'knowledge')
  81. )
  82. @property
  83. def raw_dir(self):
  84. return Path(
  85. self._cfg.get('knowledge', {}).get('rawDir', '')
  86. or str(self.base_dir / 'raw')
  87. )
  88. @property
  89. def processed_dir(self):
  90. return Path(
  91. self._cfg.get('knowledge', {}).get('processedDir', '')
  92. or str(self.base_dir / 'processed')
  93. )
  94. @property
  95. def index_file(self):
  96. return Path(
  97. self._cfg.get('knowledge', {}).get('indexFile', '')
  98. or str(self.base_dir / 'rag_index.json')
  99. )
  100. @property
  101. def keyword_file(self):
  102. return Path(
  103. self._cfg.get('knowledge', {}).get('keywordFile', '')
  104. or str(self.base_dir / 'rag_index.keywords.json')
  105. )
  106. @property
  107. def vector_index(self):
  108. return Path(
  109. self._cfg.get('knowledge', {}).get('vectorIndex', '')
  110. or str(self.base_dir / 'rag_vectors_v2.npy')
  111. )
  112. @property
  113. def vector_meta(self):
  114. return Path(
  115. self._cfg.get('knowledge', {}).get('vectorMeta', '')
  116. or str(self.base_dir / 'rag_vectors_meta_v2.pkl')
  117. )
  118. # ── Wiki paths ──
  119. @property
  120. def wiki_dir(self):
  121. return Path(
  122. self._cfg.get('wiki', {}).get('dir', '')
  123. or os.environ.get('WIKI_DIR', '')
  124. or str(Path(__file__).parent.parent / 'wiki')
  125. )
  126. @property
  127. def relations_file(self):
  128. return Path(
  129. self._cfg.get('wiki', {}).get('relationsFile', '')
  130. or str(self.wiki_dir / 'relations.json')
  131. )
  132. # ── Embedding config ──
  133. @property
  134. def embed_url(self):
  135. return (
  136. self._cfg.get('retrieval', {}).get('skills', {}).get('embedding', {}).get('baseUrl', '')
  137. or os.environ.get('OLLAMA_EMBED_URL', 'http://127.0.0.1:11435/api/embed')
  138. )
  139. @property
  140. def embed_model(self):
  141. return (
  142. self._cfg.get('retrieval', {}).get('skills', {}).get('embedding', {}).get('model', '')
  143. or os.environ.get('EMBED_MODEL', 'bge-m3')
  144. )
  145. # ── LLM config ──
  146. @property
  147. def vllm_url(self):
  148. return os.environ.get('VLLM_URL', 'http://127.0.0.1:8000/v1/chat/completions')
  149. @property
  150. def llm_model(self):
  151. return self._cfg.get('model', {}).get('name', 'qwen2.5-32b')
  152. # ── Retrieval weights ──
  153. @property
  154. def weight_profiles(self):
  155. return self._cfg.get('retrieval', {}).get('weights', {
  156. 'fact': {'bm25': 1.0, 'vector': 0.5, 'graph': 0.1, 'wiki': 0.1},
  157. 'compare': {'bm25': 0.5, 'vector': 0.8, 'graph': 0.4, 'wiki': 0.7},
  158. 'relation': {'bm25': 0.5, 'vector': 0.3, 'graph': 0.7, 'wiki': 0.8},
  159. 'synthesis': {'bm25': 0.6, 'vector': 0.5, 'graph': 0.5, 'wiki': 0.6},
  160. 'temporal': {'bm25': 1.0, 'vector': 0.2, 'graph': 0.05, 'wiki': 0.2},
  161. })
  162. @property
  163. def default_weights(self):
  164. return self._cfg.get('retrieval', {}).get('weights', {}).get('default',
  165. {'bm25': 0.7, 'vector': 0.5, 'graph': 0.3, 'wiki': 0.3})
  166. @property
  167. def rrf_k(self):
  168. return self._cfg.get('retrieval', {}).get('orchestration', {}).get('rrfK', 60)
  169. # ── RAG params ──
  170. @property
  171. def chunk_size(self):
  172. return self._cfg.get('rag', {}).get('chunkSize', 512)
  173. @property
  174. def chunk_overlap(self):
  175. return self._cfg.get('rag', {}).get('chunkOverlap', 64)
  176. @property
  177. def top_k(self):
  178. return self._cfg.get('rag', {}).get('topK', 5)
  179. # ── Workspace ──
  180. @property
  181. def workspace_dir(self):
  182. return Path(__file__).parent.parent / 'workspace'
  183. # ── Full config dict ──
  184. @property
  185. def raw(self):
  186. return self._cfg
  187. def __repr__(self):
  188. return (f"Config(base_dir={self.base_dir}, raw_dir={self.raw_dir}, "
  189. f"wiki_dir={self.wiki_dir}, embed_url={self.embed_url})")
  190. # Singleton
  191. cfg = Config()
  192. if __name__ == '__main__':
  193. print(cfg)
  194. print(f"\n base_dir: {cfg.base_dir}")
  195. print(f" raw_dir: {cfg.raw_dir}")
  196. print(f" processed_dir: {cfg.processed_dir}")
  197. print(f" index_file: {cfg.index_file}")
  198. print(f" wiki_dir: {cfg.wiki_dir}")
  199. print(f" relations: {cfg.relations_file}")
  200. print(f" embed_url: {cfg.embed_url}")
  201. print(f" vllm_url: {cfg.vllm_url}")
  202. print(f" weights: {cfg.weight_profiles}")
  203. print(f" chunk_size: {cfg.chunk_size}")
  204. print(f" workspace: {cfg.workspace_dir}")