Add MiniMax LLM integration and local .env support

- Add .env to .gitignore (API keys stay local)
- Add LLM client with MiniMax and OpenAI support
- Update config to load from environment variables
- Wire up Architect agent to actually call the LLM
- Add MiniMax API key to local .env file
This commit is contained in:
2026-03-12 18:33:29 +00:00
parent 40378ad65e
commit e151cee69f
5 changed files with 330 additions and 31 deletions
+41 -4
View File
@@ -1,11 +1,12 @@
"""Base agent class for Opus Orchestrator."""
from abc import ABC, abstractmethod
from typing import Any, Generic, TypeVar
from typing import Any, Generic, Optional, TypeVar
from pydantic import BaseModel
from opus_orchestrator.config import AgentConfig, get_config
from opus_orchestrator.utils.llm import LLMClient, get_llm_client
T = TypeVar("T", bound=BaseModel)
@@ -23,9 +24,6 @@ class AgentResponse(BaseModel):
arbitrary_types_allowed = True
from typing import Optional
class BaseAgent(ABC, Generic[T]):
"""Base class for all Opus agents.
@@ -49,6 +47,14 @@ class BaseAgent(ABC, Generic[T]):
self.system_prompt = system_prompt
self.output_schema = output_schema
self.config = config or get_config().agent
self._llm_client: Optional[LLMClient] = None
@property
def llm_client(self) -> LLMClient:
"""Get or create LLM client."""
if self._llm_client is None:
self._llm_client = get_llm_client()
return self._llm_client
@abstractmethod
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
@@ -63,6 +69,31 @@ class BaseAgent(ABC, Generic[T]):
"""
pass
async def call_llm(
self,
system_prompt: str,
user_prompt: str,
temperature: Optional[float] = None,
) -> str:
"""Call the LLM with prompts.
Args:
system_prompt: System prompt
user_prompt: User prompt
temperature: Optional temperature override
Returns:
Generated text
"""
temp = temperature if temperature is not None else self.config.temperature
return await self.llm_client.complete(
system_prompt=system_prompt,
user_prompt=user_prompt,
temperature=temp,
max_tokens=self.config.max_tokens,
)
def build_system_prompt(self, context: dict[str, Any]) -> str:
"""Build the full system prompt with context.
@@ -104,3 +135,9 @@ class BaseAgent(ABC, Generic[T]):
Please complete this task following the methodology specified in your system prompt.
"""
async def cleanup(self):
"""Clean up resources."""
if self._llm_client:
await self._llm_client.close()
self._llm_client = None
+52 -25
View File
@@ -84,8 +84,6 @@ class ArchitectAgent(BaseAgent):
Returns:
AgentResponse with BookBlueprint
"""
# This is a placeholder - actual implementation would call the LLM
# For now, we'll structure the prompt
raw_content = input_data.get("raw_content", "")
intent = input_data.get("intent", {})
genre = intent.get("genre", "general")
@@ -107,23 +105,34 @@ class ArchitectAgent(BaseAgent):
Generate a complete story blueprint following the Architect's methodology.
Include all sections specified in your system prompt.
Be specific and detailed. The blueprint should be comprehensive enough that another agent could write each chapter from it.
"""
# In actual implementation, this would call the LLM
# For now, return a structured response
return AgentResponse(
success=True,
output={
"status": "blueprint_generated",
"message": "Blueprint generation would be executed here with LLM",
},
metadata={
"role": "Architect",
"input_word_count": len(raw_content.split()),
"target_word_count": target_word_count,
"genre": genre,
},
)
try:
# Call the LLM
result = await self.call_llm(
system_prompt=self.build_system_prompt(context),
user_prompt=user_prompt,
)
return AgentResponse(
success=True,
output=result,
metadata={
"role": "Architect",
"input_word_count": len(raw_content.split()),
"target_word_count": target_word_count,
"genre": genre,
},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Architect"},
)
async def expand_chapter(
self,
@@ -157,13 +166,31 @@ Include all sections specified in your system prompt.
Expand this chapter beat into a detailed scene specification following
Template B from the Fiction Fortress methodology.
Include:
1. Opening beat - how the scene opens
2. Conflict beat - what escalates tension
3. Turn beat - what changes the situation
4. Ending beat - what hook or change ends the scene
Be specific about character motivations, dialogue objectives, and emotional progression.
"""
return AgentResponse(
success=True,
output={
"status": "chapter_expanded",
"chapter_number": chapter.chapter_number,
},
metadata={"role": "Architect", "task": "chapter_expansion"},
)
try:
result = await self.call_llm(
system_prompt=self.build_system_prompt(context),
user_prompt=user_prompt,
)
return AgentResponse(
success=True,
output=result,
metadata={"role": "Architect", "task": "chapter_expansion"},
)
except Exception as e:
return AgentResponse(
success=False,
output=None,
error=str(e),
metadata={"role": "Architect"},
)