1.0 Release
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
# --- Connection Settings ---
|
||||
api:
|
||||
base_url: "http://framework.tawny-bellatrix.ts.net:1234"
|
||||
api_version: "/v1/"
|
||||
|
||||
# --- Model Settings ---
|
||||
models:
|
||||
enrich: "lm_studio/qwen-"
|
||||
embedding: "text-embedding-qwen3-embedding-8b"
|
||||
retrieval: "lm_studio/qwen/qwen3-30b-a3b-2507"
|
||||
|
||||
# --- Ingestion Settings ---
|
||||
ingestion:
|
||||
data_dir: "/home/cosmic/DnD"
|
||||
db_path: "./data/dmv.db"
|
||||
active_llms: 5
|
||||
parallel_requests_per_llm: 2
|
||||
chunk_size: 800
|
||||
chunk_overlap: 100
|
||||
embedding_batch_size: 32
|
||||
time_file_location: "./data/time_file.txt"
|
||||
|
||||
# ---- Agent Settings ----
|
||||
ingestion_agent:
|
||||
ingestion_signature: |
|
||||
You are an expert Dungeon Master's assistant.
|
||||
Analyze the provided notes and extract a concise synopsis and relevant metadata.
|
||||
synopsis = A one-sentence summary of the document.
|
||||
tags = Relevant tags (NPCs, Locations, Items, Plot Points).
|
||||
entities = a list of Key names of people, places, or factions.
|
||||
"note -> synopsis:str, tags: list[str], entities: list[str]"
|
||||
|
||||
retrieval_agent:
|
||||
retrieval_signature: |
|
||||
You are an expert Dungeon Master's assistant.
|
||||
Given the context and the question, answer the question.
|
||||
Do not make things up, base all of your answers on the context.
|
||||
Always site your sources
|
||||
@@ -1,7 +1,7 @@
|
||||
import yaml
|
||||
|
||||
|
||||
def load_config(config_path="src/config.yaml"):
|
||||
def load_config(config_path="config.yaml"):
|
||||
with open(config_path) as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
+2
-3
@@ -6,10 +6,9 @@ CFG = load_config()
|
||||
API_BASE = CFG["api"]["base_url"]
|
||||
API_VERSION = CFG["api"]["api_version"]
|
||||
|
||||
|
||||
class LocalLMEmbeddings(Embeddings):
|
||||
def __init__(
|
||||
self, model: str, base_url: str = API_BASE, batch_size: int = 32
|
||||
):
|
||||
def __init__(self, model: str, base_url: str = API_BASE, batch_size: int = 32):
|
||||
self.url = f"{base_url}/{API_VERSION}embeddings"
|
||||
self.model = model
|
||||
self.batch_size = batch_size
|
||||
|
||||
@@ -5,11 +5,15 @@ from config_loader import load_config
|
||||
CFG = load_config()
|
||||
INGESTION_CONFIG = CFG["ingestion_agent"]
|
||||
|
||||
|
||||
class IngestionSignature(dspy.Signature):
|
||||
f"{INGESTION_CONFIG["ingestion_signature"]}"
|
||||
f"{INGESTION_CONFIG['ingestion_signature']}"
|
||||
|
||||
note: str = dspy.InputField(desc="The DM notes or session recap content.")
|
||||
answer: dict[str,str|List] = dspy.OutputField(desc="the metadata dictionary with the keys; synopsis, tags, entities")
|
||||
answer: dict[str, str | List] = dspy.OutputField(
|
||||
desc="the metadata dictionary with the keys; synopsis, tags, entities"
|
||||
)
|
||||
|
||||
|
||||
class IngestionAgent(dspy.Module):
|
||||
def __init__(self):
|
||||
|
||||
@@ -28,13 +28,12 @@ def retrieve_from_turso(embedded_question, k=5):
|
||||
rows = cur.fetchall()
|
||||
return rows
|
||||
|
||||
|
||||
# --- DSPy Signature ---
|
||||
class DnDContextQA(dspy.Signature):
|
||||
f"{RETRIEVAL_CONFIG["retrieval_signature"]}"
|
||||
f"{RETRIEVAL_CONFIG['retrieval_signature']}"
|
||||
|
||||
context = dspy.InputField(
|
||||
desc="Relevant chunks and metadata from the campaign notes."
|
||||
)
|
||||
context = dspy.InputField(desc="Relevant chunks and metadata from the campaign notes.")
|
||||
question = dspy.InputField()
|
||||
answer = dspy.OutputField(desc="A detailed answer based on the notes, citing the source file.")
|
||||
|
||||
@@ -45,16 +44,14 @@ class DnDRAG(dspy.Module):
|
||||
self.embeddings_model = LocalLMEmbeddings(
|
||||
model=EMBEDDING_MODEL,
|
||||
base_url=API_BASE,
|
||||
batch_size=1, # we only send 1 question at a time.
|
||||
)
|
||||
# Tools exposed to the ReAct loop
|
||||
self.tools = [
|
||||
self.load_file
|
||||
]
|
||||
self.generate_answer = dspy.ReAct(signature=DnDContextQA,tools=self.tools)
|
||||
batch_size=1, # we only send 1 question at a time.
|
||||
)
|
||||
# Tools exposed to the ReAct loop
|
||||
self.tools = [self.load_file]
|
||||
self.generate_answer = dspy.ReAct(signature=DnDContextQA, tools=self.tools)
|
||||
|
||||
def forward(self, question):
|
||||
# TODO: Add step here to LLM Expand
|
||||
# TODO: Add step here to LLM Expand
|
||||
# given the current question, generate 3-5 distinct search queries.
|
||||
# embed all the questions
|
||||
embedded_question = self.embeddings_model._post_request(question)
|
||||
@@ -66,13 +63,12 @@ class DnDRAG(dspy.Module):
|
||||
for i, row in enumerate(results):
|
||||
source = row[0] # file_path
|
||||
synopsis = row[1] # synopsis
|
||||
tags = row[2] # tags
|
||||
tags = row[2] # tags
|
||||
entities = row[3] # entities
|
||||
content = row[4] # chunk_data
|
||||
|
||||
|
||||
context_parts.append(f"""
|
||||
--- Chunk {i+1} from {source} ---
|
||||
--- Chunk {i + 1} from {source} ---
|
||||
synopsis: {synopsis},
|
||||
tags: {tags},
|
||||
entities: {entities}
|
||||
@@ -82,7 +78,7 @@ entities: {entities}
|
||||
# print('Closest embedding hits')
|
||||
# for part in context_parts:
|
||||
# print(part)
|
||||
|
||||
|
||||
context = "\n\n".join(context_parts)
|
||||
|
||||
prediction = self.generate_answer(context=context, question=question)
|
||||
@@ -97,4 +93,4 @@ entities: {entities}
|
||||
except Exception:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
+6
-7
@@ -13,6 +13,7 @@ RETRIEVE_MODEL = CFG["models"]["retrieval"]
|
||||
API_BASE = CFG["api"]["base_url"]
|
||||
API_VERSION = CFG["api"]["api_version"]
|
||||
|
||||
|
||||
class CallbackHandler(BaseCallback):
|
||||
"""Custom callback class for logging agent interactions."""
|
||||
|
||||
@@ -47,6 +48,7 @@ class CallbackHandler(BaseCallback):
|
||||
def _is_reasoning_output(self, outputs):
|
||||
return any(k.startswith("Thought") for k in outputs)
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Set up logging configuration for Merlin."""
|
||||
# Create a custom logger
|
||||
@@ -60,15 +62,11 @@ def setup_logging():
|
||||
console_handler.setLevel(logging.INFO)
|
||||
|
||||
# Create a file handler with rotation every 5MB
|
||||
file_handler = RotatingFileHandler(
|
||||
"dmv.log", maxBytes=5 * 1024 * 1024, backupCount=3
|
||||
)
|
||||
file_handler = RotatingFileHandler("data/dmv.log", maxBytes=5 * 1024 * 1024, backupCount=3)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a formatter
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
|
||||
# Set the formatter for the handler
|
||||
console_handler.setFormatter(formatter)
|
||||
@@ -129,5 +127,6 @@ def main():
|
||||
except Exception as e:
|
||||
print(f"\n⚠️ An error occurred: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user