fleet-memory/hindsight-clients/python/hindsight_client/hindsight_client.py
2025-11-25 19:28:26 +01:00

409 lines
12 KiB
Python

"""
Clean, pythonic wrapper for the Hindsight API client.
This file is MAINTAINED and NOT auto-generated. It provides a high-level,
easy-to-use interface on top of the auto-generated OpenAPI client.
"""
import asyncio
from typing import Optional, List, Dict, Any
from datetime import datetime
import hindsight_client_api
from hindsight_client_api.api import memory_operations_api, reasoning_api, agent_management_api
from hindsight_client_api.models import (
search_request,
batch_put_request,
memory_item,
think_request,
)
def _run_async(coro):
"""Run an async coroutine synchronously."""
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(coro)
class Hindsight:
"""
High-level, easy-to-use Hindsight API client.
Example:
```python
from hindsight_client import Hindsight
client = Hindsight(base_url="http://localhost:8888")
# Store a memory
client.put(agent_id="alice", content="Alice loves AI")
# Search memories
results = client.search(agent_id="alice", query="What does Alice like?")
# Generate contextual answer
answer = client.think(agent_id="alice", query="What are my interests?")
```
"""
def __init__(self, base_url: str, timeout: float = 30.0):
"""
Initialize the Hindsight client.
Args:
base_url: The base URL of the Hindsight API server
timeout: Request timeout in seconds (default: 30.0)
"""
config = hindsight_client_api.Configuration(host=base_url)
self._api_client = hindsight_client_api.ApiClient(config)
self._memory_api = memory_operations_api.MemoryOperationsApi(self._api_client)
self._reasoning_api = reasoning_api.ReasoningApi(self._api_client)
self._agent_api = agent_management_api.AgentManagementApi(self._api_client)
def __enter__(self):
"""Context manager entry."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit."""
self.close()
def close(self):
"""Close the API client."""
if self._api_client:
_run_async(self._api_client.close())
# Simplified methods for main operations
def put(
self,
agent_id: str,
content: str,
event_date: Optional[datetime] = None,
context: Optional[str] = None,
document_id: Optional[str] = None,
) -> Dict[str, Any]:
"""
Store a single memory (simplified interface).
Args:
agent_id: The agent ID
content: Memory content
event_date: Optional event timestamp
context: Optional context description
document_id: Optional document ID for grouping
Returns:
Response with success status
"""
return self.put_batch(
agent_id=agent_id,
items=[{"content": content, "event_date": event_date, "context": context}],
document_id=document_id,
)
def put_batch(
self,
agent_id: str,
items: List[Dict[str, Any]],
document_id: Optional[str] = None,
) -> Dict[str, Any]:
"""
Store multiple memories in batch.
Args:
agent_id: The agent ID
items: List of memory items with 'content' and optional 'event_date', 'context'
document_id: Optional document ID for grouping memories
Returns:
Response with success status and item count
"""
memory_items = [
memory_item.MemoryItem(
content=item["content"],
event_date=item.get("event_date"),
context=item.get("context"),
)
for item in items
]
request_obj = batch_put_request.BatchPutRequest(
items=memory_items,
document_id=document_id,
)
response = _run_async(self._memory_api.batch_put_memories(agent_id, request_obj))
return response.to_dict() if hasattr(response, 'to_dict') else response
def search(
self,
agent_id: str,
query: str,
fact_type: Optional[List[str]] = None,
max_tokens: int = 4096,
thinking_budget: int = 100,
) -> List[Dict[str, Any]]:
"""
Search memories using semantic similarity.
Args:
agent_id: The agent ID
query: Search query
fact_type: Optional list of fact types to filter (world, agent, opinion)
max_tokens: Maximum tokens in results (default: 4096)
thinking_budget: Token budget for search (default: 100)
Returns:
List of search results
"""
request_obj = search_request.SearchRequest(
query=query,
fact_type=fact_type,
thinking_budget=thinking_budget,
max_tokens=max_tokens,
trace=False,
)
response = _run_async(self._memory_api.search_memories(agent_id, request_obj))
if hasattr(response, 'results'):
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
return []
def think(
self,
agent_id: str,
query: str,
thinking_budget: int = 50,
context: Optional[str] = None,
) -> Dict[str, Any]:
"""
Generate a contextual answer based on agent identity and memories.
Args:
agent_id: The agent ID
query: The question or prompt
thinking_budget: Token budget for thinking (default: 50)
context: Optional additional context
Returns:
Response with answer text, facts used, and new opinions
"""
request_obj = think_request.ThinkRequest(
query=query,
thinking_budget=thinking_budget,
context=context,
)
response = _run_async(self._reasoning_api.think(agent_id, request_obj))
return response.to_dict() if hasattr(response, 'to_dict') else response
# Full-featured methods (expose more options)
def search_memories(
self,
agent_id: str,
query: str,
fact_type: Optional[List[str]] = None,
thinking_budget: int = 100,
max_tokens: int = 4096,
trace: bool = False,
question_date: Optional[str] = None,
) -> Dict[str, Any]:
"""
Search memories with all options (full-featured).
Args:
agent_id: The agent ID
query: Search query
fact_type: Optional list of fact types to filter
thinking_budget: Token budget for thinking
max_tokens: Maximum tokens in results
trace: Enable trace output
question_date: Optional ISO format date string
Returns:
Full search response with results and optional trace
"""
request_obj = search_request.SearchRequest(
query=query,
fact_type=fact_type,
thinking_budget=thinking_budget,
max_tokens=max_tokens,
trace=trace,
question_date=question_date,
)
response = _run_async(self._memory_api.search_memories(agent_id, request_obj))
return response.to_dict() if hasattr(response, 'to_dict') else response
def list_memories(
self,
agent_id: str,
fact_type: Optional[str] = None,
search_query: Optional[str] = None,
limit: int = 100,
offset: int = 0,
) -> Dict[str, Any]:
"""List memory units with pagination."""
response = _run_async(self._memory_api.list_memories(
agent_id=agent_id,
fact_type=fact_type,
q=search_query,
limit=limit,
offset=offset,
))
return response.to_dict() if hasattr(response, 'to_dict') else response
def create_agent(
self,
agent_id: str,
name: Optional[str] = None,
background: Optional[str] = None,
) -> Dict[str, Any]:
"""Create or update an agent."""
from hindsight_client_api.models import create_agent_request
request_obj = create_agent_request.CreateAgentRequest(
name=name,
background=background,
)
response = _run_async(self._agent_api.create_or_update_agent(agent_id, request_obj))
return response.to_dict() if hasattr(response, 'to_dict') else response
# Async methods (native async, no _run_async wrapper)
async def aput_batch(
self,
agent_id: str,
items: List[Dict[str, Any]],
document_id: Optional[str] = None,
) -> Dict[str, Any]:
"""
Store multiple memories in batch (async).
Args:
agent_id: The agent ID
items: List of memory items with 'content' and optional 'event_date', 'context'
document_id: Optional document ID for grouping memories
Returns:
Response with success status and item count
"""
memory_items = [
memory_item.MemoryItem(
content=item["content"],
event_date=item.get("event_date"),
context=item.get("context"),
)
for item in items
]
request_obj = batch_put_request.BatchPutRequest(
items=memory_items,
document_id=document_id,
)
response = await self._memory_api.batch_put_memories(agent_id, request_obj)
return response.to_dict() if hasattr(response, 'to_dict') else response
async def aput(
self,
agent_id: str,
content: str,
event_date: Optional[datetime] = None,
context: Optional[str] = None,
document_id: Optional[str] = None,
) -> Dict[str, Any]:
"""
Store a single memory (async).
Args:
agent_id: The agent ID
content: Memory content
event_date: Optional event timestamp
context: Optional context description
document_id: Optional document ID for grouping
Returns:
Response with success status
"""
return await self.aput_batch(
agent_id=agent_id,
items=[{"content": content, "event_date": event_date, "context": context}],
document_id=document_id,
)
async def asearch(
self,
agent_id: str,
query: str,
fact_type: Optional[List[str]] = None,
max_tokens: int = 4096,
thinking_budget: int = 100,
) -> List[Dict[str, Any]]:
"""
Search memories using semantic similarity (async).
Args:
agent_id: The agent ID
query: Search query
fact_type: Optional list of fact types to filter (world, agent, opinion)
max_tokens: Maximum tokens in results (default: 4096)
thinking_budget: Token budget for search (default: 100)
Returns:
List of search results
"""
request_obj = search_request.SearchRequest(
query=query,
fact_type=fact_type,
thinking_budget=thinking_budget,
max_tokens=max_tokens,
trace=False,
)
response = await self._memory_api.search_memories(agent_id, request_obj)
if hasattr(response, 'results'):
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
return []
async def athink(
self,
agent_id: str,
query: str,
thinking_budget: int = 50,
context: Optional[str] = None,
) -> Dict[str, Any]:
"""
Generate a contextual answer based on agent identity and memories (async).
Args:
agent_id: The agent ID
query: The question or prompt
thinking_budget: Token budget for thinking (default: 50)
context: Optional additional context
Returns:
Response with answer text, facts used, and new opinions
"""
request_obj = think_request.ThinkRequest(
query=query,
thinking_budget=thinking_budget,
context=context,
)
response = await self._reasoning_api.think(agent_id, request_obj)
return response.to_dict() if hasattr(response, 'to_dict') else response
# Alias for backward compatibility
HindsightClient = Hindsight