Stored Object Resolution in Enhanced Bridge
Overview
The enhanced bridge provides a mechanism for storing Python objects and referencing them later using string identifiers like "stored.object_id"
. This is crucial for DSPy integration where objects need to persist across multiple bridge calls.
The Problem We Solved
When configuring DSPy with a language model (LM), the enhanced bridge stores the LM object with an ID like "default_lm"
. However, when DSPy modules try to use this LM, they receive the string "stored.default_lm"
instead of the actual LM object, causing errors like “No LM is loaded”.
How Stored Object Resolution Works
1. Object Storage
When an object needs to persist across calls:
# In enhanced_bridge.py
self.stored_objects["default_lm"] = lm_instance
2. String References
Elixir sends references as strings:
# In Elixir
%{lm: "stored.default_lm"}
3. Automatic Resolution
The _resolve_stored_references
method recursively resolves these strings:
def _resolve_stored_references(self, data):
if isinstance(data, str):
if data.startswith("stored."):
parts = data.split(".", 1)
if len(parts) == 2:
object_id = parts[1]
if object_id in self.stored_objects:
return self.stored_objects[object_id]
4. Resolution Points
The resolution happens at two critical points in handle_call
:
# Resolve references in arguments BEFORE execution
call_args = self._resolve_stored_references(call_args)
call_kwargs = self._resolve_stored_references(call_kwargs)
Key Implementation Details
Recursive Resolution
The method handles nested data structures:
- Strings: Checks if they match
"stored.object_id"
pattern - Dictionaries: Recursively resolves all values
- Lists: Recursively resolves all items
- Tuples: Preserves immutability while resolving
Error Handling
If a stored object is not found:
raise ValueError(f"Stored object '{object_id}' not found")
Common Use Cases
1. Language Model Configuration
# Python side stores the LM
lm = dspy.LM("gemini/gemini-2.0-flash-lite", api_key=api_key)
dspy.configure(lm=lm)
self.stored_objects["default_lm"] = lm
# Elixir references it
{:ok, _} = Python.call(pid, "dspy.configure", %{lm: "stored.default_lm"})
2. Module Persistence
# Store a DSPy module
predictor = dspy.Predict("question -> answer")
self.stored_objects["qa_predictor"] = predictor
# Use it later
Python.call(pid, "stored.qa_predictor.__call__", %{question: "What is 2+2?"})
3. Complex Object Graphs
The resolution works with nested structures:
config = {
"primary_lm": "stored.default_lm",
"fallback_lm": "stored.backup_lm",
"tools": ["stored.search_tool", "stored.calc_tool"]
}
# All stored references are resolved automatically
Debugging Tips
1. Check Stored Objects
Add logging to see what’s stored:
print(f"Stored objects: {list(self.stored_objects.keys())}", file=sys.stderr)
2. Trace Resolution
Add logging in _resolve_stored_references
:
if data.startswith("stored."):
print(f"Resolving: {data} -> {type(self.stored_objects.get(object_id))}", file=sys.stderr)
3. Verify DSPy State
Check what DSPy actually receives:
print(f"dspy.settings.lm type: {type(dspy.settings.lm)}", file=sys.stderr)
print(f"dspy.settings.lm value: {dspy.settings.lm}", file=sys.stderr)
Best Practices
- Consistent Naming: Use descriptive IDs like
"default_lm"
,"search_tool"
- Lifecycle Management: Clear stored objects when done with
handle_clear_session
- Error Handling: Always handle the case where a stored object might not exist
- Type Safety: Document what type of object each ID should contain
Example: Complete LM Configuration Flow
# 1. Configure DSPy with LM
def handle_configure_lm(self, args):
lm = dspy.LM(f"gemini/{model}", api_key=api_key)
dspy.configure(lm=lm)
# Store for later reference
self.stored_objects["default_lm"] = lm
# Also store in dspy settings
self.stored_objects["test_lm"] = lm # Alternative name
return {"status": "ok", "message": "LM configured"}
# 2. Later call uses stored reference
def some_dspy_operation(self, args):
# This will automatically resolve "stored.default_lm" to the actual LM object
result = self.handle_call({
"target": "dspy.Predict",
"kwargs": {"signature": "question -> answer", "lm": "stored.default_lm"}
})
Related Documentation
- PYTHON_TOOL_BRIDGE.md - Overall bridge architecture
- PYTHON_TOOL_BRIDGE_COMMON.md - Protocol-specific implementations