@@ -614,11 +614,313 @@ Run benchmarks on mobile devices.
614614
615615---
616616
617+ ---
618+
619+ #### B-31: LocalLLMBehavior - Bridge Local Backends to AgentBehavior API
620+ ** Priority** : High
621+ ** Component** : Agent Runtime / Backends
622+ ** Size** : M
623+ ** Blocking** : LLM agent integration with foraging scene
624+
625+ ** Problem Statement** :
626+ The codebase has two separate agent systems that aren't connected:
627+
628+ 1 . ** AgentBehavior system** (` python/agent_runtime/behavior.py ` )
629+ - Classes: ` AgentBehavior ` , ` SimpleAgentBehavior ` , ` LLMAgentBehavior `
630+ - Used by: IPC server's ` /observe ` endpoint via ` server.behaviors ` dict
631+ - Learner-facing API with three tiers (beginner/intermediate/advanced)
632+
633+ 2 . ** Local LLM Backend system** (` python/backends/ ` )
634+ - Classes: ` LlamaCppBackend ` , ` VLLMBackend `
635+ - Used by: ` run_ipc_server_with_gpu.py ` via ` Agent ` class
636+ - GPU-accelerated local inference
637+
638+ ** Current Flow (Broken)** :
639+ ```
640+ Godot foraging scene
641+ ↓ POST /observe (sends observation)
642+ python/ipc/server.py line 424-475
643+ ↓ checks server.behaviors dict → EMPTY
644+ ↓ falls back to mock rule-based logic
645+ (LlamaCppBackend never gets called)
646+ ```
647+
648+ ** Goal** : Create ` LocalLLMBehavior ` class that wraps local backends (LlamaCppBackend, VLLMBackend) and implements the ` AgentBehavior ` interface, so local LLMs can power agents via the ` /observe ` endpoint.
649+
650+ ---
651+
652+ ** Architecture Context** :
653+
654+ The IPC server (` python/ipc/server.py ` ) has a ` behaviors ` dict that maps ` agent_id → AgentBehavior ` . When ` /observe ` receives an observation:
655+ - Line 424-426: Gets agent_id from observation
656+ - Line 427: Checks ` behavior = self.behaviors.get(agent_id) `
657+ - Line 428-471: If behavior exists, calls ` behavior.decide(observation, tools) `
658+ - Line 472-475: If no behavior, falls back to ` _make_mock_decision() `
659+
660+ The ` LLMAgentBehavior ` class (line 281-422 in behavior.py) already supports cloud LLMs (Anthropic, OpenAI, Ollama) but NOT the local backends (LlamaCppBackend, VLLMBackend).
661+
662+ ---
663+
664+ ** Files to Understand First** :
665+
666+ 1 . ` python/agent_runtime/behavior.py ` - Base classes, especially ` LLMAgentBehavior `
667+ 2 . ` python/backends/base.py ` - ` BaseBackend ` interface with ` generate() ` and ` generate_with_tools() `
668+ 3 . ` python/backends/llama_cpp_backend.py ` - Local GPU inference implementation
669+ 4 . ` python/backends/vllm_backend.py ` - vLLM server client
670+ 5 . ` python/ipc/server.py ` - See ` /observe ` endpoint (line 397-493) and ` create_server() ` function
671+ 6 . ` python/user_agents/examples/llm_forager.py ` - Example of LLMAgentBehavior subclass
672+ 7 . ` python/scenarios/foraging.py ` - Scenario definition with ` to_system_prompt() ` method
673+
674+ ---
675+
676+ ** Implementation Tasks** :
677+
678+ - [ ] ** Create ` python/agent_runtime/local_llm_behavior.py ` **
679+
680+ ``` python
681+ """
682+ Local LLM behavior adapter.
683+
684+ Bridges the AgentBehavior interface to local LLM backends
685+ (LlamaCppBackend, VLLMBackend) for GPU-accelerated inference.
686+ """
687+
688+ from agent_runtime.behavior import AgentBehavior
689+ from agent_runtime.schemas import AgentDecision, Observation, ToolSchema
690+ from backends.base import BaseBackend
691+
692+ class LocalLLMBehavior (AgentBehavior ):
693+ """
694+ AgentBehavior implementation using local LLM backends.
695+
696+ This bridges the learner-facing AgentBehavior API to the
697+ high-performance local backends (llama.cpp, vLLM).
698+
699+ Example:
700+ from backends import LlamaCppBackend, BackendConfig
701+ from agent_runtime.local_llm_behavior import LocalLLMBehavior
702+
703+ config = BackendConfig(model_path="models/llama.gguf", n_gpu_layers=-1)
704+ backend = LlamaCppBackend(config)
705+
706+ behavior = LocalLLMBehavior(
707+ backend=backend,
708+ system_prompt="You are a foraging agent..."
709+ )
710+
711+ # Register with IPC server
712+ server = create_server(behaviors={"agent_001": behavior})
713+ """
714+
715+ def __init__ (
716+ self ,
717+ backend : BaseBackend,
718+ system_prompt : str = " " ,
719+ temperature : float = 0.7 ,
720+ max_tokens : int = 256 ,
721+ ):
722+ self .backend = backend
723+ self .system_prompt = system_prompt
724+ self .temperature = temperature
725+ self .max_tokens = max_tokens
726+ self ._memory: list[Observation] = []
727+ self ._memory_capacity = 10
728+
729+ def decide (self , observation : Observation, tools : list[ToolSchema]) -> AgentDecision:
730+ """ Use local LLM to decide next action."""
731+ # 1. Store observation in memory
732+ # 2. Build prompt from system_prompt + observation + tools
733+ # 3. Call backend.generate_with_tools() or backend.generate()
734+ # 4. Parse response into AgentDecision
735+ # 5. Handle errors gracefully (return idle on failure)
736+ pass
737+
738+ def _build_prompt (self , observation : Observation, tools : list[ToolSchema]) -> str :
739+ """ Build the prompt for the LLM."""
740+ # Include: system prompt, current observation, available tools, memory
741+ pass
742+
743+ def _parse_response (self , response : str , tools : list[ToolSchema]) -> AgentDecision:
744+ """ Parse LLM response into AgentDecision."""
745+ # Handle JSON parsing, validate tool names, extract params
746+ pass
747+
748+ def on_episode_start (self ) -> None :
749+ """ Clear memory at episode start."""
750+ self ._memory.clear()
751+ ```
752+
753+ - [ ] ** Add factory function for easy creation**
754+
755+ ``` python
756+ def create_local_llm_behavior (
757+ model_path : str ,
758+ backend_type : str = " llama_cpp" , # or "vllm"
759+ n_gpu_layers : int = - 1 ,
760+ system_prompt : str = " " ,
761+ ** kwargs
762+ ) -> LocalLLMBehavior:
763+ """ Factory to create LocalLLMBehavior with backend."""
764+ pass
765+ ```
766+
767+ - [ ] ** Update ` python/ipc/server.py ` ` create_server() ` function** (line 520-541)
768+
769+ Add optional parameter to auto-create local LLM behavior:
770+ ``` python
771+ def create_server (
772+ runtime : AgentRuntime | None = None ,
773+ behaviors : dict | None = None ,
774+ host : str = " 127.0.0.1" ,
775+ port : int = 5000 ,
776+ default_behavior : AgentBehavior | None = None , # NEW: fallback for unknown agents
777+ ) -> IPCServer:
778+ ```
779+
780+ - [ ] ** Create ` python/run_local_llm_forager.py ` ** - Startup script
781+
782+ ``` python
783+ """
784+ Run foraging scene with local LLM agent.
785+
786+ Usage:
787+ python run_local_llm_forager.py --model models/llama.gguf --gpu-layers -1
788+ """
789+
790+ # 1. Load local backend (LlamaCppBackend or VLLMBackend)
791+ # 2. Create LocalLLMBehavior with foraging system prompt
792+ # 3. Register behavior for agent_id matching Godot scene
793+ # 4. Start IPC server
794+ ```
795+
796+ - [ ] ** Integrate scenario system prompts**
797+
798+ Use ` python/scenarios/foraging.py ` to generate system prompts:
799+ ``` python
800+ from scenarios import get_scenario
801+
802+ scenario = get_scenario(" foraging" )
803+ system_prompt = scenario.to_system_prompt(include_hints = True )
804+ behavior = LocalLLMBehavior(backend = backend, system_prompt = system_prompt)
805+ ```
806+
807+ - [ ] ** Add to ` __init__.py ` exports**
808+
809+ Update ` python/agent_runtime/__init__.py ` to export ` LocalLLMBehavior `
810+
811+ - [ ] ** Write tests**
812+
813+ Create ` tests/test_local_llm_behavior.py ` :
814+ - Test prompt building
815+ - Test response parsing (valid JSON, invalid JSON, missing fields)
816+ - Test tool validation
817+ - Test memory management
818+ - Mock backend for unit tests
819+
820+ ---
821+
822+ ** Reference: How LLMAgentBehavior works** (for comparison):
823+
824+ ``` python
825+ # From behavior.py lines 321-338
826+ def complete (self , prompt : str , system : str | None = None , temperature : float = 0.7 ) -> str :
827+ if self ._client is None :
828+ self ._client = self ._create_client()
829+ sys_prompt = system if system is not None else self .system_prompt
830+ return self ._call_llm(prompt, sys_prompt, temperature)
831+
832+ # _call_llm handles Anthropic/OpenAI/Ollama APIs
833+ ```
834+
835+ LocalLLMBehavior should follow similar pattern but call:
836+ ``` python
837+ result = self .backend.generate_with_tools(prompt, tools_as_dicts, temperature)
838+ # or
839+ result = self .backend.generate(prompt, temperature, max_tokens)
840+ ```
841+
842+ ---
843+
844+ ** Reference: Backend API** (from ` backends/base.py ` ):
845+
846+ ``` python
847+ class BaseBackend (ABC ):
848+ @abstractmethod
849+ def generate (self , prompt : str , temperature : float | None , max_tokens : int | None ) -> GenerationResult:
850+ pass
851+
852+ @abstractmethod
853+ def generate_with_tools (self , prompt : str , tools : list[dict ], temperature : float | None ) -> GenerationResult:
854+ pass
855+
856+ @dataclass
857+ class GenerationResult :
858+ text: str
859+ tokens_used: int
860+ finish_reason: str
861+ metadata: dict[str , Any]
862+ ```
863+
864+ ---
865+
866+ ** Agent ID Matching** :
867+
868+ The Godot foraging scene uses ` SimpleAgent ` which has an ` agent_id ` property:
869+ - If set in scene: uses that value
870+ - If empty: auto-generates ` "agent_" + timestamp `
871+
872+ For testing, either:
873+ 1 . Set ` agent_id = "forager_001" ` in the Godot scene's SimpleAgent node
874+ 2 . OR use a wildcard/default behavior in the server
875+
876+ Recommend option 1 for explicit control.
877+
878+ ---
879+
880+ ** Acceptance Criteria** :
881+
882+ - [ ] ` LocalLLMBehavior ` implements full ` AgentBehavior ` interface
883+ - [ ] Works with both ` LlamaCppBackend ` and ` VLLMBackend `
884+ - [ ] Integrates with scenario system prompts (` to_system_prompt() ` )
885+ - [ ] ` run_local_llm_forager.py ` successfully runs foraging scene with local LLM
886+ - [ ] Agent makes reasonable decisions (moves to resources, avoids hazards)
887+ - [ ] Graceful error handling (returns idle on LLM failures)
888+ - [ ] All tests pass
889+ - [ ] Pre-commit hooks pass (black, ruff, mypy)
890+
891+ ---
892+
893+ ** Testing Instructions** :
894+
895+ 1 . Download a GGUF model (e.g., ` llama-2-7b-chat.Q4_K_M.gguf ` )
896+ 2 . Run: ` python run_local_llm_forager.py --model path/to/model.gguf `
897+ 3 . Open Godot and run the foraging scene
898+ 4 . Observe agent behavior in console logs
899+ 5 . Verify decisions are LLM-generated (not mock logic)
900+
901+ ---
902+
903+ ** Related Files Summary** :
904+
905+ | File | Purpose |
906+ | ------| ---------|
907+ | ` python/agent_runtime/behavior.py ` | Base classes to extend |
908+ | ` python/backends/llama_cpp_backend.py ` | Local GPU backend |
909+ | ` python/backends/vllm_backend.py ` | vLLM server backend |
910+ | ` python/backends/base.py ` | Backend interface |
911+ | ` python/ipc/server.py ` | IPC server with /observe endpoint |
912+ | ` python/scenarios/foraging.py ` | System prompt source |
913+ | ` python/user_agents/examples/llm_forager.py ` | Reference implementation |
914+ | ` scripts/simple_agent.gd ` | Godot agent (sends agent_id) |
915+ | ` scripts/base_scene_controller.gd ` | Sends observations to /observe |
916+
917+ ---
918+
617919## Total Backlog Summary
618920
619- - ** High Priority** : 7 items
921+ - ** High Priority** : 8 items
620922- ** Medium Priority** : 15 items
621923- ** Low Priority** : 8 items
622- - ** Total** : 30 items
924+ - ** Total** : 31 items
623925
624926** Estimated Timeline** : 6-12 months for all items with 2 developers
0 commit comments